1.
Composants (Components)
Définition
● Un composant est l'unité de base dans Angular, définissant une partie de l'interface
utilisateur avec son propre comportement.
● Structure d'un composant :
○ @Component : Décorateur pour définir un composant.
○ template : HTML de la vue.
○ style : CSS/SCSS pour la vue.
○ logic : Classe TypeScript.
Exemple :
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.css']
})
export class MyComponent {
title = 'Hello Angular';
}
2. Bindings en Angular
Types de Bindings
Property Binding : Lie une propriété du composant à un attribut d’un élément.
<img [src]="imageUrl" />
Event Binding : Lie un événement du DOM à une méthode dans le composant.
<button (click)="onClick()">Click Me</button>
Two-way Binding : Liaison bidirectionnelle entre la vue et le modèle via [(ngModel)].
<input [(ngModel)]="username" />
●
Exemple avec ngModel (Two-way Binding) :
<input [(ngModel)]="username" />
<p>Bonjour, {{ username }}</p>
3. Directives
Types de Directives
● Directives Structurelles : Modifient la structure du DOM (e.g., *ngIf, *ngFor).
● Directives d'Attribut : Modifient le comportement ou l'apparence d'un élément
existant (e.g., ngClass, ngStyle).
Création d'une Directive Personnalisée
@HostBinding : Lie une propriété à un attribut du DOM.
@HostBinding('style.backgroundColor') bg = 'red';
1.
@HostListener : Écoute un événement et déclenche une méthode.
@HostListener('click') onClick() {
this.bg = 'blue';
}
2.
Directive Paramétrable
@Input() highlightColor = 'yellow';
@HostListener('mouseenter') onMouseEnter() {
this.bg = this.highlightColor;
}
<div appHighlight [highlightColor]="'green'"></div>
4. Pipes
Définition
● Pipes : Permet de transformer des données dans les templates.
Exemples :
Pipe Pur (par défaut) : La méthode transform() est invoquée uniquement lorsque les
arguments d’entrée changent.
@Pipe({ name: 'myPipe', pure: true })
export class MyPipe {
transform(value: number) {
return value * 2;
}
}
1.
Pipe Impur : La méthode transform() est appelée à chaque changement, même si les
arguments n’ont pas changé.
@Pipe({ name: 'impurePipe', pure: false })
export class ImpurePipe { ... }
2.
5. Services et Injection de Dépendances (DI)
Définition
● Services : Contiennent la logique métier ou des fonctionnalités partagées dans
l’application. Angular utilise un système d’injection de dépendances (DI) pour
gérer l'instanciation des services.
Types de Providers :
useClass : Utilise une classe comme fournisseur.
providers: [{ provide: TodoService, useClass: TodoService }]
1.
useValue : Injecte une constante ou une valeur.
providers: [{ provide: 'TOKEN', useValue: 'Hello Angular' }]
2.
useFactory : Génère dynamiquement une valeur.
providers: [
{ provide: TodoService, useFactory: (http: HttpClient) => new TodoService(http), deps:
[HttpClient] }
]
3.
useExisting : Crée un alias pour un provider existant.
providers: [{ provide: 'NEW_TOKEN', useExisting: 'OLD_TOKEN' }]
4.
Injection dans un composant :
constructor(private todoService: TodoService) {}
6. Modules et Lazy Loading
Lazy Loading :
Charge les modules uniquement lorsqu'ils sont nécessaires, optimisant ainsi la performance.
const routes: Routes = [
{ path: 'feature', loadChildren: () => import('./feature/feature.module').then(m =>
m.FeatureModule) }
];
Eager Loading :
Charge immédiatement tous les modules nécessaires dès le démarrage de l'application.
const routes: Routes = [
{ path: 'feature', component: FeatureComponent }
];
7. Hierarchical Dependency Injection
● DI Hiérarchique : Angular cherche d’abord un service au niveau du composant, puis
dans le parent et, enfin, dans les modules si nécessaire.
○ Si un service est trouvé dans un composant, il sera injecté. Sinon, Angular
remontera dans l’arbre des composants et les modules pour chercher un
fournisseur.
8. Résolution Modifiée de l'Injection
@Host et @Self
● @Self : Cherche dans l’injecteur actuel du composant.
● @Host : Cherche dans l'injecteur du composant hôte (le parent, ou le composant
dans lequel il est projeté).
● @SkipSelf : Ignore l'injecteur actuel et cherche dans les parents.
9. Signals
Voici la partie complétée avec Signals, Input/Output Signals, Computed Signals, et leur
utilisation dans Angular.
Définition de Signals
Un signal est une variable réactive qui peut être observée et qui déclenche des effets
lorsqu'elle est modifiée.
Création et utilisation d'un signal :
import { signal } from '@angular/core';
const counter = signal(0); // Création d'un signal avec une valeur initiale de 0
Effet associé au signal :
Un effet est une fonction qui observe un signal et réagit lorsque la valeur de ce signal
change.
import { effect } from '@angular/core';
const effect = effect(() => {
console.log('Valeur du compteur : ', counter());
});
Chaque fois que le signal counter est modifié, l'effet est déclenché.
Mise à jour d'un signal :
counter.set(1); // Change la valeur de 'counter' et déclenche l'effet.
Input/Output Signals
Les Input Signals et Output Signals permettent de transmettre des valeurs réactives entre
un composant parent et un composant enfant, ou d'émettre des événements réactifs depuis
un enfant vers un parent.
Input Signal :
Dans un composant enfant, vous pouvez lier un signal à une entrée dynamique pour réagir
aux changements du parent.
import { signal, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: ` <p>{{ childMessage() }}</p> `
})
export class ChildComponent {
@Input() childMessage = signal('Hello from Parent');
}
Ici, le signal childMessage est un Input Signal et peut être mis à jour par le parent.
Output Signal :
Dans un composant enfant, vous pouvez émettre un événement réactif à l’aide d’un signal.
import { signal, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `<button (click)="sendMessage()">Send Message</button>`
})
export class ChildComponent {
@Output() sendMessageEvent = new EventEmitter<string>();
sendMessage() {
this.sendMessageEvent.emit('Hello from Child');
}
}
Dans ce cas, le composant enfant émet un signal via sendMessageEvent à son parent.
Parent utilisant Input/Output Signals :
@Component({
selector: 'app-parent',
template: `
<app-child [childMessage]="parentMessage"
(sendMessageEvent)="receiveMessage($event)"></app-child>
<p>Received: {{ receivedMessage }}</p>
`
})
export class ParentComponent {
parentMessage = signal('Initial Message from Parent');
receivedMessage = '';
receiveMessage(message: string) {
this.receivedMessage = message;
}
}
Dans ce cas, le parent utilise childMessage comme un Input Signal et reçoit les données
via sendMessageEvent en tant qu’Output Signal.
Computed Signals
Les computed signals sont des signaux dérivés qui calculent leur valeur en fonction
d'autres signaux. Ils se recalculent automatiquement dès qu'un signal dépendant change.
Création d'un Computed Signal :
Un signal peut être défini de manière à dépendre d'autres signaux.
import { computed, signal } from '@angular/core';
const count = signal(0);
const doubleCount = computed(() => count() * 2);
Dans cet exemple, doubleCount est un signal dérivé qui renvoie deux fois la valeur de
count. Chaque fois que count est modifié, doubleCount sera recalculé
automatiquement.
Exemple d'utilisation de Computed Signals dans un composant :
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<p>Double Count: {{ doubleCount() }}</p>
<button (click)="increment()">Increment</button>
`
})
export class CounterComponent {
count = signal(0);
doubleCount = computed(() => this.count() * 2);
increment() {
this.count.set(this.count() + 1); // Change la valeur de count et déclenche le recalcul de
doubleCount
}
}
Comportement des Signals avec Computed :
● Recalcul automatique : Si le signal count change, le signal doubleCount se
recalculera automatiquement.
● Optimisation : Comme les computed sont réactifs, cela permet une optimisation
des performances, car seuls les signaux qui changent réellement déclencheront des
mises à jour dans la vue ou les calculs.
Conclusion sur Signals, Input/Output, et Computed Signals
Les signals dans Angular apportent une gestion réactive des données avec des avantages
significatifs pour la performance et la réactivité des applications. Voici les points clés :
● Signals : Permettent de créer des variables réactives avec une gestion automatique
des effets associés.
● Input Signals : Permettent de lier des valeurs réactives entre un parent et un enfant.
● Output Signals : Permettent aux enfants d’émettre des événements réactifs vers
leurs parents.
● Computed Signals : Calculent automatiquement des valeurs dérivées d'autres
signaux.
Ces fonctionnalités permettent de gérer des états et des mises à jour de manière plus
efficace, en réduisant les rendus inutiles et en optimisant la réactivité du système.
10. Tree-Shaking et Optimisation
● Tree-Shaking : Processus permettant de supprimer du code inutilisé lors de la
compilation, réduisant ainsi la taille de l'application.
○ Par exemple, si un service n’est jamais utilisé, il sera supprimé du build final
grâce à providedIn: 'root'.
11. providedIn et Module Configuration
● providedIn: 'root' : Un service est automatiquement fourni au niveau de
l'application, ce qui en fait un singleton.
○ Il est injecté dans tout le projet.
● Utilisation dans un module :
○ Le service peut être fourni au niveau du module via le tableau providers de
@NgModule.
Exemple :
@NgModule({
providers: [TodoService]
})
1. Routing dans Angular
Le routing dans Angular permet de gérer la navigation entre différentes vues de l'application
sans recharger la page. C'est un élément clé des applications SPA (Single Page
Applications). Voici un résumé détaillé de la partie routing.
1.1. Configuration des Routes
Les routes sont configurées dans un tableau d'objets Routes, qui contient des informations
sur les chemins (URLs) et les composants associés.
Exemple de configuration des routes :
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
const routes: Routes = [
{ path: '', component: HomeComponent }, // Route pour la page d'accueil
{ path: 'about', component: AboutComponent }, // Route pour la page 'about'
];
@NgModule({
imports: [RouterModule.forRoot(routes)], // Applique les routes définies
exports: [RouterModule]
})
export class AppRoutingModule { }
1.2. Router Outlet
Pour afficher un composant en fonction de la route active, on utilise la directive
<router-outlet></router-outlet>. Elle sert de point d'insertion dynamique pour les
vues selon la route sélectionnée.
Exemple :
<router-outlet></router-outlet> <!-- Le composant correspondant à la route active sera
affiché ici -->
1.3. Naviguer avec routerLink et routerLinkActive
La directive routerLink est utilisée pour naviguer sans recharger la page.
routerLinkActive est utilisée pour ajouter une classe CSS à un élément lorsque la route
associée est active.
Exemple de navigation avec routerLink et routerLinkActive :
<ul>
<li><a [routerLink]="['/home']" routerLinkActive="active">Home</a></li>
<li><a [routerLink]="['/about']" routerLinkActive="active">About</a></li>
</ul>
1.4. Paramètres de Route (Route Params)
Les paramètres de route permettent de passer des données dynamiques dans l'URL.
Utilisez ActivatedRoute pour récupérer ces paramètres.
Exemple de route avec paramètres :
const routes: Routes = [
{ path: 'user/:id', component: UserDetailComponent }
];
Dans le composant pour récupérer le paramètre :
import { ActivatedRoute } from '@angular/router';
export class UserDetailComponent implements OnInit {
userId: number;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.userId = +this.route.snapshot.paramMap.get('id');
}
}
1.5. Query Parameters
Les query parameters sont des paramètres passés dans l'URL après le ?. Utilisez
ActivatedRoute pour les récupérer.
Exemple de navigation avec des query parameters :
this.router.navigate(['/about'], { queryParams: { page: 1, sort: 'asc' } });
Pour récupérer les query parameters :
import { ActivatedRoute } from '@angular/router';
export class AboutComponent implements OnInit {
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
this.route.queryParams.subscribe(params => {
console.log(params['page']); // Accède au paramètre 'page'
});
}
}
2. Forms Template-driven dans Angular
Les formulaires Template-driven dans Angular sont une approche où la logique de gestion
du formulaire est définie principalement dans le template HTML, tandis que la partie logique
est plus légère dans le composant.
2.1. Configuration des Formulaires Template-driven
Les formulaires sont gérés via la directive ngModel, qui associe les champs de formulaire
aux propriétés du composant.
Exemple de formulaire avec ngModel :
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" [(ngModel)]="user.username"
required>
</div>
<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" [(ngModel)]="user.email" required>
</div>
<button type="submit" [disabled]="!userForm.valid">Submit</button>
</form>
2.2. Variables locales et accès au formulaire
Utilisez des références locales pour accéder aux propriétés du formulaire et de ses
champs. Par exemple, #userForm="ngForm" permet d'accéder à l'objet du formulaire
dans le composant.
Exemple d'utilisation de la variable locale pour obtenir des informations sur l'état du
formulaire :
<div *ngIf="userForm.invalid && userForm.touched">Form is invalid</div>
2.3. Validation et états des champs
Angular applique des classes CSS automatiquement sur les champs de formulaire selon
leur état, par exemple :
● dirty: le champ a été modifié.
● valid: le champ est valide.
● pristine: le champ n'a pas été modifié.
● touched: l'utilisateur a cliqué dans le champ.
Exemple de validation du formulaire :
<input type="text" [(ngModel)]="user.username" name="username" required
#username="ngModel">
<div *ngIf="username.invalid && username.touched">Username is required</div>
2.4. Grouping de Contrôles de Formulaire avec ngModelGroup
Pour gérer des groupes de champs, vous pouvez utiliser ngModelGroup. Cela permet de
regrouper logiquement des champs de formulaire dans une même catégorie.
Exemple :
<div ngModelGroup="userInfo" #userGroup="ngModelGroup">
<input type="text" [(ngModel)]="user.name" name="name">
<input type="email" [(ngModel)]="user.email" name="email">
</div>
Dans le composant, vous pouvez accéder au groupe via userGroup.
2.5. ngModelOptions pour personnaliser le comportement du formulaire
Avec ngModelOptions, vous pouvez spécifier quand le modèle doit être mis à jour. Par
exemple, vous pouvez choisir de mettre à jour le modèle lorsque l'utilisateur quitte un champ
(blur) au lieu de chaque changement (change).
Exemple :
<input type="text" [(ngModel)]="user.name" name="name" [ngModelOptions]="{ updateOn:
'blur' }">
3. Conclusion
● Routing dans Angular permet de définir des routes, de naviguer entre différentes
vues sans recharger la page, et de gérer des paramètres dynamiques via des routes
et des query parameters.
● Forms Template-driven simplifie la gestion des formulaires en déléguant la logique
au template. Cela permet une approche déclarative et intuitive pour gérer les
formulaires, valider les champs et réagir aux événements du formulaire.
Exemples clés de code :
Routing :
<a [routerLink]="['/home']" routerLinkActive="active">Home</a>
Forms Template-driven :
<form #userForm="ngForm" (ngSubmit)="onSubmit(userForm)">
<input type="text" name="username" [(ngModel)]="user.username" required>
<button type="submit" [disabled]="!userForm.valid">Submit</button>
</form>
Avec cette organisation, vous êtes prête à maîtriser le routing et les formulaires
Template-driven dans Angular et à construire des applications robustes et dynamiques.
Oui, par défaut, un Observable en RxJS est cold. Cela signifie que chaque abonné
déclenche une exécution indépendante de la source de données, ce qui peut être approprié
pour des flux de données qui ne doivent être partagés qu'avec chaque abonné
individuellement.
Comment rendre un Cold Observable en Hot Observable ?
Pour transformer un Cold Observable en Hot Observable, vous devez utiliser un
mécanisme qui partage le même flux de données entre tous les abonnés. Cela peut être
fait avec des Subjects ou des opérateurs comme share(), shareReplay(), etc.
import { Observable, Subject } from 'rxjs';
// Cold Observable
const coldObservable = new Observable(subscriber => {
console.log('Appel API');
setTimeout(() => {
subscriber.next('Données récupérées');
subscriber.complete();
}, 2000);
});
// Hot Observable avec Subject
const subject = new Subject();
// Transforme le Cold Observable en Hot Observable
coldObservable.subscribe(subject);
subject.subscribe(data => console.log('Abonné 1:', data)); // Reçoit 'Données récupérées'
subject.subscribe(data => console.log('Abonné 2:', data)); // Reçoit 'Données récupérées'
import { Observable } from 'rxjs';
// Cold Observable
const coldObservable = new Observable(subscriber => {
console.log('Appel API');
setTimeout(() => {
subscriber.next('Données récupérées');
subscriber.complete();
}, 2000);
});
// Utilisation de share() pour transformer le Cold Observable en Hot Observable
const hotObservable = coldObservable.pipe(
share() // Partage le flux entre plusieurs abonnés
);
hotObservable.subscribe(data => console.log('Abonné 1:', data)); // Reçoit 'Données
récupérées'
hotObservable.subscribe(data => console.log('Abonné 2:', data)); // Reçoit 'Données
récupérées'
Résumé complet : Programmation réactive, Promesses, Observables,
asyncPipe, et Subject
1. Programmation réactive
La programmation réactive repose sur l’idée de réagir à des événements ou des flux de
données qui se produisent au fil du temps. Ces flux peuvent être des événements utilisateur
(comme des clics de souris), des réponses d'API, ou tout autre type de données
dynamiques. La base de la programmation réactive repose sur deux éléments clés :
● Flux de données (Observables)
● Écouteurs d'événements (Observers)
Cette approche permet de normaliser la gestion des événements et de les combiner
facilement. Elle permet également de rendre les interactions avec l'utilisateur, les appels
API, ou les timers plus prévisibles et facilement gérables.
2. Promesses (Promises)
Une promesse est un objet qui représente la valeur d'un futur résultat d'une opération
asynchrone. Une promesse peut être :
● En attente (pendante)
● Réussie (résolue)
● Échouée (rejetée)
Syntaxe de base :
let promise = new Promise((resolve, reject) => {
let success = true;
if (success) {
resolve("Opération réussie");
} else {
reject("Opération échouée");
}
});
promise.then(result => console.log(result)) // Résout la promesse
.catch(error => console.log(error)); // Gère l'erreur
3. Observables
Un Observable est une structure qui permet de gérer des flux de données asynchrones.
Contrairement à une promesse qui ne gère qu'une seule valeur, un Observable peut
émettre plusieurs valeurs au fil du temps.
Les Observables sont utilisés pour écouter des événements continus (comme des clics ou
des changements de données), et peuvent être manipulés à l'aide de puissants opérateurs.
Différence entre Promesse et Observable :
● Promesse :
○ Gère un seul événement.
○ Non annulable.
○ Utilise then() et catch() pour gérer le résultat et les erreurs.
● Observable :
○ Gère un flux d'événements (multitude de valeurs).
○ Annulable (peut être désabonné).
○ Dispose de nombreux opérateurs pour manipuler les flux de données
(comme map, filter, etc.).
Exemple d'Observable simple avec map et filter :
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
numbers$.pipe(
filter(value => value % 2 === 0), // Filtre les nombres pairs
map(value => value * 2) // Multiplie les valeurs par 2
).subscribe(result => console.log(result)); // Affiche 4, 8
4. Opérateurs d'Observables :
Les opérateurs sont des fonctions qui manipulent les Observables. Ils permettent de
transformer, filtrer ou combiner les flux de données. Il existe deux principaux types
d'opérateurs :
● Opérateurs de création : Créent des Observables, comme of(), from(), etc.
● Opérateurs pipeables : Appliquent des transformations aux flux de données,
comme map(), filter(), mergeMap(), switchMap(), etc.
Exemple avec map et filter :
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const numbers$ = of(1, 2, 3, 4, 5);
numbers$.pipe(
filter(value => value % 2 === 0), // Filtre les valeurs paires
map(value => value * 2) // Multiplie les valeurs par 2
).subscribe(result => console.log(result)); // Résultat : 4, 8
5. Hot vs Cold Observables
Les Observables peuvent être classés en Cold et Hot selon leur comportement :
● Cold Observable : Commence à émettre des valeurs seulement lorsqu'un abonné
s'y inscrit. Chaque abonné reçoit un flux indépendant.
● Hot Observable : Émet des valeurs de manière continue, que l'abonné s'y inscrive
ou non. Tous les abonnés partagent le même flux.
Exemple de Cold Observable :
import { of } from 'rxjs';
const coldObservable$ = of(1, 2, 3);
coldObservable$.subscribe(value => console.log(`Abonné 1 : ${value}`));
coldObservable$.subscribe(value => console.log(`Abonné 2 : ${value}`));
Exemple de Hot Observable (via un Subject) :
import { Subject } from 'rxjs';
const subject = new Subject();
subject.subscribe(value => console.log(`Abonné 1 : ${value}`));
subject.next(1); // Tous les abonnés reçoivent 1
subject.next(2); // Tous les abonnés reçoivent 2
6. asyncPipe :
L'asyncPipe est un pipe dans Angular qui permet d’abonner automatiquement un
Observable et d’afficher sa dernière valeur dans le template. Ce pipe désabonne également
l'Observable lorsque le composant est détruit.
Exemple avec asyncPipe :
<div>{{ myObservable | async }}</div>
Dans ce cas, myObservable est un Observable dont la dernière valeur sera affichée
automatiquement. L'asyncPipe s'abonne et se désabonne de l'Observable de manière
transparente.
7. Subject (Subject = Observable + Observer) :
Un Subject est un type particulier d'Observable qui fonctionne à la fois comme un
Observable et un Observer. Il peut diffuser des valeurs à ses abonnés et recevoir des
valeurs externes à travers la méthode next().
Caractéristiques :
● Émission de valeurs : Un Subject peut émettre des valeurs à l'aide de la méthode
next().
● Annulabilité : Comme un Observable classique, il peut être désabonné.
● Multicast : Il permet de diffuser les mêmes valeurs à plusieurs abonnés en même
temps.
Exemple de Subject :
import { Subject } from 'rxjs';
const subject = new Subject();
subject.subscribe(value => console.log(`Abonné 1 : ${value}`));
subject.subscribe(value => console.log(`Abonné 2 : ${value}`));
subject.next(1); // Les deux abonnés reçoivent 1
subject.next(2); // Les deux abonnés reçoivent 2
Types de Subjects :
● BehaviorSubject : Nécessite une valeur initiale et émet toujours la dernière valeur
à chaque nouvel abonné.
● ReplaySubject : Permet de "rejouer" un historique de valeurs passées aux
abonnés.
● AsyncSubject : Émet uniquement la dernière valeur après que l'Observable a été
complété.
8. Conclusion :
La programmation réactive via RxJS permet de gérer les flux de données de manière
déclarative et flexible. Les Promises et Observables sont des outils essentiels dans ce
cadre, chacun ayant ses propres caractéristiques adaptées aux différents besoins en
matière de gestion d'événements asynchrones. Les Subjects, quant à eux, ajoutent un
niveau de puissance supplémentaire en permettant la diffusion de valeurs à plusieurs
abonnés simultanément.
@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(private http: HttpClient) {}
getData(): Observable<any> {
return this.http.get('https://api.example.com/data'); //
Utilisation de 'fetch' en arrière-plan
}
}
Immutabilité de HttpHeaders
La classe HttpHeaders est immutable, ce qui signifie que vous ne pouvez pas modifier
directement une instance de cette classe après sa création. Toute méthode de modification,
comme set() ou append(), crée une nouvelle instance de HttpHeaders avec les
modifications apportées. Cela permet de préserver l'immuabilité des données et d'éviter les
effets secondaires inattendus lors de la modification des headers.
const headers = new HttpHeaders()
.set('Authorization', 'Bearer token')
.append('X-Custom-Header', 'Value1')
.append('X-Custom-Header', 'Value2')
.set('Content-Type', 'application/json');
this.http.get('https://api.example.com/data', { headers })
.subscribe(response => {
console.log('Response:', response);
});
const params = new HttpParams()
.set('access_token', localStorage.getItem('token'));
return this.http.post(this.apiUrl, personne, {params});
Les intercepteurs sont utilisés pour intercepter et modifier les requêtes et les réponses
HTTP dans une application Angular. Ils font partie du système d'HTTPClient d'Angular.
Cas d'utilisation des intercepteurs :
● Modification des requêtes HTTP : Ajouter des en-têtes (comme des tokens
d'authentification) ou manipuler les données des requêtes avant qu'elles ne soient
envoyées au serveur.
● Traitement des réponses HTTP : Traiter les données de réponse, effectuer des
transformations ou gérer les erreurs globalement.
import { Injectable } from '@angular/core';
import {HttpRequest,HttpHandler,HttpEvent,HttpInterceptor,} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept( request: HttpRequest, next: HttpHandler): Observable>
{return next.handle(request); } }
OUUUUUUUUUUUU
import { HttpInterceptorFn } from "@angular/common/http";
export const newInterceptor: HttpInterceptorFn = (req, next) => {
return next(req); };
Guards en Angular :
Les guards sont utilisés pour protéger les routes ou déterminer si l'utilisateur a accès à
certaines parties de l'application. Ils sont utilisés dans le cadre de la gestion de la navigation,
avant ou après l'accès à une route spécifique.
Cas d'utilisation des guards :
● Contrôle d'accès à une route : Vous pouvez empêcher la navigation vers une route
si l'utilisateur n'est pas authentifié ou autorisé. Par exemple, en utilisant
CanActivate pour vérifier les conditions d'accès avant de permettre à l'utilisateur
de visiter une page.
● Redirection conditionnelle : Rediriger l'utilisateur vers une autre page, comme une
page de connexion, s'il n'a pas les droits nécessaires.
● Prévenir la perte de données non enregistrées : Avec CanDeactivate, vous
pouvez empêcher l'utilisateur de quitter une page avec des modifications non
sauvegardées, comme dans le cas de formulaires non soumis.
Les intercepteurs Un intercepteur est injecté au niveau du provider. Si vous utilisez un
intercepteur fonctionnel, vous utilisez la fonction withInterceptors qui est une des option de
provideHttpClient. Elle prend en paramètre un tableau d’intercepteur
Un intercepteur est injecté au niveau du provider. Si vous utilisez une classe vous devez
passez par la définition du provider puis appeler la méthode
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { LoggingInterceptor } from './logging.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
// Activation des intercepteurs à partir du DI
withInterceptorsFromDi(),
),
// Définition de l'intercepteur de type classe 2eme meth
{ provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true }
]
};
Les intercepteurs : changer la requête Les instances HttpRequest et HttpResponse sont
immuables. Ceci implique que les intercepteurs ne peuvent pas les modifierdirectement.
Solution : les cloner, à l'aide de la méthode clone() et en spécifiantquelles propriétés doivent
être mutées dans la nouvelle instance.
● Utilisez map lorsque vous devez transformer ou modifier les données du flux.
● Utilisez tap lorsque vous avez besoin d'effectuer des actions comme logger, suivre
des événements ou faire des effets secondaires, sans toucher aux données qui
transitent dans le flux.
En résumé, map transforme les données, tandis que tap permet de réagir à des
événements sans modifier les données.
Le besoin de tap dans RxJS survient principalement pour effectuer des effets secondaires
sans modifier les données qui traversent le flux de l'Observable. Contrairement à map, qui
transforme les données et retourne un nouvel Observable avec les valeurs modifiées, tap
est utilisé pour observer les valeurs sans altérer le flux.
Résumé complet : Guards, Intercepteurs, et HTTP en Angular
1. Guards (Sécurisation des routes)
Les Guards sont utilisés pour sécuriser les routes et contrôler l'accès à certaines pages
d'une application Angular. Ils vérifient certaines conditions avant d'autoriser la navigation
vers une route ou de permettre à un utilisateur de quitter une page. Les Guards retournent
soit un boolean, un Observable<boolean>, ou une Promise<boolean>, pour indiquer
si la navigation peut se poursuivre.
Types de Guards :
● CanActivate : Vérifie si une route peut être activée en fonction de certaines
conditions, comme l'authentification de l'utilisateur.
● CanActivateChild : Vérifie si les routes enfants peuvent être activées.
● CanDeactivate : Empêche de quitter une route, souvent utilisé pour avertir
l'utilisateur de la perte de données non enregistrées ou d'un processus en cours.
Exemple d'un Guard CanActivate :
import { Injectable } from '@angular/core';
import { CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (route, state) => {
return this.authService.isAuthenticated(); // Permet l'accès uniquement si l'utilisateur est
authentifié
};
Exemple d'un Guard CanDeactivate :
import { Injectable } from '@angular/core';
import { CanDeactivate } from '@angular/router';
import { FormComponent } from './form.component';
@Injectable({
providedIn: 'root'
})
export class UnsavedChangesGuard implements CanDeactivate<FormComponent> {
canDeactivate(component: FormComponent): boolean {
if (component.hasUnsavedChanges()) {
return confirm('Are you sure you want to leave without saving?');
}
return true;
}
}
2. Intercepteurs HTTP
Les intercepteurs dans Angular permettent d’intercepter et de manipuler les requêtes et les
réponses HTTP avant qu'elles ne soient envoyées ou après qu'elles aient été reçues. Cela
permet de modifier, ajouter des headers, gérer les erreurs, ou ajouter des logs, entre autres.
● Injection d'un intercepteur dans le AppModule :
○ Si vous utilisez une classe pour l’intercepteur, vous devez ajouter
l'intercepteur au tableau HTTP_INTERCEPTORS.
○ Si vous utilisez un intercepteur fonctionnel, vous devez utiliser
withInterceptors.
Exemple d'un intercepteur de classe :
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from
'@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', 'Bearer token')
});
return next.handle(clonedRequest);
}
}
Définir un intercepteur dans le provider :
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';
@NgModule({
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]
})
export class AppModule {}
Exemple avec withInterceptors (fonctionnel) :
bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([authInterceptor])
),
],
});
Manipulation de la requête dans un intercepteur : Les objets HttpRequest et
HttpResponse sont immuables, donc pour les modifier, il faut utiliser la méthode clone()
pour créer une nouvelle instance avec les propriétés souhaitées.
const clonedRequest = req.clone({
headers: req.headers.set('Authorization', 'Bearer token')
});
return next.handle(clonedRequest);
3. HTTP en Angular
Angular fournit le service HttpClient pour gérer les requêtes HTTP. Le HttpClient
retourne un Observable, ce qui permet de gérer les réponses asynchrones. Il est souvent
utilisé pour interagir avec des APIs.
Méthodes HTTP :
● get : Pour effectuer une requête GET et récupérer des données.
● post : Pour envoyer des données.
● put, delete, etc. : Pour mettre à jour ou supprimer des ressources.
Exemple d'une requête GET avec HttpClient :
this.http.get('https://api.example.com/data').subscribe({
next: (response) => {
console.log(response);
},
error: (error) => {
console.error('Error:', error);
},
complete: () => {
console.log('Request complete');
}
});
Ajout de Headers HTTP avec HttpHeaders : Les headers HTTP peuvent être ajoutés
avec la classe HttpHeaders. Vous pouvez utiliser set() pour ajouter ou remplacer un
header, et append() pour ajouter un nouveau header sans écraser les anciens.
Exemple d'ajout de headers à une requête :
import { HttpHeaders } from '@angular/common/http';
const headers = new HttpHeaders().set('Authorization', 'Bearer my-token');
this.http.get('https://api.example.com/data', { headers })
.subscribe(response => console.log(response));
4. Conclusion
Ce résumé couvre les concepts clés des Guards, des Intercepteurs, et de la gestion des
requêtes HTTP dans Angular :
1. Guards : Utilisés pour sécuriser les routes en vérifiant des conditions avant
d'autoriser ou de bloquer l'accès à une route ou de quitter une page.
2. Intercepteurs : Permettent d'intercepter les requêtes HTTP pour y ajouter des
fonctionnalités comme des headers, des logs ou la gestion des erreurs.
3. HTTP en Angular : Utilisation du service HttpClient pour gérer les appels API
avec des méthodes comme get, post, et put, et ajout de headers avec
HttpHeaders.
Les operateurs de l’observable
1) pipeable
2) de creation
a) ceation
b) jointure
[from] 1
[from] 2
[from] 3
[from] complete
[of] [1, 2, 3]
[of] complete
applatissement / flattening operators
MergeMap:
Utilisez cet opérateur si vous n'êtes pas concerné par l'ordre des émissions et que
vous êtes simplement intéressé par toutes les valeurs provenant de plusieurs flux combinés
comme si elles étaient produites par un seul flux.
SwitchMap:
L'opérateur switchMap effectue toutes les actions suivantes : Annule et se
désabonne automatiquement du second observable lorsque lepremier émet une nouvelle
valeur. Se désabonne automatiquement du second observable si nous nousdésinscrivons
du premier. S'assure que les deux observables se produisent en séquence, l'un après
l'autre.
switchMap n'a qu'un seul abonnement actif à la fois
ConcatMap:
La partie concat combine tous les flux internes observables renvoyés parla map et
émet séquentiellement toutes les valeurs de chaque fluxd'entrée. Utilisez cet opérateur si
l'ordre des émissions est important et que voussouhaitez d'abord voir les valeurs émises par
les flux qui passent parl'opérateur en premier.
ExhaustMap:
exhaustMap n'a qu'un seul abonnement actif à la fois à partir duquel les valeurs sont
transmises à un observateur. Lorsque l'observable d'ordresupérieur émet un nouveau flux
observable interne, si le flux actuel n'estpas terminé, ce nouvel observable interne est
abandonné.
CombineLatest:
Les opérateurs mettent en cache la dernière valeur pour chaque observabled'entrée
et seulement une fois que tous les observables d'entrée ont produit aumoins une valeur, il
émet les valeurs mises en cache combinées àl'observateur.
forkJoin:
forkJoin peut être appliqué à n'importe quel nombre d'Observables. Lorsque tous ces
Observables sont terminés, forkJoin émet la dernière valeur de chacun d'eux.
En d'autres termes, debounceTime émet uniquement la dernière valeur après un délai
donné, ce qui le rend utile pour éviter les émissions fréquentes en cas d'entrées rapides.
Résumé Élégant des Concepts et Opérateurs RxJS pour l’Examen
1. Introduction à RxJS
● RxJS (Reactive Extensions for JavaScript) :
Bibliothèque pour gérer les flux asynchrones et les événements en JavaScript en
utilisant des Observables.
● Concept de base :
Un Observable produit des données (flux), auxquelles les Observers s’abonnent
pour réagir.
2. Les opérateurs d’Observable
A. Opérateurs de création
Permettent de créer des Observables à partir de diverses sources.
of : Émet une série de valeurs définies.
import { of } from 'rxjs';
const observable = of(1, 2, 3);
observable.subscribe(value => console.log(value));
// Output : 1, 2, 3
1.
from : Convertit une structure itérable ou une promesse en Observable.
import { from } from 'rxjs';
const arrayObservable = from([10, 20, 30]);
arrayObservable.subscribe(value => console.log(value));
2.
interval : Émet des nombres incrémentaux à intervalles réguliers.
import { interval } from 'rxjs';
const intervalObservable = interval(1000); // Toutes les 1 seconde
intervalObservable.subscribe(value => console.log(value));
3.
throwError : Émet une erreur immédiatement.
import { throwError } from 'rxjs';
const errorObservable = throwError('Erreur simulée');
errorObservable.subscribe({
error: err => console.error(err),
});
4.
B. Opérateurs pipables
Transforment ou filtrent les données d’un Observable. Utilisés via .pipe().
1. Transformation :
map : Transforme chaque valeur émise.
import { of } from 'rxjs';
import { map } from 'rxjs/operators';
of(1, 2, 3).pipe(
map(value => value * 10)
).subscribe(result => console.log(result));
// Output : 10, 20, 30
○
2. Filtrage :
filter : Exclut les valeurs qui ne remplissent pas une condition.
import { filter } from 'rxjs/operators';
of(1, 2, 3, 4).pipe(
filter(value => value % 2 === 0)
).subscribe(result => console.log(result));
// Output : 2, 4
○
3. Opérations sur le temps :
debounceTime : Ignore les valeurs émisent trop rapidement.
import { debounceTime } from 'rxjs/operators';
of('A', 'B', 'C').pipe(
debounceTime(1000)
).subscribe(result => console.log(result));
C. Opérateurs d’aplatissement
Permettent de combiner ou transformer des Observables imbriqués.
1. mergeMap :
Abonne l’Observable principal à tous les Observables internes simultanément.
import { of } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
of('A', 'B').pipe(
mergeMap(letter => of(`${letter}1`, `${letter}2`))
).subscribe(console.log);
// Output : A1, A2, B1, B2
○
2. switchMap :
Annule les abonnements aux anciens Observables pour ne garder que le plus récent.
import { switchMap } from 'rxjs/operators';
of('X').pipe(
switchMap(letter => of(`${letter}1`, `${letter}2`))
).subscribe(console.log);
// Output : X1, X2
○
3. concatMap :
Exécute les Observables internes séquentiellement.
import { concatMap } from 'rxjs/operators';
of('A', 'B').pipe(
concatMap(letter => of(`${letter}1`, `${letter}2`))
).subscribe(console.log);
// Output : A1, A2, B1, B2
3. Gestion des erreurs dans RxJS
1. catchError : Intercepte les erreurs dans un flux Observable.
Émettre une valeur par défaut :
catchError(() => of('Valeur par défaut'));
Compléter le flux proprement avec EMPTY :
catchError(() => EMPTY);
○
Remplacer par un autre flux :
catchError(() => this.myService.getFakeData());
○
2. Combiner avec retry ou retryWhen :
retry : Réessaie automatiquement un Observable après une erreur.
retry(3); // Réessaye jusqu'à 3 fois
retryWhen : Ajoute un délai avant de réessayer.
retryWhen(errors => errors.pipe(delay(2000)));
4. Différences entre les opérateurs d’aplatissement
Opérateu Caractéristique principale Exemple d’utilisation
r
mergeMa Parallélise les Observables internes Téléchargements multiples en
p parallèle
concatM Exécute les Observables internes Traitement de tâches ordonnées
ap séquentiellement
switchM Annule les anciens Observables Requête API où seule la dernière
ap est pertinente
5. Exemple complet combinant les concepts
import { of } from 'rxjs';
import { map, filter, catchError, switchMap } from 'rxjs/operators';
of(1, 2, 3, 4).pipe(
filter(value => value % 2 === 0), // Garde uniquement les pairs
map(value => value * 10), // Multiplie par 10
switchMap(value => of(value + 1)), // Transforme en un autre flux
catchError(() => of('Erreur gérée')) // Gère les erreurs
).subscribe(result => console.log(result));
share est particulièrement utile si vous avez besoin d'empêcher desappels d'API
répétés ou des opérations coûteuses exécutées parObservables
share Vous voulez éviter des exécutions redondantes et partager une source
Observable sans stocker de valeur.
Une fois que le nombre d'abonnés atteint 0, share se désabonnera del'Observable source et
réinitialisera son Observable interne (le Subject). L'abonné (en retard) suivant déclenchera
un nouvel abonnementàl'Observable source, ou en d'autres termes, une nouvelle exécution
del'Observable source.
L'opérateur share est particulièrement utile lorsqu'une source Observable est coûteuse
(comme un appel d'API ou une opération complexe), et qu'on veut éviter de la réexécuter
pour chaque abonné.
Opérateur de Multicasting shareReplay
comme le ferait un ReplaySubject. Nous voulons donc qu’il partage à la fois la source
Observableetrejoue les dernières émissions pour les abonnés retardataires. Donc, il ne
conserve pas le nombre d'abonnés par défaut, mais vouspouvez utiliser l'option refCount
avec une valeur true pour activer cecomportement. Contrairement à share, shareReplay
expose un ReplaySubject auxabonnés. ReplaySubject().
Par défaut, shareReplay ne garde pas la trace du nombre d'abonnés, ce qui signifie qu'il
ne peut pas se désabonner automatiquement de la source Observable lorsque tous les
abonnés se désabonnent. Cependant, vous pouvez activer cette fonctionnalité en utilisant
l'option refCount.
Pour éviter les problèmes de fuite de mémoire tout en utilisant shareReplay, vous pouvez
spécifier à la fois bufferSize et refCount comme suit :
typescript
Copy code
shareReplay({ bufferSize: 1, refCount: true })
Important : Lorsque refCount atteint 0, shareReplay ne réinitialise jamais son
ReplaySubject. Il se désabonne simplement de la source Observable.
Voici un résumé complet et détaillé des concepts abordés dans cette conversation, adapté
pour un examen de niveau étudiant A++ :
1. Les Types de Subject en RxJS
BehaviourSubject
● Définition : Le BehaviourSubject est un type de Subject qui stocke la dernière
valeur émise.
● Caractéristiques :
○ Vous pouvez obtenir la dernière valeur émise de deux manières :
■ En accédant à la propriété value.
■ En vous abonnant au BehaviourSubject (même si l'abonnement
est effectué après que la valeur ait été émise).
Exemple :
const subject = new BehaviorSubject(0);
subject.next(1);
console.log(subject.value); // Affiche 1
subject.subscribe(value => console.log(value)); // Affiche 1
ReplaySubject
● Définition : Le ReplaySubject est un autre type de Subject, mais il enregistre et
"rejoue" plusieurs valeurs à de nouveaux abonnés.
● Caractéristiques :
○ Il conserve un historique des valeurs émises, et peut être configuré pour
conserver un certain nombre de valeurs.
○ Vous pouvez spécifier combien de valeurs anciennes vous voulez stocker et
pendant combien de temps.
Exemple :
const replaySubject = new ReplaySubject(2);
replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);
replaySubject.subscribe(value => console.log(value)); // Affiche 2, 3
AsyncSubject
● Définition : Le AsyncSubject émet la dernière valeur d'un observable à ses
abonnés, mais seulement lorsque l'exécution de l'observable est terminée.
● Caractéristiques : Il ne transmet pas de valeurs intermédiaires.
Exemple :
const asyncSubject = new AsyncSubject();
asyncSubject.next(1);
asyncSubject.next(2);
asyncSubject.complete(); // Nécessaire pour que la dernière valeur soit émise.
asyncSubject.subscribe(value => console.log(value)); // Affiche 2
2. Opérateurs de Multicasting
Opérateur share
● Définition : share permet de multicaster un observable, c’est-à-dire de partager les
valeurs émises à plusieurs abonnés sans réexécuter l'observable source pour
chaque abonné.
● Utilité : Permet d'éviter des appels redondants à une API ou des calculs coûteux, en
utilisant un abonnement interne à un Subject.
Exemple :
const sharedSource$ = interval(1000).pipe(
tap((ind) => console.log('Processing: ', ind)),
map(() => Math.round(Math.random() * 100)),
take(2),
share()
);
sharedSource$.subscribe(x => console.log('Subscription 1:', x));
sharedSource$.subscribe(x => console.log('Subscription 2:', x));
Opérateur shareReplay
● Définition : shareReplay combine le comportement de share et
ReplaySubject. Il permet à plusieurs abonnés de partager un même observable
tout en conservant un historique des dernières émissions (comme un
ReplaySubject).
● Caractéristiques :
○ Ne garde pas la trace des abonnés par défaut.
○ Permet de stocker plusieurs anciennes valeurs et de les "rejouer" pour de
nouveaux abonnés.
Exemple avec bufferSize et refCount :
const sharedSource$ = interval(1000).pipe(
tap((ind) => console.log('Processing: ', ind)),
map(() => Math.round(Math.random() * 100)),
take(2),
shareReplay({ bufferSize: 2, refCount: true })
);
sharedSource$.subscribe(x => console.log('Subscription 1:', x));
setTimeout(() => sharedSource$.subscribe(x => console.log('Subscription 2:', x)), 4500);
3. Gestion des Abonnements et Désabonnement
Problème de gestion de mémoire avec subscribe
● Problème : Les abonnements à un observable restent actifs même après la
disparition de la variable qui l'a généré, ce qui peut provoquer une saturation de
mémoire.
Méthode complete
● Définition : Lorsque l’observable appelle la méthode complete, tous les
abonnements sont automatiquement désabonnés.
● Utilisation : Cette méthode est pratique si vous savez que l'observable se termine
(comme dans les appels HTTP ou ActivatedRoute).
Solution avec async pipe
● Définition : async pipe dans les templates Angular permet de gérer
automatiquement l'abonnement et le désabonnement d'un observable. Il est
recommandé de l'utiliser pour éviter des fuites de mémoire.
Exemple :
export class TestUnsbscribeComponent {
todos$: Observable<Todo[]>;
constructor(private todoService: TodoService) {
this.todos$ = this.todoService.getTodos();
}
}
<div *ngIf="todos$ | async as todos">
<ol>
<li *ngFor="let todo of todos">
<pre>{{ todo | json }}</pre>
</li>
</ol>
</div>
Désabonnement manuel avec Subscription
● Définition : Si l'async pipe ne peut pas être utilisé, vous pouvez gérer les
abonnements manuellement via l'objet Subscription.
● Méthode : Vous pouvez ajouter des abonnements à un objet Subscription et
vous désabonner de tous les abonnements en une seule fois.
Exemple :
export class TestUnsbscribeComponent implements OnDestroy {
subscription: Subscription = new Subscription();
constructor(private todoService: TodoService) {
this.subscription.add(this.todoService.getTodos().subscribe());
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Désabonnement avec takeUntil et Signaux
● takeUntil : Utilisez cet opérateur pour désabonner un observable lorsque certaines
conditions sont remplies (par exemple, un signal ou un événement).
Exemple avec un signal :
export class TestUnsbscribeComponent implements OnDestroy {
signal$ = new Subject();
constructor(private testService: TestService) {
this.testService.click$.pipe(takeUntil(this.signal$)).subscribe();
}
ngOnDestroy() {
this.signal$.next();
this.signal$.complete();
}
}
4. Interopérabilité entre les Signaux et RxJS
● Objectif : Les signaux ne visent pas à remplacer RxJS, mais à limiter son utilisation
pour les flux synchrones, tandis que RxJS est mieux adapté aux flux asynchrones.
● Interopérabilité : Vous pouvez convertir des signaux en observables et vice versa
grâce aux fonctions toObservable et toSignal.
Exemple avec toSignal :
import { toSignal } from '@angular/core/rxjs-interop';
const cvs = toSignal(this.todoService.getTodos(), { initialValue: [] });
Conclusion
● RxJS et Signaux peuvent être utilisés ensemble de manière complémentaire, avec
des fonctions comme toSignal et toObservable facilitant leur interopérabilité.
● Gestion des abonnements : Utilisez des outils comme l'async pipe,
Subscription, et les opérateurs takeUntil ou takeUntilDestroyed pour
gérer proprement les abonnements et éviter les fuites de mémoire.
Lorsqu'un Router Resolver est utilisé pour résoudre des données avant d'afficher un
composant, vous devez accéder à ces données dans le composant une fois que la route a
été activée. Ces données peuvent être synchrones ou asynchrones (par exemple, sous
forme de Promise ou Observable), et Angular gère l'attente pour vous si nécessaire.
Si vous avez un CvResolver qui récupère des données pour un CV avant de charger la
vue DetailsCvComponent, vous pouvez accéder à ces données de cette manière dans le
composant :
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Cv } from './cv.model';
@Component({
selector: 'app-details-cv',
templateUrl: './details-cv.component.html'
})
export class DetailsCvComponent implements OnInit {
cv: Cv;
constructor(private route: ActivatedRoute) {}
ngOnInit(): void {
// Accéder aux données résolues via 'snapshot.data' et la clé définie dans le Resolver
this.cv = this.route.snapshot.data['cv'];
}
}
1. Resolvers
Les Resolvers dans Angular permettent de charger des données avant de naviguer vers une
route. Ils sont utiles pour éviter les erreurs liées à l'absence de données nécessaires pour
afficher un composant.
Points Clés :
● Implémentés via une classe ou une fonction.
● Utilisés pour précharger des données dynamiques avant de naviguer vers une vue.
Création d'un Resolver :
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Observable } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class CvResolver implements Resolve<any> {
resolve(): Observable<any> | Promise<any> | any {
return { id: 1, name: 'John Doe' }; // Exemple de données préchargées
}
}
Ajout du Resolver dans la Route :
const routes: Routes = [
{
path: 'cv/:id',
component: CvComponent,
resolve: { cv: CvResolver }
}
];
Accès aux Données Résolues dans le Composant :
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-cv',
template: '<p>{{ cvData | json }}</p>',
})
export class CvComponent implements OnInit {
cvData: any;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.cvData = this.route.snapshot.data['cv']; // Données résolues
}
}
2. Route Providers (Angular 14+)
La clé providers permet d’associer des services spécifiques à une route et ses enfants.
Utilisation :
const routes: Routes = [
{
path: 'cv',
component: CvComponent,
providers: [LoggerService] // Fournit un service à cette route
}
];
Effet :
● Crée un nouvel injecteur pour cette route.
● Permet une isolation des dépendances pour des cas spécifiques.
3. Router Events
Angular déclenche plusieurs événements lors de la navigation, permettant d'ajouter des
comportements personnalisés.
Exemple : Écoute des Événements du Routeur
import { Component } from '@angular/core';
import { Router, NavigationStart, NavigationEnd } from '@angular/router';
@Component({
selector: 'app-root',
template: '<router-outlet></router-outlet>',
})
export class AppComponent {
constructor(private router: Router) {
this.router.events.subscribe(event => {
if (event instanceof NavigationStart) {
console.log('Navigation Start:', event);
} else if (event instanceof NavigationEnd) {
console.log('Navigation End:', event);
}
});
}
}
Principaux Événements :
● NavigationStart : Début de la navigation.
● NavigationEnd : Fin de la navigation.
● ResolveStart et ResolveEnd : Début et fin de la résolution des données.
4. NavigationExtras
Permet de transmettre des données dynamiques lors de la navigation avec le service
Router.
Envoi de Données avec navigate :
this.router.navigate(['cv'], { state: { id: 5 } });
Récupération des Données dans le Composant :
import { Router } from '@angular/router';
constructor(private router: Router) {}
ngOnInit() {
const routeState = this.router.getCurrentNavigation()?.extras.state;
if (routeState) {
console.log('ID Transmis :', routeState['id']);
}
}
5. Configuration Avancée du RouterModule
Angular permet de personnaliser le comportement du routeur à travers la méthode
RouterModule.forRoot().
Exemple : Configuration Avancée
RouterModule.forRoot(routes, {
scrollPositionRestoration: 'enabled', // Restaurer la position de défilement
paramsInheritanceStrategy: 'always', // Hériter des paramètres de la route parent
malformedUriErrorHandler: (error, urlSerializer, url) =>
urlSerializer.parse('/page-notfound') // Gestion des URI malformées
});
6. Récupération Dynamique des Paramètres
Lorsque les paramètres de la route changent dynamiquement, Angular ne recrée pas le
composant. Il faut donc écouter la propriété params de ActivatedRoute.
Exemple : Écoute Dynamique des Paramètres
activatedRoute.params.subscribe(params => {
this.paramValue = params['id'];
});
7. Ajout de Données Statiques à une Route
Les données statiques peuvent être ajoutées via la propriété data dans la définition de la
route.
Exemple :
const routes: Routes = [
{
path: 'cv',
component: CvComponent,
data: { blackListName: ['John', 'Jane'] }
}
];
Accès aux Données Statiques :
this.blackList = this.route.snapshot.data['blackListName'];
Résumé des Concepts et Codes :
Fonctionnalité Utilisation Code Exemple
Resolvers Charger des données avant la Voir Resolvers ci-dessus
navigation
Route Providers Associer un service spécifique à Voir Route Providers
une route ci-dessus
Router Events Écouter des événements de Voir Router Events
navigation pour exécuter des ci-dessus
comportements personnalisés
NavigationExtras Transmettre des données Voir NavigationExtras
dynamiques entre routes ci-dessus
Paramètres Écouter les paramètres d'une route Voir Récupération
Dynamiques pour des changements dynamiques Dynamique des Paramètres
ci-dessus
Données Ajouter des données statiques à Voir Ajout de Données
Statiques une route via la propriété data Statiques à une Route
Configuration Personnaliser le comportement Voir Configuration Avancée
Avancée global du routeur du RouterModule
Résumé Complet et Organisé : Formulaires Réactifs (Reactive Forms)
dans Angular
1. Introduction aux Formulaires Réactifs (Reactive Forms)
Les Reactive Forms dans Angular permettent de gérer des formulaires complexes de
manière déclarative et dynamique. Contrairement aux Template-driven forms, les Reactive
Forms sont plus adaptés aux formulaires dynamiques où les champs peuvent être ajoutés
ou supprimés en fonction de l’interaction de l'utilisateur. Ils offrent un meilleur contrôle sur la
validation et la gestion des erreurs.
Principaux Composants :
● FormControl : Représente un champ individuel de formulaire.
● FormGroup : Regroupe plusieurs contrôles dans un ensemble logique.
● FormArray : Permet de gérer dynamiquement un tableau de contrôles de formulaire.
2. Manipuler les valeurs du formulaire
La gestion des valeurs dans un formulaire réactif peut être effectuée via des méthodes
comme setValue() et patchValue().
Syntaxe et Exemples :
setValue() : Remplace toutes les valeurs des contrôles d'un FormGroup ou FormArray.
this.form.setValue({name: 'John', age: 30});
●
patchValue() : Permet de mettre à jour partiellement les valeurs des contrôles.
this.form.patchValue({name: 'Aymen'}, { emitEvent: false });
Options des méthodes :
● onlySelf : Permet de ne pas déclencher la validation du groupe parent.
● emitEvent : Détermine si des événements doivent être émis lors du changement
de valeur.
3. Suivi des Modifications : Observables
Les Reactive Forms offrent des observables pour suivre les changements de valeur et de
statut de validation :
● valueChanges : Émis lorsque la valeur d'un FormControl, FormGroup, ou
FormArray change.
● statusChanges : Émis lorsque le statut de validation d'un contrôle change (valide,
invalide, en attente).
Exemple d'abonnement aux changements de valeur :
this.form.valueChanges.subscribe(value => {
console.log('Nouvelle valeur : ', value);
});
4. Création de Validateurs
Les validateurs sont des fonctions qui vérifient si une valeur est valide ou non.
Validateur Synchronous :
export function createPasswordStrengthValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (!value) return null;
const hasUpperCase = /[A-Z]+/.test(value);
const hasLowerCase = /[a-z]+/.test(value);
const hasNumeric = /[0-9]+/.test(value);
return !(hasUpperCase && hasLowerCase && hasNumeric) ? {passwordStrength: true} :
null;
};
}
Validateur Asynchrone :
Lorsqu'une validation nécessite une action asynchrone (comme vérifier l'existence d'un
utilisateur via une API), vous créez un validateur asynchrone.
export function userExistsValidator(authService: AuthService): AsyncValidatorFn {
return (control: AbstractControl) => {
return authService.findUserByEmail(control.value);
};
}
5. Utilisation de FormArray
Les FormArray sont utilisés pour gérer des collections dynamiques de contrôles de
formulaire. Contrairement aux FormGroup, qui nécessitent de connaître les contrôles à
l'avance, FormArray permet de gérer un nombre indéterminé de contrôles.
Déclaration d’un FormArray :
this.form = this.formBuilder.group({
skills: this.formBuilder.array([]),
});
Accéder au FormArray :
get skills() {
return this.form.get('skills') as FormArray;
}
Ajouter dynamiquement des éléments :
addSkill(): void {
const skillFormGroup = this.formBuilder.group({
name: [null, Validators.required]
});
this.skills.push(skillFormGroup);
}
Boucle dans le Template pour afficher les éléments :
<div formArrayName="skills">
<button (click)="addSkill()">Add Skill</button>
<div *ngFor="let skillControl of skills.controls; let i = index" [formGroupName]="i">
<input formControlName="name" class="form-control">
</div>
</div>
Méthodes communes de FormArray :
● push() : Ajoute un contrôle à la fin du tableau.
● removeAt() : Supprime un contrôle à une position donnée.
● at(index) : Accède à un contrôle à une position spécifique.
● getRawValue() : Obtient les valeurs de tous les contrôles sans se soucier de leur
statut de validation.
Les deux méthodes que vous mentionnez servent à créer un formulaire réactif, mais elles
utilisent des approches légèrement différentes pour initialiser le formulaire. Voici les
explications détaillées de chaque méthode :
1. Utilisation de new FormGroup :
this.form = new FormGroup({
credentials: new FormGroup({
email: new FormControl(null, [Validators.required, Validators.email]),
password: new FormControl(null, [Validators.required, createPasswordStrengthValidator]),
}),
username: new FormControl(null, [Validators.required]),
age: new FormControl(null, [Validators.required, Validators.pattern('[0-1]?\d{1,2}')]),
skills: new FormArray([]),
});
Explication :
● new FormGroup : Cette approche crée le formulaire en utilisant directement les
constructeurs FormGroup et FormControl.
● Chaque champ est défini explicitement avec un FormControl et un tableau de
validateurs.
● FormArray([]) : Ici, un tableau vide est passé pour initialiser un FormArray. Le
formulaire n’a pas encore d'éléments dans le tableau skills.
● Cette méthode est plus verbeuse et nécessite d'utiliser des constructeurs pour
chaque contrôleur de formulaire.
2. Utilisation de FormBuilder :
this.form = this.formBuilder.group({
credentials: this.formBuilder.group({
email: [null, {validators: [Validators.required, Validators.email]}],
password: [null, {validators: [Validators.required, createPasswordStrengthValidator()]}],
}),
username: [null, {validators: [Validators.required]}],
age: [null, {validators: [Validators.required, Validators.pattern('[0-1]?\d{1,2}')]}],
skills: this.formBuilder.array([]),
});
Explication :
● this.formBuilder.group() : Cette approche utilise le service FormBuilder
pour simplifier la création du formulaire. Il est plus concis et plus lisible.
● Le formulaire est défini à l’aide de tableaux dans lesquels les valeurs initiales et les
validateurs sont fournis sous forme d’un tableau d'options.
● Les champs sont plus faciles à lire et à maintenir, car l'initialisation est plus
compacte.
● this.formBuilder.array([]) : Un FormArray est aussi initialisé comme dans
la méthode précédente, mais en utilisant le FormBuilder pour plus de simplicité.
6. Avantages des Reactive Forms
1. Séparation claire de la logique : La logique de validation et de gestion des erreurs
est centralisée dans la classe du composant, ce qui facilite les tests unitaires et le
débogage.
2. Facilité de gestion dynamique : Les FormArray permettent de gérer des
formulaires dynamiques où les champs peuvent être ajoutés ou supprimés en
fonction des interactions de l'utilisateur.
3. Création simple de validateurs personnalisés : Vous pouvez facilement créer des
validateurs personnalisés en définissant une fonction qui renvoie un objet
ValidationErrors.
4. Contrôle total sur la gestion des valeurs, des erreurs et des événements associés à
chaque contrôle.
Conclusion
Les Reactive Forms dans Angular permettent de créer des formulaires plus robustes,
flexibles et faciles à tester. Ils sont particulièrement adaptés pour les applications ayant des
formulaires dynamiques ou complexes. Grâce à des fonctionnalités telles que les
FormArray, les validateurs synchrones et asynchrones, ainsi que l'observation des
changements, vous avez un contrôle total sur l'état et la gestion des données du formulaire.
Voici un résumé complet et organisé uniquement basé sur les informations de vos prompts,
sans ajout supplémentaire :
Résumé Complet sur l'Optimisation des Performances et la Gestion des
Dépendances dans Angular
1. Optimisation du Bundle Size
1.1 Tree-Shaking
Le tree-shaking permet d'éliminer le code mort (c'est-à-dire le code qui ne sert à rien).
Webpack détecte ce code et le marque comme non utilisé. Ensuite, des outils comme
UglifyJS peuvent l'éliminer tout en minimisant le code.
Exemple :
// Mauvais usage (importer toute la bibliothèque)
import moment from 'moment';
// Bon usage (importer uniquement ce qui est nécessaire)
import { format } from 'date-fns';
1.2 Importation Sélective de Bibliothèques
Évitez d'importer des bibliothèques entières si vous n'avez besoin que d'une fonction
spécifique. Cela aide à réduire la taille du bundle et permet un meilleur tree-shaking.
Exemple avec Lodash :
// Mauvais usage
import _ from 'lodash';
// Bon usage avec lodash-es
import { debounce } from 'lodash-es';
1.3 Utilisation de Bibliothèques Modernes et Compatibles avec le Tree-Shaking
Privilégiez des bibliothèques modernes comme date-fns et lodash-es qui sont conçues
pour être optimisées avec le tree-shaking.
Exemple avec date-fns :
// Importation de date-fns
import { format, parseISO } from 'date-fns';
2. Optimisation des Styles CSS
2.1 Eviter la Duplication des Styles CSS
L'importation répétée de fichiers CSS dans plusieurs composants peut entraîner une
duplication des styles, ce qui augmente la taille du bundle et peut affecter les
performances.
● Solution : N'importe que les styles spécifiques à chaque composant.
Exemple :
/* Mauvais : Importation globale du fichier CSS */
@import 'styles.css'; /* Ce fichier contient des styles globaux pour tous les composants */
/* Bon usage : Importer uniquement les styles nécessaires au composant */
@import './component-specific-styles.css';
2.2 Eviter les Importations Multiples du Même Fichier CSS
Assurez-vous de ne pas importer plusieurs fois le même fichier CSS dans différents
composants. Si vous avez des styles partagés, importez-les une seule fois dans le fichier
styles.css global de votre application.
Exemple d'importation globale :
/* styles.css global (importé une seule fois) */
@import './shared-styles.css';
3. Déploiement de l'Application Angular
3.1 Commande pour Compiler l'Application
Pour déployer une application Angular, utilisez la commande suivante pour construire le
projet en mode production :
ng build --prod
Cela génère un dossier dist contenant la version optimisée de votre application.
3.2 Tester Localement avec http-server
Pour tester localement, vous pouvez installer un serveur HTTP virtuel :
npm install http-server --g
Ensuite, lancez votre projet :
http-server dist/NomDeVotreProjet
3.3 Déploiement sur GitHub Pages
Angular CLI facilite le déploiement sur GitHub Pages avec la commande suivante :
ng add angular-cli-ghpages@next
Ajoutez cette configuration dans le fichier angular.json :
"deploy": {
"builder": "angular-cli-ghpages:deploy",
"options": {
"baseHref": "https://username.github.io/repoName/"
}
}
Ensuite, déployez l'application avec :
ng deploy --base-href=/the-repositoryname/
Accédez à la page déployée :
https://USERNAME.github.io/REPOSITORY_NAME
4. Mesure des Performances
4.1 Analyse de Bundle avec Source-Map-Explorer
Utilisez source-map-explorer pour analyser votre bundle et identifier les fichiers inutiles ou
trop volumineux :
npm install source-map-explorer --save-dev
ng build --source-map
source-map-explorer dist/**/*.js
En Angular, il est important qu’un composant, une directive ou un pipe soit déclaré dans un
seul module pour plusieurs raisons fondamentales :
1. Unicité de la déclaration
2. Eviter les erreurs de compilation ng:component already declared
3. Réutilisation via les modules partagés (SharedModule)
Si plusieurs modules ont besoin du même composant, directive ou pipe, la solution correcte
est de :
● Créer un module partagé (SharedModule).
4. Respect des bonnes pratiques et de l’architecture modulaire
Routing Vous pouvez créer les routes de vos modules de la même manière que pour le
routing de l’AppModule. Cependant, au lieu d’utiliser RouterModule.forRoot vous devez
utiliser la méthode forChild du RouterModule.
Différences entre Injecteur Parent et Injecteur Fils
1. Injecteur Parent (Root Injector)
○ L’injecteur parent est créé au démarrage de l’application et contient les
services déclarés dans le AppModule ou dans des modules importés
directement dans le AppModule.
○ Par défaut, tous les modules importés partagent les mêmes instances des
services, car ils utilisent le même injecteur.
2. Injecteur Fils (Child Injector)
○ Lorsque vous chargez un module de manière paresseuse (lazy loaded),
Angular crée un injecteur fils spécifique à ce module.
○ Cet injecteur fils ne partage pas les instances des services avec l’injecteur
parent. Cela signifie que si le module lazy loaded utilise un service déclaré
dans son propre module, une nouvelle instance de ce service sera créée,
indépendamment des instances déjà existantes dans l’application
Lorsqu’on utilise Lazy Loading en Angular, il arrive qu’on souhaite partager une instance
unique d’un service (comme le service de routage) entre tous les modules, y compris ceux
chargés de manière paresseuse. Cependant, avec le mécanisme de l’injecteur fils, chaque
module lazy loaded peut créer une nouvelle instance de ce service, ce qui peut poser
problème si on veut que ce service soit un singleton global (une seule instance partagée).
Le pattern forRoot / forChild est une solution à ce problème.
Solution : Le Pattern forRoot / forChild
1. forRoot
○ Ce pattern est utilisé pour fournir une instance unique d’un service au
niveau de l’injecteur parent (root injector).
○ On l’utilise dans le AppModule, et il configure le module de manière à ce qu’il
fournisse ses services au niveau global.
2. forChild
○ Ce pattern est utilisé pour importer un module dans un module lazy loaded
sans créer de nouvelle instance des services partagés.
○ Il permet d’utiliser le même service déjà fourni par forRoot dans l’injecteur
parent.
3. forChild est utilisé pour déclarer des routes secondaires dans un module
Angular.
4. loadChildren est utilisé pour charger des modules de manière paresseuse afin
d’améliorer les performances de l’application. UNE SEULE INSTANCE PARTAGÉE
5. Lorsque vous souhaitez créer un module chargé au démarrage, utilisez forChild.
Si vous voulez créer un module chargé uniquement à la demande (lazy loading),
utilisez loadChildren.
Le Guard canMatch permet de spécifier si le router doit matcher(et non activer) une route ou
non. AFTER THAT COMES canActivate canDeactivate ..
Résumé Complet : Lazy Loading et Préchargement dans Angular
1. Introduction au Lazy Loading
● Lazy Loading est une technique utilisée dans les applications Angular pour charger
les modules seulement lorsqu'ils sont nécessaires (par exemple, lorsqu'un utilisateur
accède à une route particulière).
● Cela permet d'optimiser la performance en réduisant la taille initiale du bundle de
l'application et en améliorant l'expérience utilisateur.
Problème avec le Lazy Loading:
● Lors du passage d'un module à un autre, un chargement du nouveau module se
produit à chaque fois, ce qui peut entraîner des latences.
● Si les modules sont volumineux ou que la connexion Internet est lente, cela peut
affecter l'expérience utilisateur.
2. Stratégies de Préchargement Le préchargement permet de charger certains modules
en arrière-plan, avant même qu'ils soient nécessaires.
● Méthodes de préchargement dans Angular:
1. NoPreloading (par défaut) : Aucun module n'est préchargé.
2. PreloadAllModules : Tous les modules lazy-loaded sont préchargés
après le chargement initial.
Exemple de configuration de PreloadAllModules :
import { PreloadAllModules } from "@angular/router";
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule],
})
export class AppRoutingModule {}
3. Créer une Stratégie de Préchargement Personnalisée Si vous avez de nombreux
modules et que vous ne souhaitez pas tous les précharger, vous pouvez définir une
stratégie personnalisée.
Exemple d'une stratégie personnalisée :
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// Si la route a la donnée preload définie sur true, le module sera préchargé.
if (route.data["preload"]) {
return load();
} else {
// Sinon, on retourne un Observable null, donc le module ne sera pas préchargé.
return of(null);
● La méthode preload() de la classe CustomPreloadingStrategy détermine si
un module doit être préchargé.
● La propriété route.data['preload'] permet de définir si un module sera
préchargé ou non.
4. Utilisation des Routes avec Préchargement Personnalisé Vous pouvez appliquer cette
stratégie dans votre configuration de routes en ajoutant une propriété data pour chaque
route.
const routes: Routes = [
path: 'auth',
loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule),
data: { preload: true } // Ce module sera préchargé
},
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m =>
m.DashboardModule),
data: { preload: false } // Ce module ne sera pas préchargé
];
● Ici, preload: true indique que le module sera préchargé, tandis que preload:
false signifie qu'il ne le sera pas.
5. Lazy Loading pour les Standalone Components Angular permet également de charger
les composants standalone de manière paresseuse. Cela est fait avec la clé
loadComponent dans la configuration de la route.
Exemple pour un Standalone Component :
const routes: Routes = [
path: 'track',
loadComponent: () => import('./track-by/track-by.component')
.then(c => c.TrackByComponent)
];
● Ce composant sera chargé seulement lorsqu'un utilisateur accède à la route track.
6. Les Garde-fous (Guards) : canLoad et canMatch Les guards sont des services qui
permettent de protéger l'accès à certaines routes ou modules.
1. canLoad :
○ Utilisé pour empêcher le chargement d'un module si une certaine condition
n'est pas remplie.
○ Cela est particulièrement utile pour les modules lazy-loaded, pour éviter
de les charger si l'utilisateur n'a pas les droits d'accès.
Exemple d'un canLoad guard :
@Injectable({
providedIn: 'root'
})
export class CanLoadAuthGuard implements CanLoad {
constructor(private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> {
return true; // Logique personnalisée pour valider si le module peut être chargé
2.
3. canMatch (Introduit dans Angular 14) :
○ Permet de spécifier si le routeur doit "matcher" (faire correspondre) une route
avant même de la charger.
○ C'est un guard plus flexible que canLoad, et il permet de continuer le
processus de recherche de la route.
Exemple de canMatch guard :
path: 'team/:id',
loadChildren: () => import('./team').then(mod => mod.TeamModule),
canMatch: [(route: Route, segments: UrlSegment[]) => {
const authService = inject(AuthService);
return authService.isAuthenticated();
}]
7. Stratégies de Préchargement et Goulots d'Étranglement Les stratégies de
préchargement peuvent devenir inefficaces si elles préchargent trop de modules à la fois,
surtout avec des applications volumineuses. Cela peut entraîner un goulot
d'étranglement lorsque trop de modules sont chargés en parallèle.
Exemple de la configuration de stratégie avec PreloadAllModules :
import { PreloadAllModules } from "@angular/router";
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule],
})
export class AppRoutingModule {}
8. Gérer le Goulot d'Étranglement avec une Stratégie Personnalisée Afin d'éviter ce
goulot d'étranglement, on peut adopter une stratégie de préchargement personnalisée,
comme démontré précédemment, pour charger les modules plus efficacement et en fonction
des besoins.
Conclusion
● Lazy loading et préchargement sont essentiels pour optimiser les performances
des applications Angular.
● Le préchargement sélectif via une stratégie personnalisée permet de mieux gérer
les ressources et de réduire les latences.
● L'utilisation de guards (canLoad, canMatch) permet de sécuriser les routes et
modules, tout en contrôlant leur accès.
● Le lazy loading des composants standalone est une fonctionnalité clé pour charger
des composants plus légers de manière optimale.
Grandes Lignes à Retenir :
1. Utilisez lazy loading pour charger les modules seulement lorsque nécessaire.
2. Configurez les stratégies de préchargement pour contrôler quel module doit être
préchargé.
3. Implémentez une stratégie personnalisée de préchargement pour mieux gérer les
ressources.
4. Utilisez les guards pour sécuriser l'accès aux modules et composants.
Avec ces stratégies, vous pouvez améliorer l'expérience utilisateur tout en optimisant les
performances de votre application Angular.
● Smart Component (Composant Intelligent) :
○ Un smart component est responsable de la gestion de la logique métier et
de la gestion des données.
○ Il interagit avec des services, effectue des appels API, et manipule les
données avant de les passer à des composants enfants via des propriétés
@Input.
○ Il gère les événements émis par ses composants enfants avec des propriétés
@Output et prend des décisions basées sur ces événements.
○ Le composant parent dans l'exemple est un smart component car il fournit
les données à son enfant et gère l'action après qu'un élément ait été
sélectionné.
● Dumb Component (Composant Bête ou de Présentation) :
○ Un dumb component est principalement responsable de l'affichage des
données reçues, sans logique métier.
○ Il ne connaît pas la source des données et ne prend aucune décision
concernant ces données. Il se contente d'afficher et de transmettre des
événements lorsque des actions sont effectuées.
○ Le composant de présentation dans l'exemple est un dumb component car il
se contente de recevoir les données et d'afficher la liste. Lorsqu'un élément
est sélectionné, il émet un événement, mais il ne gère pas la logique liée à
cet événement.
Résumé : Architecture de la couche Vue dans Angular
L'architecture de la couche Vue d'une application Angular repose sur la séparation des
responsabilités entre deux types principaux de composants : les composants intelligents
(Smart Components) et les composants de présentation (Presentational Components).
Cela permet de mieux structurer l'application, de favoriser la réutilisation des composants et
d'assurer une gestion claire de la logique métier et de la présentation des données.
1. Composants de présentation (Presentational Components)
● Responsabilité principale : Affichage des données sans logique métier.
● Caractéristiques :
○ Ils sont totalement indépendants de la source des données.
○ Ils n'ont aucune logique fonctionnelle, se contentant de recevoir des données
via @Input et d'émettre des événements via @Output.
○ Ils sont réutilisables dans plusieurs vues.
○ Exemples d'utilisation : boutons, listes, cartes, etc.
● Objectif : Séparer la logique de présentation du traitement des données.
2. Composants intelligents (Smart Components)
● Responsabilité principale : Gérer la logique métier et interagir avec les services.
● Caractéristiques :
○ Ils récupèrent les données via des services, manipulent les données, et les
transmettent aux composants de présentation.
○ Ils gèrent la logique de l'application et la gestion des événements envoyés
par les composants de présentation.
○ Ils peuvent avoir des interactions plus complexes avec les autres couches de
l'application (services, gestion d'état, API).
● Objectif : Gérer la logique et les interactions avec les services, tout en agissant
comme un conteneur pour les composants de présentation.
Exemple de code
1. Composant intelligent (Smart Component) :
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { ItemService } from './item.service';
@Component({
selector: 'app-root',
template: `
<h1>Liste d'articles</h1>
<app-item-list [items]="items" (itemSelected)="onItemSelected($event)"></app-item-list>
`,
})
export class AppComponent implements OnInit {
items = [];
constructor(private itemService: ItemService) {}
ngOnInit() {
this.itemService.getItems().subscribe(data => {
this.items = data;
});
onItemSelected(item: any) {
console.log('Article sélectionné:', item);
}
2. Composant de présentation (Presentational Component) :
// item-list.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-item-list',
template: `
<ul>
<li *ngFor="let item of items" (click)="onItemClick(item)">
{{ item.name }}
</li>
</ul>
`,
})
export class ItemListComponent {
@Input() items: any[] = []; // Données reçues du composant parent
@Output() itemSelected = new EventEmitter<any>(); // Événement pour informer le parent
onItemClick(item: any) {
this.itemSelected.emit(item); // Informe le parent qu'un item a été sélectionné
3. Service pour récupérer les données :
// item.service.ts
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root',
})
export class ItemService {
getItems(): Observable<any[]> {
// Simuler une requête API pour récupérer les articles
return of([
{ id: 1, name: 'Article 1' },
{ id: 2, name: 'Article 2' },
{ id: 3, name: 'Article 3' },
]);
Explication de l'exemple :
● Composant intelligent (app.component.ts) :
○ Ce composant est responsable de récupérer les données via un service
(ItemService) et de les transmettre à un composant de présentation
(app-item-list) via la propriété @Input.
○ Il gère également les événements envoyés par le composant de présentation
via @Output.
● Composant de présentation (item-list.component.ts) :
○ Ce composant reçoit les données via @Input et les affiche dans une liste.
Lorsqu'un utilisateur sélectionne un article, il émet un événement avec l'article
sélectionné via @Output.
● Service (item.service.ts) :
○ Le service est responsable de la récupération des données (dans ce cas, des
articles), simulant une requête API.
En résumé, cette architecture permet de séparer les préoccupations : les composants
intelligents gèrent la logique métier et les services, tandis que les composants de
présentation se concentrent sur l'affichage et l'interaction avec l'utilisateur.
Zone.js ne joue pas un rôle pour les opérations synchrones : Contrairement aux
opérations asynchrones, Angular ne dépend pas de Zone.js pour détecter les changements
synchrones.
Ceci est donc délégué à zone.js qui suit les APIs commeEventEmitter, DOM event listeners,
XMLHttpRequest, l’API fs dansNode.js et plus encore.
Donc si zoneJs détecte un des déclencheur du Change Detection, elle notifie Angular qui lui
va déclencher le processus de ChangeDetection. Angular utilise sa propre zone appelée
NgZone.
C’est une seule zone et le Change Detection est uniquement déclenché une opération
Asynchrone est déclenchée dans cette zone.
La méthode tick() dans Angular est utilisée pour déclencher explicitement un cycle de
détection des changements dans l'application. Elle fait partie de l'objet ApplicationRef,
qui est responsable de gérer le cycle de vie de l'application, y compris l'exécution de la
détection des changements sur l'ensemble des composants.
Angular exécute automatiquement la détection des changements après chaque cycle de
traitement, qu'il soit synchrone ou asynchrone. NgZone est impliqué dans l'ensemble du
processus pour garantir que la détection des changements fonctionne sans accroc. Dans les
opérations synchrones, cela signifie que NgZone suit toujours l'exécution des tâches et
déclenche la mise à jour de la vue si nécessaire, mais la détection des changements se
produit normalement sans avoir besoin d'une intervention explicite.
Il existe deux stratégies de Change Detection :
1. La stratégie par défaut
2. La stratégie OnPush
Dans la stratégie par défaut, le mécanisme est le suivant :
1. NgZone détecte une possibilité de modification.
2. Angular est notifié afin de déclencher le change detection.
3. La détection de changement vérifie chaque composant dansl'arborescence des
composants de haut en bas pour voir si le modèlecorrespondant a changé. Ceci est appelé
dirty checking.
4. S'il y a une nouvelle valeur, il mettra à jour la partie correspondante(DOM)
La stratégie OnPush:
Les actions suivantes ne déclenchent pas le Change Detection dans ce contexte :
setTimeout setInterval Promise.resolve().then(), Promise.reject().then()
this.http.get('...').subscribe() (En générale, n’importe quelle inscription àun Observable RxJs)
Même si ces actions n'entraînent pas directement la détection des changements sous la
stratégie OnPush, Angular utilise Zone.js et NgZone pour gérer la détection des
changements
Sans la stratégie OnPush, Angular utilise la stratégie de détection des changements par
défaut, qui examine tous les composants à chaque cycle de détection des changements
pour voir s'il y a des mises à jour à effectuer. Cela signifie que même si les données de votre
composant n'ont pas changé, Angular vérifiera tous les composants et leurs @Input() à
chaque cycle. Cela peut entraîner des coûts en termes de performance, surtout si
l'application devient complexe avec de nombreux composants.
En utilisant la stratégie OnPush, le détecteur de changement n'estdéclenché que si une
nouvelle référence est transmise en tant quevaleur @Input().
NB:
● En JavaScript, les objets et les tableaux sont passés par référence, ce qui signifie
que si vous modifiez un objet ou un tableau, vous ne changez pas sa référence
(l'adresse mémoire à laquelle il pointe), mais seulement ses propriétés ou
éléments.
● Dans Angular avec la stratégie OnPush, si vous modifiez une propriété d'un objet ou
un élément d'un tableau sans changer la référence de l'objet ou du tableau, Angular
ne saura pas qu'il y a eu un changement, et donc, la détection des changements
ne sera pas déclenchée. Cela est dû au fait que OnPush repose sur l'idée que si la
référence ne change pas, il n'y a pas de mise à jour de l'état, donc aucune mise à
jour de la vue n'est nécessaire.
// Si on modifie les propriétés d'un objet sans changer la référence, Angular ne détectera pas
le changement
this.myObj.property = 'new value'; // Aucune détection de changement sous OnPush
Immutabilité fait référence à la pratique consistant à ne jamais modifier directement un
objet ou un tableau, mais à créer une nouvelle instance chaque fois que vous souhaitez
changer son contenu. Cela s'intègre parfaitement avec la stratégie OnPush, car une
nouvelle référence de l'objet ou du tableau garantit que la détection des changements sera
déclenchée.
Immutable.js facilite l’utilisation de l’Immutabilité. Il fournit
des structures de données immuables persistantes pour les
objets(Map) et les listes (List)
● Out of Bound Change Detection:
Ce problème se produit lorsqu’une action qui modifie uniquement l'état local d’un composant
déclenche des changes détection dans les parties qui n’ont aucun rapport avec cette action.
Solution : Utiliser OnPush et considérer du refactoring de votre composant.
● Recalculation of referentially transparent expressions
Cette problématique est soulevée lorsque vous avez une expressiondansvotre Template qui
doit être recalculé même lorsque ses paramètres nechangent pas.
Solution: utiliser des pipes, memo-decorator
Résumé Complet sur la Détection des Changements dans Angular
1. Définition et Importance de la Détection des Changements (Change Detection)
La détection des changements est un mécanisme clé dans Angular, permettant à
l'application de suivre et de refléter les modifications de son état dans l'interface
utilisateur de manière synchrone et automatique. Grâce à ce mécanisme, les
modifications internes de l'application se propagent vers la vue (le DOM).
● Objectif principal : garantir que l'interface utilisateur reste en phase avec l'état
interne de l'application, en mettant à jour le DOM chaque fois qu'un
changement se produit.
● Mécanisme : Angular évalue les expressions dans les composants et met à
jour les parties du DOM lorsque des valeurs ont changé.
Exemple :
@Component({
selector: 'app-demo',
template: `<div>{{ message }}</div>`
})
export class DemoComponent {
message: string = 'Bonjour';
2. Les Différentes Méthodes de Détection des Changements
● Détection manuelle (explicit) : Angular appelle directement la détection des
changements, notamment lors de l'initialisation des composants avec
ApplicationRef.tick().
● Détection automatique (asynchrone) : Angular utilise Zone.js, une bibliothèque
qui intercepte les tâches asynchrones (comme les setTimeout, les requêtes
HTTP, etc.) et déclenche automatiquement la détection des changements.
Zone.js : Zone.js permet à Angular de suivre les opérations asynchrones et de
détecter les modifications qui en résultent. Elle suit les micro-tâches et les
macro-tâches exécutées dans l'application.
Exemple d’utilisation de Zone.js :
ngZone.run(() => {
this.someAsyncOperation().then(() => {
// Zone.js déclenche la détection des changements ici
});
});
3. Les Scénarios Déclencheurs de la Détection des Changements
Voici les scénarios dans lesquels la détection des changements est généralement
déclenchée :
● Initialisation des composants : Lors du démarrage de l'application, Angular
déclenche la détection des changements.
● Événements DOM : Des événements sur le DOM comme les clics ou les
entrées utilisateur.
● Requêtes HTTP : Après qu’une réponse HTTP est reçue.
● Tâches asynchrones : Comme setTimeout, setInterval, ou des promesses
avec then().
● Opérations de WebSocket, canvas, etc. : Les opérations asynchrones qui
affectent l'état de l'application.
4. Stratégies de Change Detection : Default vs OnPush
● Stratégie Default : Chaque changement de données entraîne une vérification
du composant et de tous ses composants enfants.
● Stratégie OnPush : La détection des changements ne se fait que lorsque :
○ La référence d'un @Input() du composant change.
○ Le composant déclenche un événement via @Output().
○ La détection des changements est manuellement déclenchée via
ChangeDetectorRef.
○ Un observable lié à un template via async pipe émet une nouvelle
valeur.
Exemple d'utilisation de OnPush :
@Component({
selector: 'app-child',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div>{{ data }}</div>`
})
export class ChildComponent {
@Input() data: any;
}
5. Le Problème de "Out of Bound Change Detection"
Ce problème survient lorsque des modifications locales dans un composant (par
exemple, un état interne) déclenchent la détection des changements dans des
composants non liés. Cela peut affecter les performances de l'application.
● Solution : Utiliser la stratégie OnPush pour limiter la détection des
changements et éviter des recalculs inutiles. Refactoriser l'application pour
s'assurer que seuls les composants directement affectés par un changement
sont vérifiés.
6. Optimisations pour la Détection des Changements
● Externalisation des événements : Déplacer les événements à l'extérieur du
composant recevant @Input(), pour que le composant ne soit réaffiché que
lorsqu'une nouvelle référence est passée.
Utilisation de pipes purs : Pour éviter des recalculs inutiles d'expressions dans le
template, utilisez des pipes purs qui recalculent leur résultat uniquement lorsque les
entrées changent.
Exemple de pipe pur :
@Pipe({
name: 'multiply',
pure: true
})
export class MultiplyPipe implements PipeTransform {
transform(value: number, multiplier: number): number {
return value * multiplier;
Memoisation : Utiliser la mémorisation pour les fonctions pures afin d'éviter des
recalculs coûteux.
Exemple avec memo-decorator :
import { Memo } from 'memo-decorator';
class SomeComponent {
@Memo()
calculateExpensiveOperation(param: number): number {
return param * 1000;
7. Le Changement de Référence des @Input()
Pour que la stratégie OnPush fonctionne correctement avec des objets ou des
tableaux dans les entrées (@Input()), il est nécessaire de modifier la référence de
l'objet ou du tableau. Les modifications des propriétés d'un objet ou des éléments
d'un tableau n'affectent pas la référence et ne déclencheront donc pas de détection
des changements.
● Solution : Utiliser des structures de données immuables comme celles
fournies par la bibliothèque Immutable.js, qui facilite la gestion des objets et
des tableaux immuables.
Conclusion : Optimisation des Performances avec la Détection des
Changements
1. Optimisation de la portée de détection : Utiliser la stratégie OnPush pour
minimiser les recalculs.
2. Éviter les recalculs inutiles dans le template : Utiliser des pipes purs et la
mémorisation pour les fonctions coûteuses.
3. Gestion des objets immuables : Utiliser des structures de données immuables
pour les @Input(), afin de garantir que la détection des changements
fonctionne correctement.
Ces techniques et stratégies permettent d'optimiser les performances des
applications Angular en réduisant la charge inutile sur la détection des changements
et en évitant les recalculs des données et du DOM.
● « Zone Pollution Pattern »:
Il se produit lorsque Angular Zone encapsule un callback qui déclenchedes
détection de changement redondants.
La solution est donc de déplacer la logique d’initialisation àl’extérieur de Angular
Zone
Injecter NgZone Appeler la méthode runOutsideAngular Passez y une callback qui
effectue le traitement que vous voulezisoler
● Le problème appelé le "Zone Pollution Pattern" se produit lorsque zone.js
encapsule des rappels (callbacks) dans la zone Angular. Par exemple, si vous avez
un événement qui se déclenche fréquemment, comme mousemove ou un
setInterval, chaque déclenchement de cet événement entraîne une détection de
changement.
● Cela peut devenir inefficace et entraîner des détections de changement
redondantes, affectant les performances de l'application.
1. Désactiver complètement zone.js
Vous pouvez désactiver la prise en main automatique de zone.js pour gérer la détection
de changement vous-même. Pour ce faire, vous devez démarrer votre application Angular
sans zone.js en utilisant une configuration comme suit :
platformBrowserDynamic().bootstrapModule(AppModule, [
{ ngZone: 'noop' }
]);
Dans ce cas, la gestion de la détection de changement n'est plus automatique, et vous
devrez appeler manuellement la méthode tick() pour effectuer la détection de
changement.
2. Méthode tick() de ApplicationRef
La méthode tick() est utilisée pour déclencher une détection de changement manuelle
pour l'ensemble de l'application, en suivant la stratégie de détection de changement des
composants.
Exemple d'utilisation :
constructor(private applicationRef: ApplicationRef) {
this.applicationRef.tick(); // Déclenche manuellement la détection de changement
3. Méthode detectChanges() de ChangeDetectorRef
La méthode detectChanges() est utilisée pour déclencher la détection de changement
uniquement sur le composant courant et ses enfants, en suivant la stratégie de détection de
changement (par défaut ou OnPush).
Exemple :
constructor(private cdr: ChangeDetectorRef) {
this.cdr.detectChanges(); // Déclenche la détection de changement pour ce composant
Vous pouvez également combiner cette méthode avec detach() pour éviter que le
composant soit surveillé par le système de détection de changement, mais pour forcer une
détection de changement manuelle lorsque cela est nécessaire.
Exemple avec detach() et detectChanges() :
constructor(private cdr: ChangeDetectorRef) {
this.cdr.detach(); // Détache ce composant du système de détection de changement
// Exécutez la détection de changement manuellement plus tard
this.cdr.detectChanges(); // Déclenche la détection de changement manuelle
4. Méthode markForCheck() de ChangeDetectorRef
La méthode markForCheck() marque un composant pour être vérifié lors du prochain
cycle de détection de changement, même si ce composant utilise la stratégie OnPush. Cela
n'entraîne pas immédiatement une détection de changement mais garantit que le composant
sera vérifié dans le prochain cycle.
Exemple :
constructor(private cdr: ChangeDetectorRef) {
this.cdr.markForCheck(); // Marque ce composant et ses ancêtres comme devant être
vérifiés
Récapitulatif des méthodes :
● tick() : Déclenche la détection de changement pour l'ensemble de l'application.
● detectChanges() : Déclenche la détection de changement pour un composant
spécifique et ses enfants.
● markForCheck() : Marque un composant pour vérification future (utile avec
OnPush).
● detach() + detectChanges() : Permet un contrôle local de la détection de
changement, détachant un composant du système de détection jusqu'à ce qu'une
détection manuelle soit nécessaire.
Ces méthodes vous permettent de mieux contrôler quand et comment Angular effectue la
détection des changements, ce qui peut être crucial pour des applications complexes où les
performances sont importantes.
NgRx - Introduction et Contexte
Redux et Flux
● Flux : une architecture créée par Facebook, basée sur un flux de données
unidirectionnel. Elle utilise un dispatcher pour distribuer les actions envoyées par
l'utilisateur ou le serveur.
● Redux : une version simplifiée de Flux. Il se distingue par :
○ Une source de vérité unique : le store.
○ Des états immuables.
○ Pas de dispatcher (un seul store suffit).
NgRx
● NgRx est une implémentation de Redux adaptée à Angular.
● Objectif : gestion centralisée de l’état de l’application.
● Fournit des bibliothèques pour :
○ Gérer l'état global et local.
○ Isoler les effets de bord.
○ Gérer les collections d’entités.
○ S’intégrer avec le routeur Angular.
Concepts Clés de NgRx
1. Store :
○ Contient l’état de l’application.
○ N’est pas directement le state mais un conteneur.
2. State :
○ La source unique de vérité.
○ Un objet immuable centralisant l’état de l’application.
○ Représente l’état de l’application à un moment donné.
3. Fonctions pures :
○ Permettent de changer l’état sans effet de bord.
○ Elles retournent une nouvelle version de l’état sans modifier les données
existantes.
Exemple :
function counterReducer(state: number = 0, action: Action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
○ }
Quand utiliser NgRx ?
Le principe SHARI aide à déterminer si NgRx est approprié :
● Shared : l’état est-il partagé par plusieurs composants ou services ?
● Hydrated : l’état doit-il être conservé entre les rechargements de pages ?
● Available : l’état doit-il être disponible lors de changements de routes ?
● Retrieved : l’état doit-il être récupéré via des effets de bord (e.g., requêtes HTTP) ?
● Impacted : l’état est-il modifié par plusieurs composants ?
Cas d’usage courants :
● Quand plusieurs composants ou services dépendent de la même portion de l’état.
● Lorsqu’il faut refactoriser un composant en plusieurs parties.
● Dans une grande application avec pagination, filtres, etc. Si l’utilisateur revient à une
page précédente, conserver l’état améliore l’expérience utilisateur.
Problèmes résolus par Redux et NgRx :
● Modification concurrente des données par le serveur et le client.
● Gestion des props non liées à l’état local d’un composant.
Exemple de Syntaxe
Déclaration d’un Store :
interface AppState {
counter: number;
const initialState: AppState = {
counter: 0
1. };
Action :
import { createAction } from '@ngrx/store';
export const increment = createAction('[Counter Component] Increment');
2. export const decrement = createAction('[Counter Component] Decrement');
Reducer :
import { createReducer, on } from '@ngrx/store';
import { increment, decrement } from './counter.actions';
const _counterReducer = createReducer(
initialState,
on(increment, (state) => ({ ...state, counter: state.counter + 1 })),
on(decrement, (state) => ({ ...state, counter: state.counter - 1 }))
);
export function counterReducer(state: any, action: any) {
return _counterReducer(state, action);
3. }
Selectors :
import { createSelector } from '@ngrx/store';
4. export const selectCounter = (state: AppState) => state.counter;
Voici une explication de l'architecture de NgRx, telle qu'illustrée dans le diagramme :
1. Component
● Le composant Angular déclenche des actions ou écoute les changements de l'état à
travers des sélecteurs.
● C'est à partir du composant que l'interaction utilisateur démarre.
2. Action
● Une action est un objet décrivant une intention d'effectuer une opération sur l'état.
● Elle est déclenchée par le composant et envoyée au Store via le Reducer.
3. Reducer
● Le réducteur (Reducer) est une fonction pure qui prend l'état actuel et une action
comme entrées, puis retourne un nouvel état.
● C'est ici que la logique de mise à jour de l'état est définie.
4. Store
● Le Store est une source unique de vérité qui contient l'état global de l'application.
● Il expose des sélecteurs permettant d'observer les changements d'état et de
déclencher des mises à jour dans les composants.
5. Selector
● Les sélecteurs sont des fonctions qui extraient des morceaux spécifiques de l'état
global, optimisant ainsi la récupération des données pour les composants.
6. Effects
● Les effets gèrent les opérations asynchrones (comme les appels aux services) et
peuvent déclencher de nouvelles actions une fois ces opérations terminées.
● Ils interceptent les actions et effectuent des traitements en dehors du Store, comme
des appels API via les Services.
7. Service
● Les services Angular exécutent des tâches métier, comme la communication avec
une API backend.
● Les données récupérées sont ensuite renvoyées à travers des actions pour mettre à
jour l'état via les Reducers.
Résumé du NgRx
1. Un composant déclenche une action.
2. L'action passe par le Reducer qui modifie l'état du Store.
3. Si une opération asynchrone est nécessaire, elle est gérée par les Effects, qui
utilisent les Services.
4. Une fois l'opération asynchrone terminée, une nouvelle action est envoyée pour
mettre à jour le Store.
5. Les Sélecteurs permettent aux composants d'écouter les changements et de réagir
en conséquence.
Ce modèle favorise une gestion centralisée et prévisible de l'état dans les applications
Angular.
● Smart Component
Dispache les actions
Se préoccupe du Store
Récupère les données du Store
● Presentational Component
Ne se préoccupe pas du store
Invoque les callbacks via les @Output
Lit les datas des @Input
Dans NgRx, le store est effectivement représenté par un Observable, ce qui signifie que
vous ne pouvez pas directement manipuler ou modifier l'état en l'appelant comme une
méthode classique. Ce comportement suit le principe Flux (ou Redux), qui est basé sur la
gestion unidirectionnelle des données.
Pourquoi le store est un Observable ?
Le store représente l'état global de l'application sous forme d'un flux de données réactif.
Lorsque vous interagissez avec le store, vous ne manipulez pas directement l'état, mais
vous émettez des actions qui sont ensuite traitées par les reducers. Ces reducers sont
responsables de la mise à jour de l'état en fonction des actions reçues.
Résumé Complet sur NgRx pour l'Examen
NgRx est une bibliothèque pour la gestion d'état dans les applications Angular basée sur le
principe de flux unidirectionnel de données. Elle s'inspire de Redux et utilise des
concepts comme les actions, reducers, et store pour gérer et mettre à jour l'état d'une
application de manière prévisible.
1. Store et Injection
Le store est une abstraction qui permet de centraliser l'état de l'application. Il expose un
observable du type Store<AppState>, ce qui permet de récupérer l'état en tant
qu'observable. Le store ne fournit pas de méthodes directes pour manipuler l'état, mais
plutôt pour écouter les changements de l'état et pour dispatcher des actions qui initient ces
changements.
Exemple de Store :
import { Store } from '@ngrx/store';
export interface AppState {
users: User[];
@Component({
selector: 'app-users',
templateUrl: './users.component.html',
})
export class UsersComponent {
constructor(private store: Store<AppState>) {}
// Pour obtenir une partie du state
users$ = this.store.select('users');
2. Actions
Les actions décrivent des événements uniques qui se produisent dans l'application, comme
un utilisateur qui se connecte ou une mise à jour de profil. Chaque action implémente
l'interface Action de NgRx, avec une propriété type qui est un identifiant de l'action et un
payload optionnel pour passer des données.
Syntaxe d'une action :
import { createAction, props } from '@ngrx/store';
import { User } from './model/user.model';
export const loginAction = createAction(
'[Login Page] User Login',
props<{ user: User }>()
);
Le type de l'action suit une convention de nommage : [Source] Event, où Source est la
source de l'événement (par exemple, Login Page) et Event est l'événement spécifique.
Dispatcher d'une action :
Pour déclencher une action, on utilise la méthode dispatch du store.
this.store.dispatch(loginAction({ user }));
3. Reducers
Les reducers sont des fonctions pures qui prennent l'état actuel et une action, et renvoient
un nouvel état. Ils doivent respecter l'immutabilité de l'état, c'est-à-dire qu'ils ne doivent pas
modifier directement l'état, mais retourner un nouvel objet d'état.
Syntaxe d'un reducer :
import { createReducer, on } from '@ngrx/store';
import { loginAction } from './actions';
import { User } from './model/user.model';
export interface AuthState {
user: User | null;
export const initialAuthState: AuthState = {
user: null,
};
export const authReducer = createReducer(
initialAuthState,
on(loginAction, (state, action) => ({ user: action.user }))
);
Les reducers peuvent être créés à l'aide de la fonction createReducer et l'utilisation de on
pour associer une action à un traitement spécifique. Dans cet exemple, lorsque l'action
loginAction est dispatchée, le reducer met à jour l'état avec l'utilisateur.
Enregistrement du Reducer dans le Store :
Pour enregistrer un reducer dans le store, on utilise la méthode provideStore dans les
providers du module Angular :
providers: [
provideStore({ app: appReducer })
4. State Principal et State Fonctionnel
Le state principal de l'application représente l'état global qui est géré par les reducers.
Chaque fonctionnalité peut avoir un état propre (un feature state), géré par un reducer
spécifique. Ces fonctionnalités peuvent être enregistrées avec provideState dans les
providers.
Exemple d'enregistrement d'un Feature State :
providers: [
provideStore({ app: appReducer }),
provideState({ name: 'auth', reducer: authReducer })
Le state des fonctionnalités (feature state) est similaire au state principal mais concerne une
partie spécifique de l'état global.
5. Action Groups
À partir de la version 13 de NgRx, il est possible de regrouper des actions similaires sous un
même groupe en utilisant la fonction createActionGroup. Cela permet de simplifier la
gestion des actions liées à une même source.
Exemple de Groupes d'Actions :
import { createActionGroup, emptyProps, props } from '@ngrx/store';
export const exampleActions = createActionGroup({
source: 'Group Actions',
events: {
'Load Users': emptyProps(),
'Add User': props<{ user: User }>(),
},
});
Les actions sont ensuite dispatchées avec la syntaxe suivante :
this.store.dispatch(exampleActions.addUser({ user }));
6. Bonnes Pratiques
● Commencer par les actions : Définissez les actions avant d'implémenter des
fonctionnalités pour avoir une vue d'ensemble des événements à gérer.
● Catégoriser les actions : Utilisez des catégories pour organiser les actions par
source d'événements.
● Immutabilité : L'état de l'application doit toujours être immuable. Les reducers ne
doivent jamais modifier l'état en place mais doivent retourner un nouvel objet.
● Eviter la logique complexe dans les reducers : Si le traitement est complexe, il est
préférable de le déplacer dans des helpers ou des services et de garder les reducers
simples.
Exemple d'une bonne pratique :
export interface AppState {
users: User[];
}
export const usersReducer = createReducer(
initialState,
on(loadUsersAction, state => ({ ...state, loading: true })),
on(addUserAction, (state, { user }) => ({
...state,
users: [...state.users, user],
}))
);
7. Debugging et DevTools
NgRx offre des outils pour debugger l'état de l'application avec StoreDevtools. Vous
pouvez activer cet outil pour suivre les actions dispatchées et l'évolution de l'état.
Exemple de configuration des DevTools :
providers: [
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
provideStore({ app: appReducer }),
Cela permet d'afficher un historique des actions et de l'état dans les outils de
développement du navigateur.
Non, le store ne crée pas automatiquement des sélecteurs. C'est vous, en tant que
développeur, qui devez créer des sélecteurs pour accéder aux différentes parties de l'état du
store de manière optimisée.
Pourquoi créer des sélecteurs ?
1. Réduire la redondance : En créant des sélecteurs, vous centralisez la logique de
sélection de l'état. Cela vous permet de ne pas répéter plusieurs fois la même
logique pour accéder à une même partie de l'état à différents endroits dans votre
application.
2. Optimisation de la performance (Memoization) : Comme je l'ai expliqué
précédemment, les sélecteurs créés avec createSelector sont memoized, ce qui
signifie que si l'état que vous sélectionnez n'a pas changé, la même valeur est
renvoyée sans recalculer l'extraction de données. Cela améliore considérablement la
performance, surtout si l'opération de sélection est coûteuse.
3. Maintenabilité : En utilisant des sélecteurs, vous pouvez modifier la structure de
l'état du store sans avoir à changer toutes les parties du code qui accèdent à cet
état. Vous n'avez qu'à modifier le sélecteur, et toutes les références à ce sélecteur
s'adapteront automatiquement.
La séquence des opérations :
Création du Store : Vous définissez l'état de votre application dans un store NgRx. Ce store
contient toute la logique liée aux actions, aux reducers, et à la gestion de l'état global de
l'application.
Exemple d'état du store :
interface AppState {
auth: { user: string | null };
userProfile: { name: string; age: number };
1.
Création des Sélecteurs : Vous créez des sélecteurs pour extraire des parties spécifiques
de cet état. Les sélecteurs sont des fonctions qui reçoivent l'état du store et renvoient des
sous-ensembles de cet état.
Exemple de sélecteur simple :
const selectAuth = (state: AppState) => state.auth;
const selectUserProfile = (state: AppState) => state.userProfile;
2.
Création des Sélecteurs Composés : Pour des sélections plus complexes, vous pouvez
utiliser createSelector pour combiner plusieurs sélecteurs, les transformer et renvoyer
un nouveau format de données.
Exemple d'un sélecteur composé :
import { createSelector } from '@ngrx/store';
export const selectUserNameAndStatus = createSelector(
selectAuth,
selectUserProfile,
(auth, userProfile) => ({
userName: userProfile.name,
isLoggedIn: !!auth.user
})
);
3.
Utilisation des Sélecteurs : Enfin, vous utilisez ces sélecteurs dans vos composants ou
services pour accéder à l'état. Cela peut se faire avec l'opérateur select de NgRx.
Exemple d'utilisation dans un composant :
this.userNameAndStatus$ = this.store.select(selectUserNameAndStatus);
4.
5. Abonnement au Sélecteur : Les sélecteurs sont souvent utilisés avec l'opérateur
select dans vos composants. Vous vous abonnez à l'Observable renvoyé par
select pour obtenir les données de l'état dans vos composants.
Résumé de la séquence :
1. Définir l'état du store : Vous définissez l'ensemble des données (state) de votre
application dans le store NgRx.
2. Créer des sélecteurs : Vous créez des sélecteurs pour extraire des morceaux de
l'état (avec ou sans createSelector).
3. Utiliser les sélecteurs : Vous utilisez ces sélecteurs dans vos composants/services
pour obtenir des valeurs à partir du store.
À quoi servent les sélecteurs dans cette séquence ?
● Accéder à l'état de manière propre et optimisée : Au lieu d'extraire des données
directement depuis l'état du store dans chaque composant, vous créez des
sélecteurs réutilisables pour y accéder de manière centralisée.
● Optimisation : Grâce à la mémorisation des sélecteurs (createSelector), vous
évitez de recalculer des valeurs coûteuses si l'état n'a pas changé.
● Sécurité de type et testabilité : Les sélecteurs offrent une meilleure sécurité de
type et rendent votre code plus facile à tester.
La fonction createFeature réduit le code répétitif dans les fichiers desélection en générant
des sélecteurs enfants pour chaque propriétéd'état de fonctionnalité.
Résumé Complet sur NgRx - Sélection des éléments du store
NgRx est une bibliothèque de gestion d’état pour les applications Angular. Elle utilise un
modèle de flux réactif basé sur RxJS, ce qui permet une gestion efficace du state global. Ce
résumé couvre les concepts essentiels de l’utilisation de NgRx, avec des explications
détaillées, des exemples de syntaxe et des pratiques recommandées pour sélectionner et
manipuler l’état dans une application.
1. Accéder aux éléments du Store
Le Store dans NgRx est un observable qui permet de gérer l’état global de l’application.
Pour accéder au store, vous pouvez utiliser différentes méthodes.
a. Souscription au Store
L'une des manières d'accéder à l'état est de s'abonner au Store en utilisant la méthode
subscribe. Cela permet de récupérer le state global de l'application.
Exemple :
this.store.subscribe((state) => {
console.log("state : ", state);
console.log("authState : ", state['auth']);
});
Cette approche permet de récupérer l'état à chaque changement, mais peut ne pas être
optimale si l'état est très large ou s'il change fréquemment.
2. Utiliser l'Opérateur map pour Sélectionner des Parties du Store
L'opérateur map de RxJS permet de sélectionner une partie du store (par exemple, une
sous-propriété de l'état) et de la transformer.
Exemple :
this.isLoggedIn$ = this.store.pipe(
map((state) => !!state["auth"].user)
);
Ici, l'opérateur map transforme l'état global pour extraire la valeur de auth.user.
L'expression !! permet de vérifier si l'utilisateur est connecté (en renvoyant true ou
false).
3. Optimisation avec distinctUntilChanged
Le problème avec l'utilisation simple de map est que l'opération sera exécutée chaque fois
que l'état change, même si la valeur de sortie reste identique. Pour éviter cela, on utilise
l'opérateur distinctUntilChanged, qui évite les recalculs inutiles en vérifiant si la valeur
a changé.
Exemple :
this.isLoggedIn$ = this.store.pipe(
map((state) => state["auth"]),
distinctUntilChanged()
);
Avec distinctUntilChanged(), la sélection ne sera effectuée que si la valeur de
state["auth"] change réellement.
4. Utiliser l'Opérateur select de NgRx
NgRx fournit un opérateur select optimisé pour la sélection d’une portion de l’état du store.
Il applique automatiquement distinctUntilChanged, ce qui évite des recalculs inutiles
et améliore la performance.
Exemple :
this.isLoggedIn$ = this.store.select(
(state) => !!state["auth"].user
);
Cet opérateur sélectionne la valeur de auth.user et ne déclenche le flux que si cette
valeur change.
5. Sélecteurs avec createSelector
createSelector est une fonction fournie par NgRx pour créer des sélecteurs optimisés
avec mémorisation (memoization). Cela permet de ne recalculer les sélections que lorsque
les données changent réellement, ce qui améliore la performance.
Exemple de Sélection Simple :
import { createSelector } from '@ngrx/store';
export const selectFeature = (state: AppState) => state.feature;
export const selectFeatureCount = createSelector(
selectFeature,
(state: FeatureState) => state.counter
);
Ici, selectFeature sélectionne une partie du state (feature), et selectFeatureCount
est un sélecteur dérivé qui récupère le compteur de cette partie du state.
Exemple de Sélecteur Composé :
export const selectVisibleBooks = createSelector(
selectUser,
selectAllBooks,
(selectedUser: User, allBooks: Book[]) => {
return selectedUser ? allBooks.filter(book => book.userId === selectedUser.id) : allBooks;
);
Ici, selectVisibleBooks filtre les livres en fonction de l'utilisateur sélectionné, ce qui
démontre l’utilisation de sélecteurs composés pour des logiques plus complexes.
6. Sélecteurs de Fonctionnalité avec createFeatureSelector
NgRx permet de centraliser la gestion d’une fonctionnalité spécifique en utilisant des
sélecteurs de fonctionnalité. Cela permet de sélectionner une sous-section spécifique du
state.
Exemple :
export const authFeatureSelector = createFeatureSelector<AuthState>("auth");
Ce sélecteur permet de récupérer l’état auth du store.
7. Réduire le Code Répétitif avec createFeature
À partir de la version 16 de NgRx, la fonction createFeature permet de simplifier la
création des sélecteurs de fonctionnalité en réduisant le code répétitif.
Exemple de Création de Feature :
export const appFeature = createFeature({
name: "app",
reducer: reducer
});
Cela crée un sélecteur de fonctionnalité pour le nom app, et permet également de
centraliser l’état, les réducteurs, et les sélecteurs associés à cette fonctionnalité.
Ajout de Sélecteurs Supplémentaires avec extraSelectors :
export const todoFeature = createFeature({
name: "todo",
reducer: todoReducer,
extraSelectors: ({ selectTodos }) => {
const selectCompletedTodos = createSelector(selectTodos, (todos) =>
todos.filter((todo) => todo.completed)
);
return { selectCompletedTodos };
});
Cela permet d’ajouter des sélecteurs personnalisés pour la fonctionnalité "todo" de manière
propre et modulable.
Conclusion
NgRx offre une approche robuste pour gérer l'état global d'une application Angular en
utilisant des sélecteurs puissants et optimisés. Grâce à des outils comme
createSelector, select, et createFeature, vous pouvez sélectionner et manipuler
l'état de manière performante, avec un focus sur la mémorisation pour éviter les recalculs
inutiles et améliorer l'efficacité de l'application. Utiliser des sélecteurs bien conçus rend le
code plus modulaire, testable et facile à maintenir.
Pourquoi utiliser { dispatch: false } ?
Lorsque vous utilisez createEffect(), l'objectif est généralement de déclencher une
action après l'exécution de l'effet. Cependant, dans certaines situations, vous ne voulez pas
déclencher une nouvelle action, mais simplement effectuer une tâche (par exemple, loguer
des informations, exécuter une action côté serveur, etc.).
Résumé Complet de la Conversation :
1. Introduction à NgRx et les Effets (Effects)
NgRx est une bibliothèque de gestion d'état pour les applications Angular, inspirée de
Redux. Elle permet de gérer de manière prévisible l'état de l'application en utilisant des
concepts comme le Store, les Actions, et les Reducers.
Les Effets (Effects) sont un composant clé de NgRx, qui permet de gérer les effets de
bord (side effects), comme les appels API, la gestion des données asynchrones, ou les
tâches secondaires sans interférer directement avec les composants. Cela permet de rendre
les composants plus purs et de maintenir une architecture plus claire.
Problématique des Effets de Bord
● Dans une architecture classique, les composants interagissent directement avec les
services pour communiquer avec des API ou effectuer des actions asynchrones.
Cela crée des dépendances fortes et limite la testabilité.
● Les effets permettent d’isoler ces effets de bord, en les déplaçant dans un service
dédié qui écoute les actions dispatchées depuis le Store et effectue les tâches
nécessaires en réponse à ces actions.
2. Fonctionnement des Effets
Les Effets sont des services qui écoutent les actions dispatchées depuis le Store. Ils
exécutent des tâches synchrones ou asynchrones et peuvent renvoyer des nouvelles
actions au Store. Cela permet de déclencher des modifications d’état sans que les
composants aient à gérer directement ces interactions.
Schéma général :
1. Le composant dispatch une action.
2. L’effet écoute cette action et déclenche une tâche, par exemple une requête API.
3. Lorsque la tâche est terminée, l’effet peut dispatch une nouvelle action pour mettre
à jour le store avec les résultats.
3. Installation des Effects
Installation avec NPM :
npm install @ngrx/effects
Installation via CLI (recommandé) :
À partir de la version 6 de NgRx, il est recommandé d’utiliser la commande ng add :
ng add @ngrx/effects@latest
Cette commande :
● Met à jour package.json avec la dépendance @ngrx/effects.
● Exécute npm install pour installer les dépendances.
● Met à jour app.module.ts avec la configuration nécessaire :
EffectsModule.forRoot([]).
4. Configuration des Effects
Dans votre module principal, vous devez configurer les effets en les ajoutant à la méthode
provideEffects() dans le tableau des providers.
Exemple de configuration :
providers: [
provideStore({ app: reducer }),
provideEffects([YourEffects]),
Si vous utilisez un module de fonctionnalité, vous ajoutez des effets spécifiques à ce module
:
@NgModule({
providers: [
provideEffects([YourFeatureEffects]),
],
})
export class YourModule {}
5. Création d'un Effect
Pour créer un effet, NgRx vous fournit la fonction createEffect qui vous permet de créer
un effet basé sur un Observable. Il écoute une action spécifique et envoie une nouvelle
action ou effectue une autre tâche.
Exemple : Charger des Films depuis une API :
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { MoviesService } from './movies.service';
@Injectable()
export class MovieEffects {
loadMovies$ = createEffect(() => this.actions$.pipe(
ofType('[Movies Page] Load Movies'),
mergeMap(() => this.moviesService.getAll()
.pipe(
map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
catchError(() => EMPTY)
));
constructor(private actions$: Actions, private moviesService: MoviesService) {}
}
Dans cet exemple :
● ofType('[Movies Page] Load Movies') : Filtre l’action qui doit déclencher cet
effet.
● mergeMap() : Effectue un appel au service pour récupérer les films.
● map() : Crée une nouvelle action avec les films récupérés.
● catchError() : Gère les erreurs et retourne un Observable vide en cas d’échec.
6. Effets Sans Nouvelle Action (dispatch: false)
Certaines fois, vous ne souhaitez pas dispatcher une nouvelle action après l'exécution de
l'effet. Cela peut être utile pour effectuer des tâches secondaires, comme la sauvegarde
dans le localStorage, sans modifier l'état du store.
Exemple avec { dispatch: false } :
import { Injectable } from "@angular/core";
import { Actions, createEffect, ofType } from "@ngrx/effects";
import { tap } from "rxjs/operators";
import { AuthActions } from "../ngrx/action-types";
@Injectable()
export class AuthEffects {
login$ = createEffect(
() => this.actions$.pipe(
ofType(AuthActions.loginAction),
tap(action => localStorage.setItem('user', JSON.stringify(action.user)))
),
{ dispatch: false } // Pas d'action à dispatcher après cet effet
);
constructor(private actions$: Actions) {}
}
Ici, { dispatch: false } empêche l’effet de dispatcher une nouvelle action après avoir
effectué la sauvegarde dans localStorage.
7. Opérateurs RxJS dans les Effects
Les effets utilisent des opérateurs RxJS pour transformer les flux d’actions et effectuer des
tâches asynchrones.
● mergeMap() : Pour effectuer une tâche asynchrone (ex : appel API).
● map() : Pour transformer les données reçues en une nouvelle action.
● catchError() : Pour capturer les erreurs et renvoyer un résultat alternatif (par
exemple, un Observable vide).
● tap() : Pour effectuer une tâche secondaire sans affecter le flux d'actions.
8. Résumé Final
Les effets dans NgRx permettent de gérer de manière propre et efficace les effets de bord
dans une application Angular. Au lieu de mélanger la logique métier dans les composants,
les effets isolent ces actions dans des services, rendant ainsi les composants plus purs et
faciles à tester. Les effets peuvent écouter des actions, effectuer des appels asynchrones et
dispatcher des nouvelles actions pour mettre à jour l'état de l'application.
Principaux Concepts :
● createEffect : Crée un effet qui écoute les actions.
● ofType : Filtre les actions basées sur leur type.
● dispatch: false : Empêche l’envoi d’une action après l’effet.
● RxJS operators : Utilisés pour manipuler les flux d’actions et gérer les tâches
asynchrones.
Exemple de syntaxe :
loadMovies$ = createEffect(() => this.actions$.pipe(
ofType('[Movies Page] Load Movies'),
mergeMap(() => this.moviesService.getAll()
.pipe(
map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
catchError(() => EMPTY)
));
Installation et Configuration :
npm install @ngrx/effects
ng add @ngrx/effects@latest
Résumé Complet sur les Service Workers dans Angular pour un Examen
1. Introduction aux Services Workers dans Angular
Les Service Workers sont une fonctionnalité puissante qui permet de gérer le cache et
d'offrir des comportements hors ligne pour les applications web progressives (PWA). Dans
Angular, les services workers sont gérés par la bibliothèque @angular/service-worker.
Ils permettent de mettre en cache les ressources de l’application et de les actualiser en
arrière-plan.
2. Mise en Place d’un Service Worker dans Angular
Commande pour ajouter un Service Worker : La commande suivante est utilisée pour
ajouter le support de PWA (Progressive Web App) à un projet Angular :
ng add @angular/pwa --project NOM_DU_PROJET
● Cette commande effectue plusieurs actions :
○ Ajout de la dépendance @angular/service-worker.
○ Activation du service worker dans le CLI Angular.
○ Mise à jour de index.html pour inclure le fichier
manifest.webmanifest.
○ Création des fichiers d'icônes pour l’application mobile.
3. Fichiers de Configuration
3.1 Fichier ngsw-config.json
Le fichier ngsw-config.json permet de spécifier les fichiers à mettre en cache et la
stratégie de mise à jour. Il décrit les AssetGroups et déclare comment les fichiers doivent
être mis à jour ou mis en cache.
Exemple :
"$schema": "./node_modules/@angular/service-worker/config/schema.json",
"index": "/index.html",
"assetGroups": [
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/favicon.ico", "/index.html", "/manifest.webmanifest", "/*.css", "/*.js"
},
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**", "/*.(svg|jpg|jpeg|png)"
]
}
3.2 Fichier ngsw.json
Le fichier ngsw.json est généré lors de la construction du projet. Il est utilisé par le Service
Worker pour exécuter les actions de mise en cache au moment de l’exécution. Ce fichier
contient les URLs des fichiers à mettre en cache et des informations supplémentaires sur la
mise à jour des ressources.
Exemple :
"configVersion": 1,
"index": "/index.html",
"assetGroups": [
"name": "app",
"urls": [
"/favicon.ico", "/index.html"
},
"name": "assets",
"urls": [
"/assets/icons/icon-128x128.png", "/assets/icons/icon-144x144.png"
],
"hashTable": {
"/assets/icons/icon-128x128.png": "b1f06c7d714abb4a0cc4d1f7c954feb26826e4c4",
"/assets/icons/icon-144x144.png": "96a0d629765c1bc85d9263c6bc83094ca776d267"
4. Les AssetGroups
Les AssetGroups définissent les ressources à mettre en cache et leur mode d'installation et
de mise à jour. Chaque AssetGroup peut inclure des fichiers ou des URLs spécifiques.
4.1 Modes d'Installation (installMode)
Les ressources peuvent être installées avec l'une des deux stratégies suivantes :
● prefetch : Toutes les ressources sont téléchargées au moment de la mise en
cache.
● lazy : Les ressources sont mises en cache uniquement lorsqu'elles sont
demandées.
4.2 Modes de Mise à Jour (updateMode)
● prefetch : Le Service Worker met à jour les ressources dès que la nouvelle version
est disponible.
● lazy : Le Service Worker attend que les ressources soient demandées pour les
mettre à jour.
5. Manifest WebApp (manifest.webmanifest)
Le fichier manifest.webmanifest contient des informations essentielles sur l’application,
comme le nom, les couleurs, et les icônes qui seront affichées sur l'écran d'accueil d'un
appareil mobile.
Exemple :
"name": "My App",
"short_name": "App",
"theme_color": "#000000",
"background_color": "#ffffff",
"display": "standalone",
"icons": [
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
6. Service SwUpdate
Le service SwUpdate permet de gérer la mise à jour des applications et de notifier
l'utilisateur lorsque de nouvelles versions sont disponibles. Ce service permet également
d’effectuer trois opérations principales :
1. Vérifier si une mise à jour est disponible.
2. Notifie l’utilisateur d’une mise à jour disponible.
3. Activer la dernière version lorsque l'application est mise à jour.
6.1 Propriété versionUpdates
versionUpdates est un observable qui émet plusieurs événements :
● VersionDetectedEvent : Une nouvelle version a été détectée.
● NoNewVersionDetectedEvent : Aucune nouvelle version n’a été trouvée.
● VersionReadyEvent : La nouvelle version est prête à être activée.
● VersionInstallationFailedEvent : L’installation de la nouvelle version a
échoué.
7. Flux de Mise à Jour des Applications
● Lors du premier chargement après un déploiement, le Service Worker détecte la
nouvelle version grâce au fichier ngsw.json et télécharge les nouvelles ressources
en arrière-plan.
● Au deuxième rechargement, l'utilisateur voit la nouvelle version de l'application,
puisque toutes les ressources ont été mises à jour dans le cache.
Conclusion
Les Service Workers dans Angular offrent une solution efficace pour gérer les mises à jour
des applications et permettre une expérience hors ligne fluide. En utilisant des stratégies de
mise en cache flexibles et en intégrant la mise à jour en arrière-plan, les applications
Angular peuvent offrir des performances optimales et des mises à jour transparentes.
Pour résumer, voici comment les nouvelles versions d'application sont gérées par Angular
Service Worker : Pour chaque nouvelle version, un nouveau nsw.json est disponible Pour
le premier rechargement de l'application après le déploiement de la nouvelle version -
Angular Service Worker détecte le nouveau ngsw.json et charge tous les nouveaux fichiers
en arrière-plan Pour le deuxième rechargement après le déploiement de la nouvelle version
- l'utilisateur voit la nouvelle version
Les ressources critiques doivent être chargées en premier, tandis que les autres ressources
moins cruciales doivent être chargées plus tard, uniquement si nécessaire. Ceci est fait à
travers le lazy loading. Cependant, le lazy loading nous permet uniquement de différer un
composant complet. Mais que se passe-t-il si vous disposez d'un grand écran avec de
nombreuses dépendances et que vous souhaitez charger des parties de la page qui ne sont
pas immédiatement visibles au besoin. Par exemple quand l'utilisateur fasse défiler la page
vers le bas ou clique sur un bouton. Ce type de chargement différé très fin n’est tout
simplement pas possible avec le lazy loading. @defer permet un chargement différé d’une
partie de votre composant Il permet de dire quand et pour quelle raison une partie sera
chargée dans le navigateur de votre client.
● @loading : Affiché uniquement pendant le chargement du bundle du composant
différé.
● @placeholder : Affiché avant que le contenu différé ne soit chargé et peut être
remplacé par ce contenu une fois prêt.
Résumé Complet : Optimisation avec @defer dans Angular
Introduction à @defer
@defer est un mécanisme d'optimisation dans Angular qui permet de différer le chargement
et l'affichage de certaines parties d'un composant afin d'améliorer les performances de
l'application. Cela permet de charger des ressources non critiques uniquement lorsque cela
est nécessaire, ce qui réduit le temps de chargement initial de l'application et améliore
l'expérience utilisateur.
Les deux niveaux de contrôle avec @defer
@defer offre deux niveaux de contrôle distincts :
1. Déclencheur de préchargement (prefetch) : Permet de charger un bundle (une
ressource) depuis le backend avant qu'il soit affiché. Il se produit en arrière-plan,
sans perturber l'expérience utilisateur.
2. Déclencheur d'affichage (@defer) : Contrôle le moment où le contenu est
réellement affiché à l'utilisateur. Le contenu sera rendu lorsque le déclencheur est
activé.
Ces deux événements (préchargement et affichage) peuvent être contrôlés
indépendamment, ce qui offre une flexibilité pour gérer des cas d'utilisation avancés.
Déclencheurs Prédéfinis et Personnalisés
1. Déclencheurs Prédéfinis :
○ Angular propose des déclencheurs prédéfinis pour les cas d'utilisation les
plus courants.
○ Le mot-clé on est utilisé pour ces déclencheurs prédéfinis, tels que :
■ idle : Déclenche l'événement lorsque le navigateur est inactif,
c'est-à-dire lorsque toutes les ressources de la page sont chargées et
que le navigateur ne fait rien.
Exemple de syntaxe avec un déclencheur prédéfini :
@defer(on idle; prefetch on idle) {
<app-huge />
2.
○ Explication : Le composant app-huge est préchargé dès que le navigateur
devient inactif (quand tout le contenu est déjà chargé). Il sera affiché une fois
que le navigateur sera inactif.
3. Déclencheurs Personnalisés :
○ Vous pouvez définir vos propres déclencheurs si les prédéfinis ne
conviennent pas.
○ Le mot-clé when est utilisé pour ces déclencheurs personnalisés.
Exemple de déclencheur personnalisé :
@defer(when customTrigger; prefetch on customTrigger) {
<app-huge />
4.
○ Explication : customTrigger est une condition que vous pouvez définir
dans votre logique d'application pour déclencher le préchargement et
l'affichage.
Les Blocs @defer, @placeholder, @loading, et @error
1. Bloc @defer :
○ Utilisé pour différer le chargement d'un composant ou d'une partie de la page.
○ Le bloc est chargé lorsque le déclencheur spécifié se produit.
Exemple :
@defer {
<app-huge />
○
○ Explication : Le composant app-huge est chargé en différé.
2. Bloc @placeholder :
○ Utilisé pour afficher un contenu de remplacement (souvent un message ou un
espace vide) avant que le bloc différé ne soit affiché.
○ Le contenu dans @placeholder est chargé dans le bundle principal et
visible immédiatement.
Exemple :
@placeholder(minimum 1s) {
<h2>Chargement en cours...</h2>
○
○ Explication : Un message est affiché pendant au moins 1 seconde avant que
le composant différé ne soit chargé et affiché.
3. Bloc @loading :
○ Utilisé pour afficher un message ou un indicateur de chargement pendant que
le bloc @defer charge son bundle.
Exemple :
@loading(minimum 1s; after 500ms) {
<h3>Chargement du composant en cours...</h3>
○
○ Explication : Le message "Chargement du composant en cours..." sera
affiché pendant au moins 1 seconde, et ce bloc sera montré après 500ms de
chargement.
4. Bloc @error :
○ Utilisé pour afficher un message d'erreur si le chargement différé échoue pour
une raison quelconque (comme un problème réseau).
Exemple :
@error {
<h2>Un problème est survenu, nous ne pouvons pas charger le Huge Component :'</h2>
○
○ Explication : Si le bloc différé échoue à se charger, un message d'erreur
sera affiché à l'utilisateur.
Gestion des Dépendances dans @defer
Pour que les dépendances au sein d'un bloc @defer soient différées, elles doivent répondre
à deux conditions :
1. Autonomie : Les dépendances doivent être autonomes (standalone). Les
dépendances non autonomes seront toujours chargées dans le bundle principal,
même à l'intérieur des blocs @defer.
2. Références externes : Elles ne doivent pas être directement référencées en dehors
du bloc @defer (par exemple, dans des requêtes ViewChild).
Utilisation des Déclencheurs avec @defer
Le déclencheur de préchargement (prefetch) et le déclencheur d'affichage (@defer)
peuvent être utilisés pour contrôler précisément quand une ressource est chargée et
affichée. Vous pouvez spécifier le déclencheur via le mot-clé on (pour les déclencheurs
prédéfinis) ou when (pour les déclencheurs personnalisés).
Exemple avec déclencheur idle :
@defer(on idle; prefetch on idle) {
<app-huge />
}
● Explication : Le composant app-huge sera préchargé lorsque le navigateur est
inactif, et sera affiché également lorsque le navigateur est inactif.
Résumé des concepts clés
● @defer : Permet de différer le chargement et l'affichage des ressources non
critiques pour améliorer les performances de l'application.
● Déclencheurs : Contrôlent quand le bundle est chargé (préchargement) et quand le
bloc @defer est affiché (affichage). Les déclencheurs prédéfinis comme idle
peuvent être utilisés pour des cas courants.
● Blocs @placeholder, @loading, @error : Offrent des mécanismes pour gérer le
contenu de remplacement et afficher des messages d'attente ou d'erreur pendant
que le contenu différé se charge.
● Déclencheurs personnalisés : Offrent une flexibilité supplémentaire pour définir des
comportements spécifiques lorsque les déclencheurs prédéfinis ne suffisent pas.
L'utilisation de @defer et de ses blocs associés permet une gestion plus fine des
performances et de l'expérience utilisateur dans une application Angular.
@defer(on viewport) : Cette directive semble indiquer qu'un bloc de contenu ou un
composant (par exemple, <app-huge />) doit être chargé ou rendu uniquement lorsque
son contenu entre dans la zone visible de la fenêtre de l'utilisateur (la "viewport"). Cela se
fait en utilisant l'API IntersectionObserver, qui permet de détecter lorsque l'élément
cible (dans ce cas, <app-huge />) entre dans la fenêtre d'affichage du navigateur.
Résumé Complet sur l'Optimisation avec @defer et Préchargement
Introduction
L'optimisation du rendu dans les applications modernes est cruciale pour offrir une
expérience utilisateur fluide et rapide. Angular propose des outils puissants comme la
directive @defer pour contrôler de manière fine le moment où les ressources (chunks de
composants) sont chargées. Cette directive permet de définir des déclencheurs qui activent
le chargement différé de manière conditionnelle, en fonction de l'état de l'application et des
interactions utilisateur. De plus, l'utilisation combinée de @defer et du préchargement
(prefetch) peut améliorer encore plus les performances en anticipant le chargement des
ressources.
1. Déclencheurs de @defer
Angular offre six déclencheurs principaux pour gérer le moment du chargement différé des
composants :
● idle : Le chargement différé est déclenché lorsque le navigateur atteint un état
d'inactivité, détecté à l'aide de l'API requestIdleCallback. Comportement par
défaut.
viewport : Le bloc différé est chargé lorsque le contenu spécifié entre dans la fenêtre du
navigateur, en utilisant l'API IntersectionObserver. Exemple :
@defer(on viewport) {<app-huge/>}
Cela peut être combiné avec un élément de référence pour spécifier un déclencheur :
<p #deferTriggerElement>defer examples!</p>
@defer(on viewport(deferTriggerElement)) {<app-huge/>}
interaction : Le bloc différé se déclenche lorsque l'utilisateur interagit avec l'élément via des
événements comme click ou keydown. Exemple :
@defer(on interaction) {<app-huge/>}
hover : Le bloc différé se déclenche lorsque l'utilisateur survole l'élément, avec les
événements mouseenter ou focusin. Exemple :
@defer(on hover) {<app-huge/>}
immediate : Le chargement différé est effectué immédiatement après le rendu du client.
Exemple :
@defer(immediate) {<app-huge/>}
timer : Le chargement différé est déclenché après une durée spécifiée en millisecondes ou
en secondes. Exemple :
@defer(on timer(3s)) {<app-huge/>}
2. Combinaison des Déclencheurs
Il est possible de combiner plusieurs déclencheurs pour définir des conditions plus
complexes. Cela signifie que plusieurs conditions sont évaluées en tant qu'OR logique.
Exemple : Le bloc différé se déclenche soit lorsque l'utilisateur survole un élément, soit
après 3 secondes.
<p #deferTrigger>defer trigger</p>
@defer(on hover(deferTrigger); on timer(3s)) {<app-huge/>}
Cela permet d'optimiser le rendu en fonction des comportements utilisateurs spécifiques.
3. Déclencheurs Personnalisés avec @defer
Les déclencheurs personnalisés utilisent des expressions logiques pour contrôler le moment
où les ressources sont chargées. Ces expressions retournent un booléen et lorsque la
condition devient vraie, le @defer est activé.
Exemple : Déclenchement du chargement différé lorsqu'un champ de texte dépasse une
longueur spécifique.
<input type="text" #myInput (keyup)="myInput.value.length > 4 ? loadDeffered = true :
loadDeffered = false" class="form-control">
@defer(when loadDeffered) {
<app-huge/>
@placeholder(minimum 1s) {
<h2>Je suis là en attendant le vrai composant :)</h2>
● Explication :
○ Le keyup détecte les changements dans le champ input et ajuste la
variable loadDeffered en fonction de la longueur du texte.
○ Si la longueur du texte dépasse 4 caractères, loadDeffered devient true,
ce qui déclenche le chargement différé du composant <app-huge/>.
4. Préchargement avec @defer et prefetch
Le préchargement permet de récupérer les ressources avant qu'elles ne soient réellement
nécessaires, réduisant ainsi les temps d'attente lors de l'interaction avec l'utilisateur. Lorsque
@defer est combiné avec prefetch, il est possible de commencer à charger les
dépendances avant que l'utilisateur interagisse avec le composant.
Principe du Préchargement :
● prefetch : Permet de précharger les ressources en fonction des conditions
spécifiées par when et on. Cela garantit que les ressources sont déjà chargées
lorsqu'un utilisateur les nécessite.
Exemple avec Préchargement : Préchargement des ressources lorsque l'utilisateur survole
l'élément et que la condition de chargement différé est remplie :
<input type="text" #myInput (keyup)="myInput.value.length > 4 ? loadDeffered = true :
loadDeffered = false" class="form-control">
@defer(on hover; prefetch when loadDeffered) {
<app-huge/>
@placeholder(minimum 1s) {
<h2>Je suis là en attendant le vrai composant :)</h2>
@loading(minimum 1s; after 500ms) {
<h3>loading.....</h3>
● Explication :
○ Lorsque l'utilisateur survole l'élément (on hover), les ressources
nécessaires pour le composant <app-huge/> sont préchargées, mais
uniquement si la condition loadDeffered est vraie (définie par la longueur
du texte dans l'input).
○ Si l'élément est encore en attente, un message de chargement est affiché
après un délai de 500 ms.
Conclusion
Les optimisations de rendu dans Angular, à l'aide de @defer et du préchargement,
permettent de gérer efficacement le chargement des composants et des ressources selon le
comportement utilisateur. En combinant différents déclencheurs (idle, viewport,
interaction, hover, etc.) et en utilisant des déclencheurs personnalisés, on peut garantir
une expérience utilisateur rapide et fluide. Le préchargement permet d'anticiper les besoins
de l'utilisateur et de réduire la latence perçue en chargeant les ressources à l'avance.
Points Clés à Retenir pour l'Examen :
● Comprendre les six déclencheurs principaux de @defer et leur syntaxe.
● Savoir comment combiner les déclencheurs pour des comportements avancés.
● Utiliser des déclencheurs personnalisés avec des expressions booléennes.
● Exploiter le préchargement pour améliorer la réactivité des applications Angular.
fait un resumé COMPLETE de TOUTE LA CONVERSATION,
un résumé bien organisé et complet, pour examen, etudiant A++
met le contenu de mes prompts ainsi que qlq explications a partir de tes reponses +
exemples de syntaxe de code