• 12 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 20/06/2022

Préparez le module

Plus une application devient complexe, plus elle a de données à gérer, et plus ça devient compliqué pour les développeurs de les gérer proprement et sans risque de corruption.

En parallèle, une application qui a plus de components qui touchent aux données risque d'être de moins en moins performante, entre les requêtes inutiles au serveur et les cycles de change detection de plus en plus rapprochés et coûteux.

Pour répondre à ces problématiques, il faut implémenter un système de state management. Le state management, c'est le suivi de l'état de votre application :

  • toutes les données : qu'elles soient reçues depuis le serveur ou entrées par l'utilisateur ;

  • l'interface : menu ouvert, filtres activés, thème sélectionné…

Je vous propose d'implémenter un système "fait maison" :

  • ce système utilisera un pattern très important et utile, Subject-as-a-service, et créer votre propre système vous aidera à mieux l'apprivoiser ;

  • pour des applications de complexité moyenne, un système "maison" peut amplement suffire, surtout si les délais ne permettent pas l'apprentissage et l'implémentation d'une bibliothèque tierce ;

  • cela vous fera plus d'entraînement à manipuler des Observables ;

  • pas besoin de passer par l'apprentissage d'une bibliothèque pour découvrir des facettes importantes du pattern.

Ces montages réactifs facilitent la création de fonctionnalités avancées, et permettent de changer la stratégie de change detection en OnPush. OnPush fait qu'un component lance un cycle de change detection dans trois cas :

  • la référence de l'un de ses  @Input  change ;

  • un événement est déclenché dans le component (ou l'un de ses enfants) ;

  • on le lance manuellement.

En gros, ça permet de réduire drastiquement le nombre de cycles de change detection, et de s'approcher du strict minimum. Plus vous avez de components sous le régime OnPush, meilleures seront les performances de votre application.

Dans ce premier chapitre, vous préparerez le module et, surtout, vous mettrez en place le service qui en gérera le state.

Construisez le module et ses routes

Vous commencez à bien connaître le processus ! Créez le module avec :

ng g m reactive-state --routing

Ajoutez la route  'reactive-state'  à AppRoutingModule, et implémentez le lazy loading :

{ path: 'reactive-state', loadChildren: () => import('./reactive-state/reactive-state.module').then(m => m.ReactiveStateModule) },

Comme ça, vous pouvez ajouter un lien dans HeaderComponent :

<ul>
    <li><a routerLink="/social-media">Social Media</a></li>
    <li><a routerLink="/complex-form">Complex Form</a></li>
    <li><a routerLink="/reactive-state">Reactive State</a></li>
</ul>

Vous aurez besoin de deux components : CandidatesListComponent et SingleCandidateComponent. Créez-les avec le CLI avec :

ng g c reactive-state/components/candidate-list
ng g c reactive-state/components/single-candidate

Dans ReactiveStateRoutingModule, ajoutez les deux routes principales, ainsi qu'une redirection depuis la route vide :

const routes: Routes = [
  { path: 'candidates', component: CandidateListComponent },
  { path: 'candidates/:id', component: SingleCandidateComponent },
  { path: '', pathMatch: 'full', redirectTo: 'candidates' }
];

Les bases du module sont posés : passons aux components et parlons de stratégie de change detection.

Passez les components en OnPush

Le système de change detection d'Angular fait partie des grandes forces du framework : c'est ce qui vous permet de modifier une donnée dans votre component TypeScript, et d'en voir le résultat dans le DOM. C'est plutôt magique.

Oui mais ! Le problème est que ce système est terriblement inefficace : tous les components sont checkés à chaque cycle, et un cycle est déclenché pour tout changement, où que ce soit dans l'application.

Pour une petite application avec peu de components, ce n'est pas très grave, mais lorsque votre application devient plus complexe, vous allez vite perdre en performances si chaque micromodification déclenche un recalcul de l'application entière.

Du coup, Angular nous propose de modifier la stratégie, avec notamment l'OnPush.

Dire à Angular qu'un component emploie la stratégie OnPush, c'est lui dire qu'un cycle de change detection doit être déclenché si et seulement si :

  • la référence de l'un de ses @Input()  change ;

  • un événement HTML est émis dans le component ou dans l'un de ses enfants ;

  • on choisit de déclencher manuellement la change detection. 

Du coup, la stratégie OnPush fonctionne particulièrement bien avec un montage Observable, où chaque modification d'objet ou de tableau, par exemple, fait émettre un tout nouvel objet ou un tout nouveau tableau. La référence est modifiée, et le cycle de change detection nécessaire pour l'affichage des nouvelles données est déclenché.

Grâce à un système de state management réactif, vous pouvez profiter des gains de performances de la stratégie OnPush.

Profitez-en donc pour passer les deux components de ce nouveau module sur cette stratégie. Voici ce que ça donne sur CandidateListComponent :

import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-candidate-list',
  templateUrl: './candidate-list.component.html',
  styleUrls: ['./candidate-list.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CandidateListComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Préparez le modèle et le service

Vous aurez besoin d'un modèle de données pour les candidats, donc créez  candidate.model.ts  dans un dossier models du nouveau module :

export class Candidate {
  id!: number;
  firstName!: string;
  lastName!: string;
  email!: string;
  job!: string;
  department!: string;
  company!: string;
  imageUrl!: string;
}

Maintenant, vous allez mettre en place la base du service qui gérera le state réactif avec des BehaviorSubjects.

Créez candidates.service.ts dans un nouveau dossier services : 

import { Injectable } from '@angular/core';

@Injectable()
export class CandidatesService {
}

Ajoutez-le aux  providers  de ReactiveStateModule, et profitez-en pour ajouter SharedModule à ses  imports :

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ReactiveStateRoutingModule } from './reactive-state-routing.module';
import { CandidateListComponent } from './components/candidate-list/candidate-list.component';
import { SingleCandidateComponent } from './components/single-candidate/single-candidate.component';
import { CandidatesService } from './services/candidates.service';
import { SharedModule } from '../shared/shared.module';


@NgModule({
  declarations: [
    CandidateListComponent,
    SingleCandidateComponent
  ],
  imports: [
    CommonModule,
    ReactiveStateRoutingModule,
    SharedModule
  ],
  providers: [
    CandidatesService
  ]
})
export class ReactiveStateModule { }

Il y a deux éléments de state – des sources de données – à exposer dans CandidatesService :

  • loading$  – qui émettra  true  ou  false  selon qu'un chargement est en cours ou non ;

  • candidates$  – qui émettra des tableaux de  Candidate .

Pour créer des BehaviorSubjects qui sont exposés comme des Observables simples (empêchant des components d'appeler leur méthode  next ), je vous propose un pattern  private + getter  :

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Candidate } from '../models/candidate.model';

@Injectable()
export class CandidatesService {
    
  private _loading$ = new BehaviorSubject<boolean>(false);
  get loading$(): Observable<boolean> {
    return this._loading$.asObservable();
  }

  private _candidates$ = new BehaviorSubject<Candidate[]>([]);
  get candidates$(): Observable<Candidate[]> {
    return this._candidates$.asObservable();
  }
  
}

Ce système permet de traiter  loading$  et  candidates$  comme des variables.

Par exemple, dans CandidateListComponent :

export class CandidateListComponent implements OnInit {

  loading$!: Observable<boolean>;

  constructor(private candidatesService: CandidatesService) { }

  ngOnInit(): void {
    this.loading$ = this.candidatesService.loading$;
  }

}

Le service aura besoin du HttpClient  , et vous pouvez dès maintenant implémenter une méthode – qui ne sera appelée qu'à l'intérieur du service – pour faire émettre un nouvel état de chargement :

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { Candidate } from '../models/candidate.model';

@Injectable()
export class CandidatesService {
  constructor(private http: HttpClient) {}

  private _loading$ = new BehaviorSubject<boolean>(false);
  get loading$(): Observable<boolean> {
    return this._loading$.asObservable();
  }

  private _candidates$ = new BehaviorSubject<Candidate[]>([]);
  get candidates$(): Observable<Candidate[]> {
    return this._candidates$.asObservable();
  }

  private setLoadingStatus(loading: boolean) {
    this._loading$.next(loading);
  }
}

Appeler  next  sur l'un des BehaviorSubjects du service, c'est s'assurer que tous les components qui sont souscrits à leurs Observables recevront cette nouvelle donnée.

En résumé

  • Le pattern Subject-as-a-service est basé sur des BehaviorSubjects qui "stockent" et émettent les dernières versions des données.

  • Ce pattern permet d'appliquer la stratégie OnPush de change detection. 

  • Créer une variable  private  dans un service et l'exposer via un getter permet de la traiter comme une variable depuis l'extérieur du service, tout en limitant l'accès au strict nécessaire.

Maintenant que le module est prêt, rendez-vous au prochain chapitre pour récupérer les premières données à gérer !

Exemple de certificat de réussite
Exemple de certificat de réussite