image auteur
Lucas BENTO-VERSACE 6min • 02-04-2024

Angular - 3 choses à savoir sur le pipe async

Angular - 3 choses à savoir sur le pipe async

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])
      );
  }
async.component.ts

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), []);
    }
  }
async.component.ts

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>;
  }
child.component.ts

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), []));
    }
  }
parent.component.ts

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',
    ...
  })
ChangeDetectionStrategy.OnPush

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 !

Auteur

image auteur
Lucas BENTO-VERSACE Alternant chez Codewise / Étudiant en développement web lucas.bentoversace@ynov.com
"Alternant chez Codewise, passioné par l'informatique et les technologies depuis mon plus jeune âge. J'ai découvert le monde du web et depuis je ne m'en lasse pas ! Fan de jeux vidéos, de sports et de musiques. "