Il s'avère que le pipe async nous cache bien des choses ! Vous allez le découvrir dans cet article
Vous avez certainement entendu parler du pipe async
d'Angular. Si ce n'est pas le cas, je vous recommande d'aller voir cet article qui explique le fonctionnement du pipe async.
Abonnement longue durée
Aux premiers abords, quand nous utilisons l'async pipe basiquement, nous pensons directement aux valeurs récupérées à partir d'un appel http
.
Vous émettez une requête http, obtenez un observable en réponse de votre requête, vous appliquez quelques transformations : map, filter
etc.
Et enfin vous avez un Observable
correspondant au modèle que votre composant attend.
Dans ce scénario, on dit que cet Observable est de courte durée. Il émet une valeur et se complète juste après.
Cependant, il est tout à fait possible de s'abonner à des Observables émettant plusieurs valeurs via le pipe async.
Regardez cet exemple où un Observable émet un tableau de nombres mais au lieu d'émettre un tableau une fois, il reçoit des valeurs progressivement toutes les 100ms.
1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector: 'app-async',
template: `
<ul>
<li *ngFor="let item of items | async"></li>
</ul>`
})
export class AppComponent {
items = Observable.interval(100).pipe(
map(val => [val, val + 1, val + 2])
);
}
Le pipe async présent sur le ngFor
reste abonné aux données émises par l'Observable tant que ce dernier n'est pas completé.
Suivi de références
Vous avez sûrement entendu ou lu qu'avec le pipe async le désabonnement à l'Observable se fait dès que le composant est détruit. Mais il faut savoir aussi qu'il se désabonne dès que la variable contenant l'observable est réassigné !
Regardez cet exemple :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component({
selector: 'my-app',
template: `
<button (click)="newSeq()">Nouvelle séquence</button>
<ul>
<li [style.color]="item.color"
*ngFor="let item of items | async"></li>
</ul>`
})
export class AppComponent {
items$: Observable<any>;
constructor () {
this.newSeq();
}
newSeq() {
// générer une couleur
let color = '#' + Math.random().toString(16).slice(-6);
this.items$ = Observable.interval(1000)
.scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []);
}
}
Dans cet exemple, un premier observable est affecté à la variable items$ lors de l'exécution du constructeur.
Si par la suite on réexecute la méthode newSeq()
, un nouvel Observable sera affecté à la variable items$
. Dans ce cas-là, le pipe async se désabonnera automatiquement de l'Observable qui était dans items$
auparavant.
Cela vous protège des fuites de mémoires et vous évite d'avoir à vous soucier du désabonnement. Donc vous gardez votre code lisible, simple et performant.
Marquer pour vérification
La dernière chose à savoir est que le pipe async marque votre composant comme devant être re-rendu (re-rendering) à chaque fois que l'observable reçoit une nouvelle valeur.
Prenons l'exemple d'un composant parent et enfant. Le composant parent gérera les données et les transmettra à l'enfant via un @Input()
.
1
2
3
4
5
6
7
8
9
10
11
12
@Component({
selector: 'app-child',
template: `
<ul>
<li [style.color]="item.color"
*ngFor="let item of items"></li>
</ul>`
})
export class SeqComponent {
@Input()
items: Array<any>;
}
Le composant parent transmettra les valeurs émises par l'Observable via l'input à chaque enfant.
Notez l'utilisation du pipe async dans l'expression de liaison de propriété [items]="seq | async"
pour transmettre le tableau au lieu de l'Observable car c'est ce qu'attend le composant enfant.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component({
selector: 'app-parent',
template: `
<button (click)="newSeq()">Nouvelle séquence</button>
<ul>
<app-child *ngFor="let seq of seqs" [items]="seq | async"></app-child>
</ul>`
})
export class AppComponent {
seqs = [];
constructor () {
this.newSeq();
}
newSeq() {
// générer couleur
let color = '#' + Math.random().toString(16).slice(-6);
this.seqs.push(Observable.interval(1000)
.scan((acc, num)=>[{num, color }, ...acc].slice(0, 5), []));
}
}
Si vous utilisez la détection de changement par défaut dans tous vos composants, cela signifie que tout votre arbre de composants sera re-rendu dès lors qu'un évènement géré par Angular sera déclenché (click sur un bouton géré dans l'application par exemple). Le markForCheck()
déclenché par le pipe async n'aura donc aucun effet car tous les composants sans exceptions seront re-rendus, même ceux qui n'ont pas été marqués via markForCheck()
Cependant c'est un gaspillage de ressource car cela signifie que tous les composants sont re-rendus à chaque fois.
Si par contre, la détection de changement pour un de nos composants est définie à : OnPush
, cela signifie qu'il ne sera pas re-rendu, sauf dans les cas où le composant à été marqué dirty par la méthode markForCheck()
.
Et le pipe async exécute markForCheck()
à notre place, car il faudra bien évidemment recalculer le rendu du DOM après qu'on ait reçu une valeur d'observable à afficher dans le DOM.
1
2
3
4
5
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'app-child',
...
})
Si tous vos autres composants sont en ChangeDetectionStrategy.Default, ils seront recalculés à chaque évènement aussi malheureusement. Mais au moins vous évitez à cet endroit-là, les re-rendus inutiles des composants grâce à OnPush.
Conclusion
Voilà tout pour le pipe async, ceci sont des notions avancées. En esperant vous avoir éclairé au mieux sur cet outil qui permet en outre d'éviter les fuites de mémoires et gagner en lisibilité.
N'hésitez pas à laisser un commentaire !