ChangeDetectionStrategy dans Angular (de zéro à héros) - Partie 1/2
Tags: angular, performance, programming, javascript
Published: 2023-03-06 06:20:04


![Détection de changement dans Angular](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9cb0pxm0xwstz0actkio.png)

# 1- Change Detection Strategy

La stratégie de détection de changement dans Angular régit la manière dont Angular décide s'il faut ou non mettre à jour l'interface utilisateur lorsque le modèle de données change (via l'annotation @Input()). 

En Angular, il existe deux stratégies de détection de changement disponibles.

## 1.1- Default Change Detection Strategy

Cette stratégie de détection des changements est par défaut dans Angular. elle vérifie toutes les propriétés du modèle de données à chaque cycle de rendu pour déterminer si des modifications ont été effectuées. Si des modifications sont détectées, Angular met à jour l'interface utilisateur en conséquence. Cette méthode assure une grande précision, mais peut ralentir les performances des applications complexes qui ont de nombreuses données en constante évolution. Dans ces cas-là, il est recommandé de passer à une stratégie de détection de changements personnalisée pour améliorer les performances.

{% embed https://stackblitz.com/edit/angular-performances-onpush?embed=1&file=src/app/app.component.ts %}

> NB: Quand il y a un changement, la carte devient jaune.

## 1.2- OnPush Change Detection Strategy

La stratégie de détection de changements OnPush est une stratégie de performance optimisée. Elle vérifie les modifications uniquement lorsque les données sont explicitement modifiées via des actions telles que des clics de bouton ou des entrées utilisateur, ou lorsque de nouvelles références d'objets ou de tableaux sont fournies. Si aucun changement n'est détecté, Angular ne met pas à jour l'interface utilisateur. Cette stratégie est particulièrement utile pour les applications complexes avec des données statiques. Toutefois, vous devez être prudent lorsque vous utilisez cette stratégie, car si des modifications sont apportées aux données sans en informer Angular, l'interface utilisateur ne sera pas mise à jour.

{% embed https://stackblitz.com/edit/angular-performances-onpush-sj9rgq?embed=1&file=src/app/app.component.ts %}

# 2- Passage par valeur & par référence

Lorsque nous utilisons la stratégie de détection de changement OnPush, nous devons travailler avec des objets immuables, ce qui représente un contrat que nous signons avec Angular. L'avantage d'utiliser l'immuabilité dans le contexte de la détection de changement est que Angular peut effectuer une vérification simple des références pour déterminer si la vue doit être mise à jour. Ces vérifications sont moins coûteuses qu'une comparaison approfondie. Il est important de noter que les variables sont transmises par valeur, tandis que les objets (y compris les tableaux en JavaScript) sont transmis par référence.

## 2.1- Cas d'une variable primitive

```
function sayHello(name) {
   name = "Hello " + name;
}

let user = "Angular";
sayHello(user);
console.log(user); // "Angular"
```

Dans cet exemple, la fonction sayHello reçoit une variable primitive name en argument. Lorsque nous appelons cette fonction avec la variable user, qui contient la chaîne de caractères "Angular", la valeur de user ne change pas. Par conséquent, la variable user n'est pas modifiée en dehors du contexte de la fonction. Il est important de comprendre que les chaînes de caractères, les booléens et les nombres sont transmis par valeur.

## 2.2- Cas d'un object / array

```
// Object
const user = { id: 1, name: 'User1' };
const anotherUser = user;

user.hobby = 'Programming';

console.log(user); // { id: 1, name: 'User1', hobby: 'Programming' }
console.log(anotherUser); // { id: 1, name: 'User1', hobby: 'Programming' }

// Array
const users = ['A', 'B'];
const friends = users;

users.push('C');

console.log(users); // ['A', 'B', 'C']
console.log(friends); // ['A', 'B', 'C']
```

Dans cet exemple, nous créons un objet user avec deux propriétés, id et name, ainsi qu'un tableau users contenant deux éléments. Nous assignons ensuite user à la variable anotherUser, et users à la variable friends. Nous ajoutons ensuite une propriété hobby à l'objet user et un élément C au tableau users. Lorsque nous imprimons user, anotherUser, users et friends, nous pouvons constater que les modifications apportées à user et users ont également été appliquées à anotherUser et friends, respectivement. Cela s'explique par le fait que les objets et les tableaux sont transmis par référence, et non par valeur.

Il est essentiel de comprendre la différence entre les passages par référence et par valeur, car cela nous permettra de mieux comprendre la manière dont nous passons les données à nos composants Angular via le décorateur @Input(), ainsi que la stratégie OnPush.

# 3- Exemples concrets d'utilisation de la stratégie OnPush avec le passage par valeur/référence

Dans l'exemple ci-dessous, il est possible de constater que le clic sur le bouton ne fonctionne pas comme prévu. En effet, cela est dû à la modification directe de la référence de l'objet via la ligne de code suivante :

```
this.card.title = 'Card 1 title changed !!';
```

{% embed https://stackblitz.com/edit/angular-performances-onpush-fnzbhz?embed=1&file=src/app/app.component.html %}

Afin d'obtenir le comportement souhaité dans l'exemple précédent, il est nécessaire de changer la référence de l'objet. Cela peut être réalisé en affectant un nouvel objet à la variable, comme suit :

```
this.card = { title: 'Card 1 title changed !!', description: 'Lorem ipsum 1' };
```

Une autre méthode consiste à utiliser l'opérateur de propagation ("spread operator") pour créer une copie de l'objet existant, puis à remplacer la propriété désirée, comme suit :

```
this.card = { ...this.card, title: 'Card 1 title changed !!' };
```

Il est par ailleurs possible d'utiliser des méthodes helpers telles que **cloneDeep** de la bibliothèque **lodash** ou la méthode native JavaScript **structuredClone** pour réaliser une copie profonde de l'objet.

{% embed https://stackblitz.com/edit/angular-performances-onpush-zgwpsx?embed=1&file=src/app/app.component.ts %}

### OnPush & l'asynchronisme
Dans l'exemple suivant, lorsqu'on clique sur le bouton "Add synchronically", Angular effectue une détection de changement et met à jour la vue conformément aux modifications apportées.

{% embed https://stackblitz.com/edit/angular-performances-onpush-1?embed=1&file=src/app/app.component.ts %}

Il peut sembler logique que cela fonctionne avec toutes les API asynchrones qui déclenchent la détection de changement, mais ce n'est malheureusement pas le cas. En effet, un clic sur le bouton "Add Asynchronically" ne fonctionnera pas.

Notez que l'asynchronisme peut être déclenché par l'utilisation de certaines fonctions telles que setTimeout, setInterval, des Promises et Http, entre autres.

# 4- Lancer la "Change Detection" explicitement.
 
Angular nous fournit un moyen pour déclencher nous-mêmes la détection de changements lorsque cela est nécessaire.

Une des méthodes pour déclencher manuellement la détection des changements dans Angular est d'utiliser la fonction detectChanges(). Cela permet d'indiquer à Angular d'exécuter la détection de changements sur le composant et ses enfants.

{% embed https://stackblitz.com/edit/angular-performances-onpush-1-ntyrcb?embed=1&file=src/app/app.component.ts %}

# 5- Async Pipe
Le pipe async souscrit à un observable ou une promesse et renvoie la dernière valeur qu'il a émise.

Voyons un exemple simple d'un composant qui utilise la stratégie OnPush et qui reçoit un observable en entrée (@input()).

{% embed https://stackblitz.com/edit/angular-performances-onpush-2-s2u9fc?embed=1&file=src/app/card/card.component.html %}

Lorsque nous cliquons sur le bouton, nous ne verrons pas la vue mise à jour. Cela est dû au fait qu'aucune des conditions mentionnées ci-dessus ne s'est produite, Angular ne vérifiera donc pas le composant lors du cycle de détection de changement actuel.

Une solution possible consiste, comme expliqué précédemment, à utiliser la méthode detectChanges() pour déclencher explicitement la détection de changements.

{% embed https://stackblitz.com/edit/angular-performances-onpush-2-deqthy?embed=1&file=src/app/app.component.ts %}

Très bien ! Utilisons maintenant le pipe async.

Le pipe async est un moyen pratique de gérer des valeurs asynchrones dans un template Angular. Il souscrit automatiquement à un observable ou à une promesse et retourne la dernière valeur émise.

Dans notre cas, nous pouvons utiliser le pipe async pour notre observable cards$ et Angular se chargera automatiquement de détecter les changements et de mettre à jour la vue lorsque la valeur de l'observable change.

Voici comment nous pouvons utiliser le pipe async dans notre example:

{% embed https://stackblitz.com/edit/angular-performances-onpush-2?embed=1&file=src/app/app.component.ts %}

# 6- Conclusion

Dans cet article, nous avons examiné les deux stratégies de détection de changement disponibles dans Angular, à savoir la stratégie **par défaut** et la stratégie **OnPush**. 

Nous avons souligné l'importance de travailler avec des objets immuables lors de l'utilisation de la stratégie OnPush et expliqué les différences entre les passages par référence et par valeur. 

En outre, nous avons fourni des exemples pratiques d'utilisation de la stratégie OnPush avec le passage par valeur/référence et nous avons également illustré l'utilisation concrète le pipe async.

J'espère que cet article vous a été bénéfique, une deuxième partie de cet article sera bientôt disponible, nous aurons l'occasion de nous retrouver pour approfondir davantage le sujet.