Cours sur NodeJs, Angular et les Signaux PDF
Document Details
Uploaded by Deleted User
Tags
Summary
Ce document décrit les concepts de NodeJS, Angular et les signaux. Il explique les environnements d'exécution Javascript, les composants d'Angular tels que les templates, les composants, les métadonnées, les directives et les services. Il détaille également le flux de contrôle, la gestion des changements, ainsi que la création et la manipulation de signaux.
Full Transcript
Introduction 1. C’est quoi NodeJs? Environnement d’exécution JS Utilise V8 le moteur JS de Google. 2. les composants pricipaux du moteur javascript: Memory Heap : sert à allouer la mémoire utilisée dans le programme. Call Stack (pile d’exécution) : contient les données des fonctions...
Introduction 1. C’est quoi NodeJs? Environnement d’exécution JS Utilise V8 le moteur JS de Google. 2. les composants pricipaux du moteur javascript: Memory Heap : sert à allouer la mémoire utilisée dans le programme. Call Stack (pile d’exécution) : contient les données des fonctions exécutées par le programme. 3. Qu’est ce qu’un environnement d’exécution JS (NodeJs, Browser) ? l’endroit ou votre code JS va être exécuté déterminer quels variables globales sont accessibles pour vous (window pour le runtime envirement de votre browser) Ajax, DOM et d’autres API’s ne font pas parti de JavaScript. C’est l’environnement d’exécution Js (Ici fourni par votre Browser) qui les rend disponible. 4. Est-ce que JavaScript est Synchrone ou Asynchrone ? Js est SYNCHRONE. Les fonctions asynchrones telles que setTimout ne font pas partiede JS. Elles sont fournies par l’environnement d’exécution JS (Browser, NodeJs, etc..) Dans NodeJs c’est libuv qui permet cet aspet Asynchrone. 5. Comment ca marche dans le browser A part le moteur JS, votre environnement possède des API’s qui perme- ttent à votre Moteur JS d’exécuter certaines fonctionnalités comme le timeout, ou fetch. A ces API’s est associé un event loop et des callback queue. Pour le browser vous avez l’API DOM 6. Que contient l’environnement d’exécution NodeJs? Le noyau de l’environnement d’exécution est le moteur Js (V8 de Google) Il y a aussi les APIs Node (fs, http, etc..) Libuv la bibliothèque qui permet de gére r les I/O Asynchrone Les nodes Js Binding qui permettent de faire le lien entre le moteur Js et les APIs Node 1 7. NodeJs est-il Asynchrone? Dans sa présentation, NodeJs se présente comme étant unenvironnement d’exécution JavaScript asynchrone et dirigé par les événements. 8. Est-ce que NodeJs est Multi thread ? NodeJs est monothread au départ puis le multithrading a été introduit. 9. C’est quoi Angular? Framework JS développé par Google SPA Supporte plusieurs langages : ES5, EC6, TypeScript, Dart Rapide Orienté composant” 10. Les composants principaux d’Angular Template (html) Component (ts) Metadata Directive Injector: {services} 12. les méta data Appelé aussi « decorator », ce sont des informations permettant de décrire les classes. (@Component) 13. le data binding le mécanisme qui permet de mapper des éléments du DOM avec des pro- priétés et des méthodes du composant. Le Data Binding permettra aussi de faire communiquer les composants 14. les directives des classes avec la métadata @Directive. permettent de modifier le DOM et de rendre les Template dynamiques. Apparaissent dans des éléments HTML comme les attributs. Un composant est une directive à laquelle Angular a associé un Template 2 types de directives : les directives structurelles et les directives d’attributs. 2 15. les services Classes permettant d’encapsuler des traitements métiers. Doivent être légers. Associées aux composants et autres classes par injection de dépendances 16. méthodes d’installation d’Angular QuickStart seed – git clone https://github.com/angular/quickstart.git quickstart – npm install Angular CLI: – node >= 14.2 – npm install -g @angular/cli [@version] – ng new my-app or npx @angular/cli new my-app – ng help – ng serve [–host leHost] [–port lePort] 3 Les composants 1.1. Qu’est ce qu’un composant (Component) Un composant est une classe qui permet de gérer une vue. Il se charge uniquement de cette vue un fragment HTML géré par une classe JS (component en angular et controller en angularJS) 1.2. Qu’est ce qu’une application Angular un arbre de composants. La racine de cet arbre est l’application lancée par le navigateur au lancement 1.3. Les 3 caractéristiques d’un composant Composable (normal c’est un composant) Réutilisable Hiérarchique (n’oublier pas c’est un arbre) 1.4. Dans @Component donner les 5 propriétés les plus utilisées selector : spécifier le tag (nom de la balise)associé ce composant standalone imports: L’ensemble des dépendances du composant template ou templateUrl styles ou styleUrls 1.5. Créer un composant ng generate component nomDuComposant (ng g c nomDuComposant) 1.6. les options de la commande generate (3) –inlineStyle = true|false (-s): Inclus les styles css dans le composant –inlineTemplate = true|false (-t): Inclus le template dans le composant –prefix=prefix (-p): Préfixe le sélecteur du composant. Par défaut c’est app 2. Les bindings interpolation property binding event binding 1 two-way binding ## 2.1. Property binding – Binding unidirectionnel – Permet de récupérer dans le DOM des propriétés du composant – La propriété liée au composant est interprétée avant d’être ajoutée au Template ## 2.2. les 2 possibilités pour la syntaxe de property binding – [property] = “valeur” exemple : [src] = “image” – bind-property = “valeur” exemple : bind-src = “image” ## 2.3. Event binding – Binding unidirectionnel – Permet d’interagir du DOM vers le composant à travers les événe- ments. ## 2.4. les 2 possibilités pour la syntaxe de event binding – (event) = “expression” exemple : (click) = “onClick()” – on-event = “expression” exemple : on-click = “onClick()” ## 2.5. Interpolation – Permet d’afficher une valeur du composant dans le template – Syntaxe : {{valeur}} exemple : {{nom}} ## 2.6. accéder à une propriété de style d’un élément – [style.property] = “valeur” exemple : [style.color] = “red” ## 2.7. Two-way binding – Binding bidirectionnel – Permet d’intéragir du DOM vers le composant et vice versa – se fait à travers la dierctive ngModel ## 2.8. utiliser la directive ngModel – syntaxe : [(ngModel)] = “valeur” exemple : [(ngModel)] = “nom” – il faut importer le module FormsModule dans le module de l’application 3. le nouveau flux de control (angular 17) @If @for @switch ## 3.1. @if, @else, @else if 2 – associé à une expression booléenne. Si cette expression estfausse (false) alors l’élément et son contenu sont retirés du DOM, oujamais ajoutés. – exemple: @if(isVisible){ Contenu1 } @else if (isHidden){ Contenu2 } … @else{ Contenu3 } ## 3.2. @for – permet de boucler sur un itérable etd’injecter les éléments dans le DOM – exemple: @for(item of items; track item.id){ {{item.title}} } ## 3.2.1. les informations fournies par @for (5) ∗ $index : l’index de l’élément courant (0, …) ∗ $odd : vrai si l’index est impair ∗ $even : vrai si l’index est pair ∗ $first : vrai si c’est le premier élément ∗ $last : vrai si c’est le dernier élément ## 3.2.2. track ∗ la fonction de tracking est devenue obligatoire ∗ La fonction de suivi créée via est utilisée pour permettre au mé- canisme de détection des changements de savoir quels éléments mettre à jour dans le DOM après les modifications de l’itérable d’entrée. ∗ La fonction de suivi indique à Angular comment identifier de- manière unique un élément de la liste ## 3.2.3. s’il n’ya rien d’unique dans les éléments du tableau ∗ utiliser l’index de l’élément comme track ∗ @for (episode of episodes; track $index) {…} ## 3.2.3. track peut être une fonction ∗ track peut prendre en paramètre une fonction à laquelle on pas- sel’index et l’élément actuel de l’itération ∗ @for (episode of episodes; track trackFn($index, episode)) {…} ∗ trackFn(index: number, item: Episode): number { return item.id; } 3 ## 3.2.4. gestion d’un itérable vide ∗ Afin de gérer le cas ou votre itérable est vide, nous avons le block @empty qui n’est activé que si l’itérable est vide ∗ @for (episode of episodes; track trackFn($index, episode)) {…} @empty { Il n’y a pas d’épisodes } ## 3.3. @switch, @case, @default – @switch(streamingService) { @case (‘Disney+’) { ‘Mandalorian’ } @case (‘AppleTV’) { ‘Ted Lasso’ } @default { ‘Peaky Blinders’ }} ## 3.4. migration automatique (angular 17.2) – ng g @angular/core:control-flow – fonctionnalité en developer preview (angular 17.2) 4. Change detection voir 14. optimisation.md 4 Les Signaux 1. Qu’est ce qu’un Signal ? agit comme une enveloppe (wrapper) autour d’une valeur, ayant lacapacité d’avertir les consommateurs lorsque la valeur change primitive réactive qui représente une valeur et qui nouspermet de – suivre ses changements au fil du temps. – modifier cette même valeur en notifiant tous ceux qui en dépendent. un concept utilisé dans plusieurs frameworks (Qwik, SolidJs, Vue, Knock- outJs) 2. les roles des signaux (3) définir l’état réactif de votre application avoir une identification précise de quels composants sont impactés par un changement simplifier le développement en donnant une alternativeplus simple que RxJS pour gérer certains cas de réactivité d’un façon plus simple. 3. les signaux et versions d’angular Le concept du Signal dans Angular est une fonctionnalité introduite en‘Developer Preview’ dans la version 16 de la bibliothèque @angular/core stable à partir de Angular 17. 4. pourquoi intégrer les signaux ? (3) Reactvity everywhere : Les signaux permettent de réagir aux changements d’état (State) n’importe où dans notre code et pas seulement au sein d’un composant. (ex: services, directives, pipes, etc.) Precision Updates : Les signaux boostent les performances de votre appli- cation en réduisant le travail que fait Angular pour garder le DOM à jour avec les valeurs des données Lightweight dependencies: Les signaux pèsent 2KB,n’ont pas besoin de chargerdes dépendances tierces et nereprésentent aucun coût dedémarrage lorsque votreapplication se charge. 5. les APIs proposées pour utiliser les signaux (3) signal computed effect 1 5.1. Créer un signal via l’api signal() La fonction signal permet d’initialiser une variable et d’informer Angular et son contexte à chaque fois que sa valeur change. Elle retourne un objet de type WritableSignal. Un signal doit avoir une valeur initiale. exemple: lastname: WritableSignal< string > = signal(‘sellaouti’); 5.2. récupérer la valeur d’un signal e l’appeler commeune fonction. exemple: lastname(); 5.3. les méthodes de modification d’un signal (2) set(value: T): void: – affecter une nouvelle valeur au signal. – exemple: lastname.set(‘sellaouti’); update(fn: (value: T) => T): void: – calculer une nouvelle valeur d’un signal en fonction de valeur précé- dente – exemple: lastname.update((lastname) => lastname + ‘sellaouti’); 5.4. computed() L’api computed() permet de créer un nouveau signal dont la valeur dépend d’autres signaux. Lorsqu’un signal est mis à jour, tous ses signaux dépendants seront alors automatiquement mis à jour. On note que computed() retourne un objet de type Signal et non Writa- bleSignal. exemple: fullname: Signal< string > = computed(() => ${firstname()} ${lastname()}); 5.4.1. c’est quoi Object.is ? – c’est une fonction utilisée par Angular pour identifier un signal qui a changé, et donc si on doit exécuté un computed, – 2 valeurs sont identiques par Object.is si: ∗ elles sont toutes les deux undefined ∗ elles sont toutes les deux null ∗ elles sont toutes les deux true ou toutes les deux false ∗ elles sont des chaînes de caractères de la même longueur et avec les mêmes caractères (dans le même ordre) ∗ elles sont toutes les deux le même objet (même référence) ∗ elles sont des nombres et 2 · sont toutes les deux égales à +0 · sont toutes les deux égales à -0 · sont toutes les deux égales à NaN 5.4.2. computed() comment ça marche ? Arbre de dépendance – L’arbre de dépendance est crée dynamiquement à chaque appel du computed. Il faut donc faire très attention dans la définition de vos computed lorsqu’il y a des traitement conditionnel. – L’arbre de dépendance est recalculé à chaque fois qu’un signal dépen- dant change mais elle est créée une seule fois quand le computed est créé. – exemple: multiplier: number = 0; derivedCounter = computed(() => { if (this.multiplier < 10) { return 0; } else { return this.counter() * this.multiplier; } }); 5.4.3. Exclure un signal de l’arbre de dépendence – Si vous voulez lire une valeur d’un signal dans un comptued mais sans l’intégrer dans l’arbre de dépendance, vous pouvez utiliser l’Api untracked. – fullname = computed(() => ${this.firstname()} ${untracked(this.lastname)}): Dans cet exemple le computed fullname ne sera mis à jour que si lesignal firstname change 5.4.4. modifier un signal dans un computed – La fonction computed doit être sans effets de bord, ce signifie qu’elle nedoit accéder qu’aux valeurs des signaux dépendants et éviter toute mise à jour. – Vous ne pouvez pas modifier un signal dans un computed, ceci provo- queraune erreur. 5.4.5. autres propriétés de computed – paresseux (lazy), ce qui signifie que computed n’est invoquée que lorsque quelqu’un s’intéresse (lit) sa valeur. Cela d’optimiser les per- formances en évitant les calculs inutiles. – Les computed sont automatiquement supprimés lorsque la référence du signal calculée devient hors de portée. Cela garantit que les ressources sont libérées et qu’aucune opération de nettoyage explicite n’est requise. Ceci est due à l’utilisation des weakRefrence avec les signaux 3 5.4.6. signals et writeable signals et readonly – Par défaut, tous les signaux créés par la fonction Signal sont de typeWritableSignal. Ainsi, n’importe quelle entité peut modifier sa valeur(via les méthodes set() et update()). – Si on désire interdire toute modification de la valeur d’un signal, Angular nous propose la méthode asReadOnly(). ∗ exemple: const lastname: Signal< string > = signal(‘aziz’).asReadOnly(); ∗ Les signaux créés par computed sont, par défaut, read only 5.5. effect() Dans certains cas d’utilisation, vous avez besoin d’être notifié par lechange- ment d’un signal pour effectuer un effet de bord, donc faire untraitement sans pour autant créer un nouveau signal ou en modifierd’autres exemples: – constructor(){effect(() => console.log(Hello ${fullname()})); } – private logFullName = effect(() => console.log(Hello ${fullname()})); 5.5.1. les caractéristiques des effects() – Les effets s’exécutent toujours au moins une fois lors de leur création – L’effet est semblable aux computed, les effets gardent une trace de leurs dépendances de manière dynamique et ne suivent que les sig- naux qui ont été lus lors de l’exécution la plus récente – Les effets s’exécutent toujours de manière asynchrone, pendant le processus de change detection. – Les effets seront exécutés le nombre minimum de fois. Si un effet dépend de plusieurs signaux et que plusieurs d’entre eux changent simultanément, une seule exécution de l’effet sera programmée. – l’API effect() est toujours en aperçu développeur (17.3). 5.5.2. ou définir un effect() – Un effect doit être définit dans un contexte d’injection: ∗ component, pipe, directive, constructeur de service ∗ injecter Injector et le passer en 2eme paramètre : effect(() => console.log(Hello ${fullname()}), injector); 5.5.3. exclure un signal de l’arbre de dépendance – untracked : effect(() =>{ untracked(() => { // Lire des signaux ici sans qu’ils soient suivis dans l’arbre des dépendances }); // lire des signaux ici qui seront suivis dans l’arbre des dépendances }) 4 6. Cycle de vie d’un composant (6) Angular Crée le composant il L’affiche il Crée ses fils il Les affiche il Ecoute le changement des propriétés il Le détruit avant de l’enlever du DOM ## 6.1. Comment ca marche réellement (6) 1. Angular lance l’application 2. Il crée les classes pour chaque composant, il lance donc le Construc- teur du component parent. 3. Il gère toutes les dépendances injectées au niveau du constructeur et les définit comme des paramètres 4. Il crée le nœud du Dom qui va héberger le composant 5. Il commence ensuite la création du composant fils et appelle son constructeur. Ici la propriété binded n’est pas prise en considération par Angular. 6. A ce moment Angular lance le processus de détection des change- ments. C’est ici qu’il mettra à jour le binding du parent et lancera l’appel à ngOnInit, suivi de la gestion du binding du fils puis l’appel de son ngOnInit. ## 6.2. Interfaces de gestion du cycle de vie d’un composant – disponibles dans la librairie cored’Angular – Chaque interface offre une seule méthode dont le nom suit la conven- tion suivante : ng + NomDeL’Interface (ex: ngOnInit) ## 6.3. lister ces interfaces et l’ordre d’appel (8) 0. constructor() 1. ngOnChanges(): – Appelée à chaque fois qu’une propriété d’entrée du composant change (input) – Prend en parametre un objet représentant les valeurs actuelles et précédentes disponibles pour ce composant 2. ngOnInit(): – Appelée une seule fois après la première détection des change- ments – initialise le composant après qu’Angular ait initialisé les pro- priétés du composant. 3. ngDoCheck(): Appelée après chaque fois que la détection des change- ments est lancée 5 4. ngAfterContentInit(): Appelée après que le contenu du composant a été initialisé 5. ngAfterContentChecked(): Appelée après chaque vérification du con- tenu du composant 6. ngAfterViewInit(): Appelée après que la vue du composant a été initialisée 7. ngAfterViewChecked(): Appelée après chaque vérification de la vue du composant 8. ngOnDestroy(): Appelée juste avant que le composant soit détruit ## 6.4. interactions pere fils (2) et fils pere (2) – propertyBinding + @Input – eventBinding + @Output 7. @Input ChildComponent { @Input() name: string; } 8. Signal Input La version 17.1 a vu Angular introduire le concept de Signal Input pour améliorerl’interaction entre les composants Les Signal Input permettent de lier les valeurs des composants parents. Cesvaleurs sont exposées à l’aide d’un signal et peuvent changer au cours du cycle devie de votre composant. exemple: counter = input(); au lieu de @Input() counter: number; 8.1. Caractéristiques des Signal Input (5) – lecture seule – peut être optionnel ou required : ∗ optional: counter = input(); ∗ required: counter = input.required(); – peut avoir une valeur par défaut: counter = input.required(0); – peut avoir une Alias en cas de conflit de nom: counter = input(0, { alias: ‘counterAlias’ }); – peut etre transformé: counter = input(0, { transform: (value) => value + 1 }); 9. @Output ChildComponent { @Output() nameChange = new EventEmitter(); in- crement() { this.nameChange.emit(‘newName’); } } 6 10. Signal Output L’API output() remplace directement le décorateur @Output() tradition- nel. Le décorateur @Output n’est pas obsolète Angular a donc ajouté output() comme nouvelle façon de définir les sorties des composants dans Angular, d’une manière plus sûre et mieux intégrée à RxJ quel’approche traditionnelle @Output et EventEmitter. exemple: counterChange = output(); au lieu de @Output() counter- Change = new EventEmitter(); 7 Les directives 1. Qu’est ce qu’une directive Une directive est une classe permettant d’attacher un comportement aux éléments du DOM. Elle est décorée avec l’annotation @Directive. Apparait par défaut dans un élément comme un tag: < app appHigh- light>Bonjour je teste une directive La command pour créer une directive est ng g d nomDirective 2. les 3 types de directives les composants: directives avec un template les directives structurelles: changent l’apparence du DOMen ajoutant et supprimant des éléments les directives d’attribut: permettent de changer l’apparenceou le comporte- ment d’un élément 3. les directives d’attribut ngStyle ngClass ## 3.1. ngStyle – Permet de changer le style d’un élément – Exemple: < div [ngStyle]=“{color: ‘blue’, ‘font-size’: sizeVari- able}”>Je suis en bleu – Elle utilise le property Binding ## 3.2. ngClass – Permet de changer la classe d’un élément – cohabite avec l’attribut class – prend en paramètre ∗ Une chaine (string) ∗ Un tableau (dans ce cas il faut ajouter les [ ] donc [ngClass]) ∗ Un objet (dans ce cas il faut ajouter les [ ] donc [ngClass]) – Elle utilise le property Binding – Exemple: < div [ngClass]=“{class1: isClass1, class2: isClass2}”>Je suis en bleu ## 3.3. customiser un attribut directive – utiliser un HostBinding sur la propriété que vous voulez binder ∗ Exemple: @HostBinding(‘style.backgroundColor’) background- Color: string; – Si on veut associer un événement à notre directive on utilise un HostListnerqu’on associe à un événement déclenchant une méthode. 1 ∗ Exemple : @HostListener(‘mouseenter’) mouseover(){this.bg =this.highlightColor;} – Afin d’utiliser le HostBinding et le HostListner il faut les importer du core d’angular – Tous les paramètres de la directive peuvent être mises en input puis récupérer à partir de la cible 4. les directives structurelles permet de modifier le DOM appliquées sur l’élément HOST. généralement précédées par le préfix *. ## 4.1. les directives les plus connues – *ngIf – *ngFor – [ngSwitch] ## 4.2. *ngIf – prend un booléen en paramètre. si vrai, l’element host est visible, sinon caché – Exemple: < div *ngIf=“isTrue”>Je suis visible ## 4.3. *ngFor – Permet de répéter un élément plusieurs fois dans le DOM – Prend en paramètre les entités à reproduire. – Fournit certaines valeurs : ∗ index ∗ first ∗ last ∗ odd ∗ even – Exemple: < div *ngFor=“let item of items; let i = index; let isOdd = odd”>Je suis un item 2 Les pipes 1. Qu’est ce qu’un pipe fonctionnalité qui permet de formater et de transformer vos données avant de les afficher dans vos Templates. Il existe des pipes offerts par Angular et prêt à l’emploi 2. Syntaxe {{ variable | nomDuPipe }} – {{ variable | nomDuPipe1 | nomDuPipe2 | nomDuPipe3 }} 3. Pipes disponibles par défaut uppercase lowercase titlecase currency date json percent … 4. Paramétrer un pipe {{ variable | nomDuPipe : param1 : param2 : param3 }} 5. Pipe personnalisé Un pipe personnalisé est une classe: – décorée avec le décorateur @Pipe – implémente l’interface PipeTransform et sa méthode transform qui prend en paramètre la valeur cible ainsi qu’un ensemble d’options et retourne la valeur transformée. – Le pipe doit être déclaré au niveau de votre module de la même manière qu’une directive ou un composant – Pout créer un pipe avec le cli : ng g p nomPipe 6. Exemple de pipe personnalisé @Pipe({ name: 'convertToSpaces' standalone: true }) export class ConvertToSpacesPipe implements PipeTransform { transform(value: string, character: string): string { 1 return value.replace(character, ' '); } } {{ product.productName | convertToSpaces: '-' }} 7. Pipe pure et impure Par défaut, les pipes sont purs. la méthode transform est appelée unique- ment si la valeur de l’entrée change. Une fonction est dite pure si elle : – Ne provoque pas de side effect. – Renvoie la même valeur pour les mêmes paramètres. Pour déclarer un pipe impur, il suffit de rajouter pure: false dans le déco- rateur @Pipe *. Memo Il existe une bibliothèque memo-decorator qui permet de mémoriserdes fonctions pures. Elle permet en l’utilisant de cacher les résultats des fonctions pures etde les réutiliser. Pour l’installer utiliser la commande npm i memo-decorator exemple d’utilisation pour une fonction pure : @Memo() function myFunc- tion() { // code } 2 les services et injection de dépendances 1. Qu’est ce qu’un service ? une classe qui permet d’exécuter un traitement Permet d’encapsuler des fonctionnalités redondantes permettant ainsi d’éviter la redondance de code. un médiateur entre la vue et la logique 2. Création d’un service ng generate service nomDuService; ng g s nomDuService @Injectable() export class FirstService { constructor() { } } 3. Injection de dépendances (DI) un patron de conception. Déléguer la tache de l’instanciation des dépendances à une entité tierce (injector) ## 3.1 etapes de l’injection de dépendances Déclarer la dépendance dans le provider du module ou du composant Passer la dépendance comme paramètre du constructeur de l’entité qui en a besoin ## 3.2. que peut on injecter ? Toute dépendance de votre classe, à savoir Des instances de classes, Des constantes ## 3.3. avantages de l’injection de dépendances Couplage lâche Facilement remplacer une implémentation d’une dépendance à des fins de test Prendre en charge plusieurs environnements d’exécution: développement, test, production Fournir de nouvelles versions d’un service à un tiers qui utilise votre service. ## 3.4. Comment ca fonctionne Afin de Lier cette dépendance au système d’injection dedépendance d’Angular nous devons répondre à deux questions: Comment Angular va créer la dépendance ? => Provider factory Quand est ce qu’Angular doit utiliser cette dépendance ? ## 3.5. Comment créer une dépendance => Provider factory une fonction simple qu’Angular peut appeler afin de créer une dépendance peut être créée implicitement par Angular en utilisant des conventions simples (cas le plus répondu) ou par vous même. pour toute dépendance de votre application, il existe une Provider Factory qui sait comment la créer et qui le fait. exemple: function todoServiceProviderFactory(): TodoService { return new TodoService(); } function todoServiceProviderFactory(http:HttpClient): TodoService { return new TodoService(http); } ## 3.6. Quand Angular doit utiliser cette dépendance ==> Tokens Comment Angular sait-il quoi injecter, et donc quelle Provider factory appeler pour créer quelle dépendance ? Un Token peut avoir plusieurs formes et il a pour rôle d’identifierune Provider Factory ## 3.6.1. Premiere forme : InjectionToken une instance de la class InjectionToken Son rôle est d’identifier le service dans le système d’injection dedépendance ## 3.6.1.1. création d’un token exemple: export const TODO_SERVICE_TOKEN = new InjectionToken< TodoService >(‘TODO_SERVICE_TOKEN’); ## 3.6.1.2. utilisation d’un token Maintenant que nous avons notre Provider Factory et notre Token, nous devons configurer Angular pour qu’ils les prennent enconsidération. Ceci sera fait à travers le Provider qui n’est qu’un objet de configuration. Il peut prend en paramètres 3 clés (pas que): provide: qui est notre Token useFactory: qui est notre Factory deps: un tableau des dépendances de votre factory exemple: { provide: TODO_SERVICE_TOKEN, useFactory: todoServiceProviderFactory, deps: [HttpClient] } ## 3.6.1.3. injection et utilisation du service On utilise @Inject au niveau du constructeur et on lui passe le Token du Provider Factory à injecter. exemple: constructor(@Inject(TODO_SERVICE_TOKEN) private todoService: TodoService) { } ## 3.6.2. les providers personnalisés: autres formes de tokens Comme nous l’avons présenté, le Token peut avoir plusieurs formes. Parmi elles, le nom de la classe. Le token peut être aussi une chaine de caractères, mais ceci est déconseillé afin d’éviter les collisions de noms. Le TOKEN doit être unique pour éviter toute collision, les Provider factory sont stockés dans une map, et si le provider est simple et qu’il a le même nom, la map ne contiendra que le dernier provider défini exemple: { provide: TodoService, useFactory: todoServiceProviderFactory, deps: [HttpClient] } constructor(@Inject(TodoService) private todoService: TodoService) { } ## 3.6.3. useClass au lieu de useFactory au lieu de spécifier laFonction du Provider Factory avec useFactory, vous pouvezutilisez la clé useClass En utilisant useClass, Angular saura que la valeur que noustransmettons est un constructeur valide, qu’Angular peutsimplement appeler en utilisant l’opérateur new. exemple: { provide: TodoService, useClass: TodoService, deps: [HttpClient] } constructor(@Inject(TodoService) private todoService: TodoService) { } ## 3.6.3.1. avantage de useClass: token déduit Angular essaiera de déduire le Token d’injection au moment de l’exécution en fonction de la valeur des annotations de type Typescript. avec les dépendances useClass, nous n’avonsmême plus besoin du décorateur Inject exemple: constructor(private todoService: TodoService) { } ## 3.6.3.2. avantages de useClass: injection dynamique des dépendances { provide: LoggerService, useClass: config.env === ‘development’ ? DevelopmentLoggerService : ProductionLoggerService, } ## 3.6.4. @Injectable un décorateur permettant de rendre une classe injectable: on peut y injecter des dépendances @Component, @Pipe, et @Directive sont des sous classes de @Injectable() => on peut y injecter des dépendances. Si vous n’aller injecter aucun service dans votre service, cetteannotation n’est plus nécessaire mais recommandée ## 3.6.4.1. avantage de @Injectable on n’a plus à spécifier les dépendances qui seront déterminé au niveau du constructeur par le Système d’Injection { provide: TodoService, useClass: TodoService, deps: [HttpClient] } => { provide: TodoService, useClass: TodoService } => { TodoService } ## 3.6.5. multi La plupart des dépendances de notre système correspondront à uneseule valeur, comme par exemple une classe. il y a des occasions où nous voulons avoir plusieursinstances pour le même provider. Pour ce faire, ajouter la clé multi et mettez la à true => Au lieu de recevoir une instance, vous recevrez un tableau exemple: { provide: TodoService, useClass: TodoService, multi: true } ## 3.6.6. fournir un provider pour toute l’application ajouter en deuxième paramètre de la fonction bootstrapApplication, un objet d’options. Cet objet contient une clé providers qui prend en paramètre un tableau de providers. exemple: bootstrapApplication(AppComponent, { providers: [TodoService] }); ## 3.6.7. récupérer les providers offerts par un module avec la fonction importProvidersFrom(moduleCible): const providers = importProvidersFrom(AppModule); A partir de la version 15, vous avez certaines fonctions spécifiques pour les modules les plus utilisés: provideHttpClient() provideRouter(APP_ROUTES) ## 3.7. Standalone Component @Component({ providers:[CvService] }) export class CvComponent implements OnInit { constructor(private monPremierService:CvService) {}} ## 3.8. la fonction inject (après Angular 14) permet d’injecter un injectable. Avant Angular 14 et à partir d’Angular 9, la fonction inject pouvait être utilisé uniquement dans la factory de l’InjectionToken ou dans le factory du @Injectable. A partir d’Angular 14, cette fonction n’est plus limitée aux factory. à utiliser dans vos composants, directive set pipes exemple: private myService = inject(MyService); au lieu de constructor(private myService: MyService) { } ## 3.8.1. avantages de la fonction inject le type safety avec le décorateur @Inject facilite aussi l’héritage en externalisant le dépendance du composant ## 3.9. Chargement automatique du service: providedIn A partir de Angular 6 vous pouvez ne plus utiliser le provider du module afin de charger votre service mais le faire directement au niveau du service à travers l’annotation @Injectable({providedIn:‘..’}) providedIn:‘root’: charger le service (singleton) dans toute l’application providedIn: ModuleName: charger le service dans un module particulier et l’importer providedIn: ‘platform’: injecteur de plateforme singleton spécial partagé par toutes les applications de la page. providedIn: ‘any’: (obsolète): Fournit : une instance unique dans chaque module chargé d’une manière lazy une seule instance partagée par tous les modules chargés en eager ## 3.9.1. Avantage de l’utilisation du providedIn Permettre le Tree-Shaking des services non utilisés : Si le service n’est jamais utilisé, son code ne sera entièrement retiré du build final. ## 3.10. Autres providers Dans certains cas d’utilisation, l’utilisation standard des providers ne convient pas, imaginer l’un des cas suivants : créer une instance personnalisée au lieu de laisser le container le faire pour vous. injecter une bibliothèque externe mocker une classe pour le teste injecter des instances différentes selon le contexte … ## 3.10.1. useValue exemple: { provide: ‘API_URL’, useValue: ‘http://localhost:3000’ } useValue est utile pour injecter Une valeur constante, Une bibliothèque externe Remplacer une implémentation réelle par un objet fictif ## 3.10.1.1. comment injecter si on utilise useValue Si vous injecter une classe, l’injection via le constructeur: constructor(private myService: MyService) { } Si vous injecter une valeur, l’injection via le décorateur @Inject: constructor(@Inject(‘API_URL’) private apiUrl: string) { } ## 3.10.2. useExisting Avec useExisting, vous créer un alias pour un provider déjà existant. exemple : { provide: ‘API_URL, useExisting: ’API_URL’ } useExisting est aussi utile lorsque nous voulons créer un provider basé sur un autre provider existant. Un autre cas d’utilisation est lorsque nous avons méthodes sur le service et que nous ne voulons en utiliser que quelques-unes. Cela aide à réduire la taille del’interface exemple: class CarService{ getWeight(): number {…} getName(): string {…} getWidth(): number {…} getHeight(): number {…} } abstract class CarSizeService { abstract getWidth(): number; abstract getHeight(): number; } { provide: CarSizeService, useExisting: CarService } ## 3.11. DI Hiérarchique Le système d’injection de dépendance d’Angular est hiérarchique. Il possède deux hiérarchie d’injecteur: Une hiérarchie d’injecteur niveau composant (element/Node Injector Hierarchy): plus prioritaire Une hiérarchie d’injecteur niveau module ## 3.11.1. Hiérarchie d’injecteur niveau composant Un arbre d’injecteur est crée. Cet arbre est // à l’arbre de composant. L’algorithme suivant permet la détection de l’injecteur adéquat : Injection d’un service dans un composant Vérification de l’injecteur à ce niveau Si l’injecteur contient le service, il l’injecte Sinon il remonte dans l’arbre des composants jusqu’à trouver l’injecteur adéquat ou arriver à la racine ## 3.11.2. Hiérarchie d’injecteur niveau module Si Angular ne trouve pas de provider au niveau de la hiérarchie des composants, il voit dans la hiérarchie de module Le ModuleInjector peut être configuré de deux manières en utilisant : @Injectable pour référencer un NgModule, ou ’root Le tableau des providers dans @NgModule() Le moduleInjector identifie les providers disponibles en effectuant un aplatissement de tous les tableaux de fournisseurs qui peuvent être atteints en suivant les NgModule.imports de manière récursive. 6. Routing 1. Qu’est-ce que le routing ? Séparer différentes fonctionnalités du système Maintenir l’état de l’application Ajouter des règles de protection rafrachir la page, favoris, partage 2. Création d’un système de Routing provider vos routes avec la fonction provideRouter au niveau de la fonc- tion bootstrapApplication export const routes: Routes = [{ path: "", component: HomeComponent }]; export const appConfig: ApplicationConfig = { providers: [provideRouter(routes)], }; bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err) ); 3. Préparer l’emplacement d’affichage des vues correspon- dantes aux routes Router outlet est une directive qui permet de spécifier l’endroit ou la vue va être chargée. exemple: et importer RouterOutlet 4. Syntaxe minimaliste d’une route Une route est un objet. Les deux propriétés essentielles sont path et component. – path permet de spécifier l’URI. Cette url ne doit pas commencer par un / – component permet de spécifier le composant à exécuter. 5. Déclencher une route routerLink L’utilisation de < a href > va déclencher le chargement de la page ce qui est inconcevable pour une SPA utilisation de la directive routerLink quiliera la directive à la route que nous souhaitons déclencher sans recharger la page Home routerLinkActive="active" va associer la classe active à l’uri cible ainisi qu’à tous ces ses ancetres 1 [routerLinkActiveOptions]: {exact: true} pour que la classe active ne soit associée qu’à l’uri cible 6. Déclencher une route à partir du composant on utilise le service Router et sa méthode navigate qui prend un tableau contenant la description de la route. constructor(private router: Router) {} this.router.navigate(['home']) Il permet de : naviguer vers des routes définies : router.navigate([‘home’,id]) ou navigateByUrl('/home') s’abonner aux événements de navigation : la propriété events: router.events.subscribe((event: Event) => {}); récupérer des informations sur la route active: la propriété url ou routerState : router.routerState.snapshot.url; 7. Les paramètres d’une route pour spécifier à notre router qu’un segment d’une route est un paramètre: ajouter ‘:’ devant le nom de ce segment: /home/:id 7.1 ActivatedRoute – ActivatedRoute: un service qui gère la route active: récupérer les paramètres d’une route constructor(private route: ActivatedRoute) {} – L’objet ActivatedRoute contient des informations sur la route actuellement activée utilisé pour : ∗ accéder et souscrire aux informations de route · params: Retourne un observable des paramètres de route actuels this.route.params.subscribe((params) => { console.log(params["id"]); }); · queryParams: Retourne un observable des paramètres de re- quête actuels. this.route.queryParams.subscribe((params) => { console.log(params["id"]); }); 2 · data: Retourne un observable des données de route associées à laroute actuelle this.route.data.subscribe((data) => { console.log(data["title"]); }); · snapshot: Retourne un instantané de la route actuelle. this.route.snapshot(statique); · url: Retourne un observable de l’URL de la route actuelle this.route.url.subscribe((url) => { console.log(url); }); · parent: Retourne l’instance de ActivatedRoute de la route parente. this.route.parent; · firstChild: Retourne l’instance de ActivatedRoute du pre- mier enfantde la route actuelle. this.route.firstChild; · children: Retourne un tableau d’instances de ActivatedRoute desenfants de la route actuelle. this.route.children; · paramMap: Retourne un observable qui contient les paramètres de route sous forme de map this.route.paramMap.subscribe((params) => { console.log(params.get("id")); }); · queryParamMap: Retourne un observable qui contient lesparamètres de requête sous forme de map. this.route.queryParamMap.subscribe((params) => { console.log(params.get("id")); }); 7.2 snapshot – La propriété snapshot de l’objet ActivatedRoute contient un instan- tané del’état de la route actuelle. – Elle est généralement utilisée pour accéder aux informations de routedans un composant lors de son initialisation – l’instantané de la route ne change pas lorsque la route change. Il représente un état figé de la route – Si vous rappeler votre composant avec la même route mais en changeant les paramètres, Angular ne recrée par une nouvelle instance du composant, ceci implique que ni le constructeur, ni le ngOnInit ne soient appelés, l’objet snapshot ne sera pas mis à jour. 3 7.2.1. les propriétés de snapshot – url: l’URL de la route actuelle sous forme de tableau de segments d’URL – params: Retourne un objet qui contient les paramètres de route actuels: this.route.snapshot.params['id'] – paramMap: Retourne un map qui contient les paramètres de route actuels: this.route.snapshot.paramMap.get('id') – queryParams: Retourne un objet qui contient les paramètres de re- quête actuels. – fragment: Retourne la partie de l’URL après le symbole “#” (“/about#team”, fragment retourne “team”) – data: Retourne les données de route associées à la route actuelle – outlet: Retourne le nom de l’outlet de route actuel. – component: Retourne le composant de route actuel – routeConfig: Retourne la configuration de la route actuelle. 7.3 Passer le paramètre à travers le tableau de router- Link this.router.navigate(["/about", this.id]); 7.4 Les queryParameters – Les queryParameters sont les paramètres envoyé à travers une requête GET. Identifié avec le ?. – Afin d’insérer un queryParameters on dispose de deux méthodes: ∗ On ajoute dans la méthode navigate du Router un second paramètre de type objet this.router.navigate(["/home"], { queryParams: { id: 1 } }); ∗ La deuxième méthode est en l’intégrant à notre routerLink Home 8. Route Fils L’idée est de préfixer nos routes: le préfixe dans la propriété path, et la propriété children contiendra tableau des routes const routes: Routes = [ { path: "home", children: [{ path: "about", component: AboutComponent }] }, ]; on peut ajouter component apres path pour afficher un composant parent par défaut const routes: Routes = [ { path: "home", 4 component: HomeComponent, children: [{ path: "", component: AboutComponent }], }, ]; 11. Routing avancé 11.1. Master Detail Implementation un modèled’architecture utilisé pour afficher des données dans une inter- face utilisateur avoir une vue “maître” qui affiche une listed’éléments, et une vue “détail” qui affiche les détails d’unélément sélectionné dans la vue “maître”. Cela permet aux utilisateurs de naviguer facilement entre lesdifférents élé- ments et de voir les détails correspondants sans avoir àcharger une nouvelle page. Afin de l’implémenter, il faut utiliser les routes fils 11.2. Route Resolver un mécanisme qui permet derésoudre des données avant qu’une route ne soit chargée permet de charger les données nécessaires pour afficher unevue spécifique en utilisant un service qui est lié à la route utilisé lorsque vous avez besoin de charger des données avant d’afficher une vue. utilisé pour éviter les erreurs de chargement de vue : les données néces- saires sont chargées avant de naviguer vers une route soit une fonction (à partir d’Angular 15) soit une classe qui implémente l’interface Resolve et qui est générique. Vous devez donc spécifier ce que le Resolver va fournircomme données z implémenter la fonction Resolve qui devra retournerun objet de T ou une Promise ou un Observable. Elle prend en paramètre un objet de type ActivatedRouteSnapshotqui vous permet de récupérer les paramètre de votre route à travers le paramètre paramMap et sa méthode get. // function resolver export const firstResolver: ResolverFn = ( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ) => { return !!route.paramMap.get("id"); }; // class resolver @Injectable({ 5 providedIn: "root", }) export class CvResolver implements Resolve { resolve( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | Cv { const cvId = route.paramMap.get("id"); return this.cvService.getCvById(cvId); } } afin de passez le résultat de votre resolver à votre route, ajoutez une propriété resolve à votre route dans votre fichierde routing et passez lui un object avec comme clé le nom que vous voulez donner à votre propriété et comme valeur le resolver const routes: Routes = [{path: 'cv/:id', component: CvComponent, resolve: {cv: CvResolver}}] Maintenant, dans votre composant, injecter le serviceActivatedRoute. Pour accéder à la valeur de retour du resolver (si c’est une Promiseou un Observable, il attendra la valeur émise), accéder à la propriétésnapshot qui contient une propriété data qui contiendra le champ. constructor(private route: ActivatedRoute) {} const cv = this.route.snapshot.data['cv']; Si vous voulez un accès dynamique, utilisez l’Observable data del’ActivatedRoute. this.route.data.subscribe((data) => { this.cv = data['cv']; }); 11.3. Route Provider A partir d’Angular 14 A partir d’Angular 14, la clé provider a été introduite dans l’objetroute. Ceci permettra de provider des provider pour la route et ses enfants const routes: Routes = [{path: 'cv/:id', component: CvComponent, resolve: {cv: CvResolver}, Ceci va créer un nouvel Injector qui sera appelé juste après l’element In- jector 11.4. Cycle de vie du routeur Pour chaque navigation, une série d’étapes a lieu avant que le rou- teurn’affiche les nouveaux composants à l’écran. C’est ce qu’on appelle le cycle devie de navigation du routeur 6 Le résultat d’une navigation réussie est que les nouveaux composants serontrendus à l’aide de , et une arborescence de structures de donnéesAc- tivatedRoute sera créée en tant qu’enregistrement interrogeable de lanav- igation. Durant ce cycle de vie, des events vous permettent de vous y greffer (s’abonner) Vous pouvez activer la visualisation de ces events en développement via l’option enableTracing ou en utilisant la fonction withDebugTracing avec la fonction provideRouter RouterModule.forRoot(routes, {enableTracing: true}) // ou provideRouter(routes, withDebugTracing()) 11.5. Router Events Le routeur d’Angular déclenche plusieurs événements vous permettantde vous y greffer et d’y ajouter un comportement métier Afin de vous y greffer, injecter le Service Router. Il contient un Observable events qui retourne l’event déclenché Inscrivez vous à l’observable et selon le type de l’event voulu, exécutezle traitement. constructor(private router: Router) {} this.router.events.subscribe((event: Event) => { if (event instanceof NavigationStart) { console.log('NavigationStart', event); }else if (event instanceof NavigationEnd) { console.log('NavigationEnd', event); } }); 11.5.1. Les types d’events (13: Navigation:4, Routes:1, Guards:2, Resolve:2, Activation:2, ChildActivation:2) NavigationStart: déclenché lorsqu’une nouvelle navigation est démarrée. Ilpeut être utilisé pour déclencher un loader RoutesRecognized: déclenché lorsque le routeur a reconnu les routescor- respondant à l’URL demandée NavigationEnd: déclenché lorsque la navigation a abouti avec succès. Peutêtre utilisé pour arrêter le loader ou envoyer une notificationà votre outil Analytics. NavigationCancel: déclenché lorsque la navigation est annulée. Peut êtreutilisé pour arrêter le loader NavigationError: déclenché é lorsqu’une erreur survient lors de la naviga- tion.Peut être utilisé pour arrêter le loader ou envoyer des log au serveurs. 7 GuardsCheckStart: déclenché lorsque le routeur démarre lavérification des gardes de route. ChildActivationStart: déclenché lorsque l’activation d’uncomposant en- fant démarre. ActivationStart: déclenché lorsque l’activation d’un composant démarre. GuardsCheckEnd: déclenché lorsque le routeur a terminé lavérification des gardes de route ResolveStart: déclenché lorsque le routeur démarre la résolution desdon- nées pour une route. ResolveEnd: déclenché lorsque le routeur a terminé la résolution desdon- nées pour une route ActivationEnd: déclenché lorsque l’activation d’un composant estter- minée. ChildActivationEnd: déclenché lorsque l’activation d’uncomposant en- fant est terminée. 11.6. Ajouter des données à votre Route ajouter des données à votre route àtravers le resolver: sert lorsque les données sont dynamique, à travers le data de la route: sert lorsque les données sont statiques – Ceci peut être utile lorsque le composant peut être utilisé dans deux- contextes différents navigationExtras: permet de passer des données à travers la navigation 11.6.2. route data export const routes: Routes = [ { path: 'context1', component: MyComponent, data: { title: 'Contexte 1', description: 'Page pour le premier contexte' }, }, { path: 'context2', component: MyComponent, data: { title: 'Contexte 2', description: 'Page pour le second contexte' }, }, ]; 11.6.3. navigationExtras A partir de la version 7.2, on peut passer des info via le routeur d’une façon dynamique en utilisant les navigationExtras 8 Une première façon de passer ces informations est à travers laméthode navigate de votre router. le second paramètre de cette méthode est un objet de typeNavigationEx- tras. Cet objet a une propriété state dans laquelle vouspouvez passer les informations que vous voulez this.router.navigate(['cv'], {state: {id: 1}}); Afin de récupérer cet objet dans le composant cible, vous devezutiliser appelez sa méthode getCurrentNavigation() const id = this.router.getCurrentNavigation()?.extras.state?.id; 9 7. Form 1. Approches de gestion des formulaires Template-driven forms Reactive forms (à voir plus tard) 2. Template-driven forms Importer le module FormsModule dans app.module.ts Angular détecte automatiquement un objet form à l’aide de la balise FORM. Cependant, il nedétecte aucun des éléments (inputs). Spécifier à Angular quel sont les éléments (contrôles) à gérer. – Pour chaque élément ajouter la directive angular ngModel. – Identifier l’élément avec un nom permettant de le détecter et de l’identifier dans le composant Associer l’objet représentant le formulaire à une variable et la passer à votre fonction en utilisant le référencement interne # et la directive ng- Form < form #f="ngForm" (ngSubmit)="onSubmit(f)"> < input type="text" name="username" ngModel> < input type="password" name="password" ngModel> < button type="submit">Submit export class TmeplateDrivenComponent{ onSubmit(formulaire: NgForm){console.log(formul 3. Validation Afin de valider les propriétés des différents contrôles, Angular utilise des attributs et des directives – required – email La propriété valid de ngForm permet de vérifier si le formulaire est valid en se basant sur les validateurs qu’ils contient 4. NgForm En détectant le formulaire, Angular décore les différents éléments du for- mulaire avec des classes qui informe sur leur état : – dirty: le fait que l’une des propriétés du formulaire a été modifié ou non – Valid : informe si le formulaire est valide ou non – untouched: informe si le formulaire est touché ou non: si l’utilisateur a cliqué sur un élément du formulaire ou non 1 – pristine: informe si le formulaire n’a pas été touché 5. Accéder aux propriétés d’un champ (contrôle) du for- mulaire Pour accéder à l’objet form et ces propriétés nous avons utilisé #notre- Form=«ngForm » Pour les champs du formulaire c’est la même chose mais au lieu dungForm c’est un ngMdoel #notreChamp=« ngModel » < input type="text" name="username" ngModel #username="ngModel"> 6. Associer des valeurs par défaut Pour associer des valeurs par défaut aux champs d’un formulaireassocié à Angular il faut le faire à partir du composant. Au lieu d’avoir juste la primitive ngModel associée au contrôle d’unélément on ajoute le property binding avec [ngModel] < input type="text" name="username" [ngModel]="defaultValue"> 7. ngModelChange et ngModelOptions A chaque changement dans vos input, un événementngModelChange est déclenché < input type="text" name="username" [ngModel]="defaultValue" (ngModelChange)="onUsern Vous pouvez aussi manipuler quand cet événement est déclenché àtravers le binding de la directive ngModelOptions. < input type="text" name="username" [ngModel]="defaultValue" [ngModelOptions]="{updat Elle prend en paramètre un objet contenant les propriétés suivantes : – updateOn : Définit l’événement sur lequel la valeur decontrôle du formulaire et la validité sont mises à jous: ∗ change: la valeur est mise à jour à chaque changement (par dé- faut) ∗ blur: la valeur est mise à jour lorsque l’élément perd le focus ∗ submit: la valeur est mise à jour lorsque le formulaire est soumis – name : une alternative à la définition de l’attribut namesurl’élément de contrôle de formulaire. – standalone : ∗ lorsqu’il est défini sur true, le ngModel ne s’enregistrera pas avec son formulaire parent. ∗ La valeur par défaut est false. ∗ Si aucun formulaire parent n’existe, cette option n’a aucuneffet 2 8. template driven forms drawbacks Difficilement maintenable quand le formulaire grandit Au fur et à mesure que nous ajoutons de plus en plus de balises de val- idation à unchamp ou lorsque nous commençons à ajouter des valida- tions inter-champscomplexes, la lisibilité et la maintenabilité du formulaire diminuent 9. template driven forms advantages la simplicité, et c’est probablement suffisant pour créer des formulaires de petite à moyenne taille C’est aussi similaire à ce qui a été fait dans AngularJs par ng-model, donc ce modèle sera familier à beaucoup de développeurs 10. Grouping form Afin de grouper l’ensemble des contrôles (propriétés/champs) d’un formu- laire: utiliser la technique du grouping form controls ajouter la directive ngModelGroup dans la div qui englobe les propriétés à grouper Afin d’accéder à cet objet vous pouvez le référence localement enutilisant le mot clé ngModelGroup < div ngModelGroup="user" #user="ngModelGroup"> < input type="text" name="username" ngModel> < input type="password" name="password" ngModel> export class TmeplateDrivenComponent{ onSubmit(formulaire: NgForm){console.log(formulaire.value.user);} } 12. Reactive Form une deuxième méthode de gérer vosformulaires avec Angular, générés pro- grammatiquement dans la partie TS. Ceci permet d’alléger le template des validateurs. D’autre part ca permet d’avoir un formulaire testable Finalement ceci permet de plus facilement générer des Validateurs person- nalisés. 3 1. créer un formulaire 1.1. new FormGroup ajouter le module ReactiveFormModule + créerun objet FormGroup Un FormGroup prend en paramètre un objet décrivant le formulaire. – Chaque champ de l’objet a comme première propriété le nom et comme valeur un objet définissant les champs associés à ce formu- laire. Ce sont les FormControl. – Chaque FormControl définit un champ du formulaire. constructor( controls: TControl, // c'est un objet qui contient la valeur initiale du champ validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] |null ); export declare interface AbstractControlOptions { validators?: ValidatorFn | ValidatorFn[] | null; asyncValidators?: AsyncValidatorFn | AsyncValidatorFn[] | null; updateOn?: 'change' | 'blur' | 'submit'; } – Exemple: this.form = new FormGroup({ name: new FormControl(null), firstName: new FormControl("Aziz"), age: new FormControl(0, {nonNullabe: true, validators: Validators.required, updateO }); 1.2. formBuilder.group Vous pouvez aussi passer par le service FormBuilder et sa méthode group. prend en paramètre un objet avec comme clé le nomduformControl et comme valeur un tableau avec comme premièrepropriété la valeur initiale du champ moins verbeux et plus simple à gérer pour les grands formulaires. constructor(private fb: FormBuilder){} this.form = this.fb.group({ name: [null], firstName: ["Aziz"], age: [0, {nonNullabe: true, validators: Validators.required, updateOn: 'blur'}], }); 2. Associer le FormGroup à votre form ajouter la directive formGroup (qui setrouve dans le module ReactiveFormsModule) au niveau de la balise form et la binder à votre objet de type FormGroup au niveau devotre fichier TS 4 utiliser ladirective formControlName et associer le à l’identifiantduFormControl Submit 3. Récupérer les FormControl dans le HTML utiliser la méthode get de votre form group et passer lui l’identifiantduFormControl à récupérer Submit 4. Récupérer les erreurs du FormControl dans le HTML Afin de récupérer les erreurs de votre control dans le HTML,utiliser l’attribut errors 10 7.2. méthodes utiles disponibles dans l’API FormArray (8) controls: tableau contenant tous les FormControl qui font partie du FormArray (AbstractControl[]) length: longueur totale du FormArray (number) at(index: number): retourne le FormControl à une position donnée (Ab- stractControl) push(control: AbstractControl): ajoute un FormControl à la fin du FormArray (void) insert(index: number, control: AbstractControl): insère un Form- Control à une position donnée (void) removeAt(index: number): supprime un FormControl à une position donnée (void) getRawValue(): obtient les valeurs de tous les contrôles de formulaire via control.value de chacun (any[]) setValue(value: any[], options: {onlySelf?: boolean, emitEvent?: boolean}): définit les valeurs de tous les contrôles 7.3. Ajouter un FormControl à un FormArray Afin d’ajouter un élément dynamiquement, vous pouvez utiliser l’APIFormArray et sa méthode push. Préparer l’élément à ajouter et pusher le dans votre Array addSkill(){ this.skills.push(this.formBuilder.group({name: [null, Validators.required]})); } 8. avantages du reactive form (4) Les Reactives Forms sont beaucoup plus propres, et se concentrent uniquement sur la logique de présentation et validation Toutes les règles de validation métier ont été déplacées vers la classedu composant. Ceci facilite les tests unitaires. La définition dynamique du formulaire devient plus facile avec lesFor- mArray. Il est beaucoup plus facile de créer un validateur personnalisé: il suffit de définir une fonction et de se connecter à notre configuration; Pour l’approche basée Template on doit passer par une directive 11 La programmation asynchrone 1. les promesses des objets qui représentent une complétion ou l’échec d’une opération asynchrone 1.1. le fonctionnement des promesses On crée une promesse La promesse va toujours retourner deux résultats : – resolve en cas de succès – reject en cas d’erreur Vous devrez donc gérer les deux cas afin de créer votre traitement exemple: 1.2. Créer une promesse const promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('success'); }, 2000); }); 1.3. Consommer une promesse promise.then((result) => { console.log(result); }).catch((error) => { console.log(error); }); 2. La programmation réactive Nouvelle manière d’appréhender les appels asynchrones: Programmation avec des flux de données asynchrones Programmation réactive = Flux de données (observable) + écouteurs d’événements(observer). 2.1. le pattern « Observer » permet à un objet de garder la trace d’autres objets, intéressés par l’état de ce dernier. Il définit une relation entre objets de type un-à-plusieurs. Lorsque l’état de cet objet change, il notifie ces observateurs 1 2.2. Promesse Vs Observable (5) Promesse: gère un seul evenement ; Observable: gère un flux d’événements Promesse: non annulable ; Observable: annulable: c’est à dire que vous pouvez vous désabonner à tout moment Promesse: traitement immédiat; Observable: lazy: le traitement n’est déclenché qu’à la premiere utilisation : subscribe Promesse: 2 méthodes uniquement: then et catch; Observable: plusieurs méthodes: map, reduce, merge, filter, … Observable: opérateurs tels que retry et replay 2.3. création d’un observable import { Observable } from 'rxjs'; const observable = new Observable((observer) => { observer.next('hello'); observer.next('world'); observer.complete(); }); observable.subscribe((value) => { console.log(value); }); 2.4. Hot Vs Cold Observable Les Cold Observables commencent à émettre des valeurs uniquement quand on s’y inscrit. Les Hot observables émettent toujours. Les Cold Observables diffusent un flux par inscrit: unicast. Chaque nou- velle inscription crée un nouveau contexte d’exécution Les Hot observables, sont multicast, le même flux est partagé par tous les inscrits. Dans les Cold Observables, la source de données est à l’intérieur de l’observable Dans les HotObservables, la source de données est à l’extérieur de l’observable 2.5. asyncPipe un pipe qui permet d’afficher directement unobservable: {{observable | async}} s’inscrit automatiquement à l’observable et affiche ledernier résultat en- voyé Quand le composant est détruit l’asyncPipe se désinscritautomatiquement de l’observable. 2 2.6. Les operateurs de l’observable Les opérateurs sont des fonctions. Il y a deux types d’opérateurs : – Les opérateurs de création, elles permettent de créer un Observable: from, of, timer, fromEvent – Un opérateur pipeable: une fonction qui prend un observable comme entrée et renvoie un autre observable. C’est une opération pure, le précédent Observable reste inchangé: observable.pipe(map, filter, take, throttleTime, debounceTime) #### 2.6.1. exemples d’opérateurs – map: transforme les valeurs émises par un observable: observ- able.pipe(map((value) => value * 2)) – filter: filtre les valeurs émises par un observable: observ- able.pipe(filter((value) => value > 2)) 2.7. Les subjects Un subject est un type particulier d’observable. En effet Unsubjectest en même temps un observable et un observer, il possède donc les méthodes next, error et complete (comme un observer) Pour broadcaster une nouvelle valeur, il suffit d’appeler la méthodenext, et elle sera diffusé aux Observateurs enregistrés 3 Http 1. Installation de HTTP Afin d’utiliser le service HttpClient, vous devez le configurez enutilisant la méthode provideHttpClient, que la plupart desapplications incluent dans la clé providers de votre app.config.ts export const appConfig: ApplicationConfig = { providers: [provideHttpClient()] }; http = inject(HttpClient); ou constructor(private http: HttpClient) {} 2. Configuration du HttpClient Par défaut, HttpClient utilise l’API XMLHttpRequest pour effectuer des requêtes. La fonctionnalité withFetch permet au client d’utiliser l’API de récupéra- tion à la place fetch est une API plus moderne et est disponible dans quelquesenviron- nements où XMLHttpRequest n’est pas pris en charge. Il présente quelques limitations, telles que le fait de ne pas pro- duired’événements de progression du téléchargement. export const appConfig: ApplicationConfig = { providers: [provideHttpClient(withFetch 3. Interagir avec une API Get Request Afin d’exécuter une requête get le module http nous offre une méthode get qui retourne un observable Cet observable a un objet contenant 3 callback function comme paramètres: – next: en cas de réponse – error: en cas d’erreur – complete: en cas de fin du flux de réponse this.http.get(API_URL).subscribe({ next: (data) => console.log(data), error: (error) => console.error(error), complete: () => console.log('complete') }); 4. Interagir avec une API POST Request Afin d’exécuter une requête post le module http nous offre une méthode post qui retourne un observable (next, error, complete) Diffère de la méthode get avec un attribut supplémentaire : body 1 this.http.post(API_URL, {name: 'John'}).subscribe({ next: (data) => console.log(data), error: (error) => console.error(error), complete: () => console.log('complete') }); 5. Les headers Afin d’ajouter des headers à vos requêtes, le HttpClient vous offre la classe immutable HttpHeaders (read-only) – set(clé,valeur) permet d’ajouter des headers. Elle écrase les anciennes valeurs. – append(clé,valeur) concatène de nouveaux headers toutes les méthodes de modification retourne un HttpHeaders permettant un chainage d’appel: – headers = new HttpHeaders().set(‘Content-Type’, ‘applica- tion/json’).set(‘Authorization’, ‘Bearer token’); – this.http.post(API_URL, {name: ‘John’}, {headers}).subscribe({}); 6. Les paramètres Afin d’ajouter des paramètres à vos requêtes, le HttpClient vous offre la classe immutable HttpParams – set(clé,valeur) permet d’ajouter des paramètres. Elle écrase les anci- ennes valeurs. – append(clé,valeur) concatène de nouveaux paramètres toutes les méthodes de modification retourne un HttpParams permettant un chainage d’appel: – params = new HttpParams().set(‘name’, ‘John’).set(‘age’, ‘25’); – this.http.get(API_URL, {params}).subscribe({}); 7. ajouter le token dans la requete dans les params: HttpParams – const params = new HttpParams().set(‘access_token’, localStor- age.getItem(‘token’)); – return this.http.post(this.apiUrl, personne, {params}); dans les headers: HttpHeaders – const headers = new HttpHeaders() – headers.append(‘Authorization’, ‘Bearer’ + localStorage.getItem(‘token’)); – return this.http.post(this.apiUrl, personne, {headers}); 8. Securiser vos routes Angular a pris en considération ce cas en fournissant un mécanisme via l’utilisation des Guard 2 8.1. Guard Ce sont des classes (jusqu’à Angular 14) ou des fonctions (à partir d’Angular 14) qui permettent de gérer l’accès à vos routes Un guard informe sur la validité ou non de la continuation du process de navigation en retournant un booléen, une promesse d’un booléen ou un observable d’un booléen A partir d’Angular 14 (utiliser inject dans tous les contexte d’injection), Angular préconise les fonctionnals Guards 8.2. exemple de guard export const myGuard: CanActivateFn = (route, state) => { return true; }; 8.3. types de guards (3) Le routeur supporte plusieurs types de guards, par exemple : – CanActivate: permettre ou non l’accès à une route: {path: ‘home’, component: HomeComponent, canActivate: [myGuard]} – CanActivateChild: permettre ou non l’accès à une route fille – CanDeactivate: permettre ou non de quitter une route ∗ utilisé pour s’assurer de ne pas quitter une page et il y a des données non enregistrées ou un upload en cours ∗ ne s’applique pas aux ‘children Routes’ ∗ fortement couplé au composant export class FormComponent implements CanDeactivate { canDeactivate(component: FormComponent, currentRoute: ActivatedRouteSnaps RouterStateSnapshot, nextState?: RouterStateSnapshot ): Observable< boolean> | Promise< boolean> | boolean { return confirm('Are you sure you want to leave?');} } 9. Les intercepteurs Un intercepteur (fournit par le client HTTP) nous permet d’intercepter une requête à l’entrée et à la sortie de l’application. Un intercepteur est soit une fonction ( angular 17 ) ou une classe qui implémente l’interface HttpInterceptor et intercept() 9.1. syntaxe des 2 types d’intercepteurs export const newInterceptor: HttpInterceptorFn = (req, next) => { return next(req); }; export class MyInterceptor implements HttpInterceptor { intercept(req: HttpRequest, next: HttpHandler): Observable { 3 return next.handle(req); } } 9.2. injection des intercepteurs Un intercepteur est injecté au niveau du provider: – intercepteur fonctionnel : la fonction withInterceptors qui est une des option de provideHttpClient: ∗ export const appConfig: ApplicationConfig = { providers: [provideHttpClient(withInterceptors([newInterceptor]))] }; – intercepteur classe: passez par la définition du provider puis appeler la méthode ∗ export const appConfig: ApplicationConfig = { providers: [ provideHttpClient( withInterceptorsFromDi()), { provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi: true } ]}; 9.3. Cloner une requête Les instances HttpRequest et HttpResponse sont immuables: les intercep- teurs ne peuvent pas les modifier directement. Solution : cloner par la méthode clone() et en spécifiant quelles propriétés doivent être mutées dans la nouvelle instance. intercept(req: HttpRequest, next: HttpHandler): Observable { const clonedReq = req.clone({ headers: new HttpHeaders().set('Authorization', 'Be return next.handle(clonedReq); } 9.4. intercepter la réponse Afin d’intercepter les réponses ou les erreurs, il faut récupérer laréponse et vérifier s’il y a une erreur intercept(req: HttpRequest, next: HttpHandler): Observable { return next.handle(req).pipe( tap((event: HttpEvent) => { if (event instanceof HttpResponse) { console.log('Response: ', event); } }), catchError((error: HttpErrorResponse) => { console.error('Error: ', error); return throwError(error); }) ); 4 } 5 10. RxJs 1. Les operateurs (2) opérateurs de création opérations de création de jointure La principale différence entre eux réside dans le fait que les opérateursde création de jointure créent des observables à partir d’autresobservables, alors que les opérateurs de création créent desobservables à partir d’objets qui diffèrent des observables. 1.1. Les opérateurs de création from of timer fromEvent 1.1.1. from convertit des types d’objets JavaScript (un tableau, une promesse ou un objet itérable) en une séquence observable de valeurs. from([10, 20, 30]).subscribe( next: value => console.log(value), error: err => console.error(err), complete: () => console.log('done') ); émet également une chaîne sous forme de séquence de caractères: from('hello') 1.1.2. of convertir un argument en un observable. ne fait aucun aplatissement ou conversion et émet chaque argument sous le même type qu’il reçoit couramment utilisé pour renvoyer une valeur là où un observable est at- tendu ou de démarrer une chaîne observable of([10, 20, 30], new Date(), {name: 'John Doe'}).subscribe( next: value => console.log(value 1.1.3. timer créer un observable qui commence à émettre les valeurs après un délai d’attente, la valeur continuera d’augmenter Il prend en premier paramètre quand déclencher l’event, en deuxième paramètre l’intervalle de temps entre chaque émission 1 timer(1000, 500).subscribe( next: value => console.log(value)); ou timer(1000).subscribe( ne 1.1.4. fromEvent utilisé pour émettre un observable en sebasant sur un événement.prend en paramètre l’élément cible puis l’événement à écouter. fromEvent(document, 'click').subscribe( next: value => console.log(value)); 1.2. Les opérateurs de transformation (pipable) map filter take throttleTime debounceTime operateurs d’aplatissement/ flatteningoperators – MergeMap – SwitchMap – ConcatMap – ExhaustMap – combineLatest – forkJoin 1.2.1. map transforme les valeurs émises par un observable en une nouvelle valeur map(value => value * 2) 1.2.2. filter filtre les valeurs émises par un observable en fonction d’une condition filter(value => value > 10) 1.2.3. take prend des valeurs de la source observable à l’observateur (mise en miroir) jusqu’à ce qu’il atteigne le seuil de valeurs défini pour l’opérateur Sur chaque valeur, take compare le nombre de valeurs qu’il a mises en miroir avec le seuil défini. Si le seuil est atteint, il termine le flux en se désabonnant de la source et en transmettant la notification complète à l’observateur exemple: from([10, 20, 30, 40, 50]).pipe(take(3)).subscribe( next: value => console.log(value)); // result: 10, 20, 30 2 1.2.4. throttleTime prend en paramètres un nombre x représentant le nombre de millisecondes. Au départ le timer est désactivé. Dés que la première valeur est émise, elle la laisse passer et lance un timer pendant x ms Pendant la durée du timer rien ne passe et même si une nouvelle valeur arrive le timer reste inchangé. Une fois le timer fini, throttleTime refait la même chose et attend la première émission pour reprendre le même processus Cas d’utilisation : déclencher une fois un évènement dans un intervalle de temps donné (survol souris) // Observable qui émet une valeur toutes les 200ms const source$ = interval(200); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,... source$.pipe( throttleTime(1000) // Limite les émissions à une valeur toutes les 1000ms ).subscribe({ next: value => console.log(`Valeur émise : ${value}`), // 0, 6, 12, 18,... complete: () => console.log('Terminé'), error: err => console.error('Erreur :', err) }); 1.2.5. debounceTime retarde les valeurs émises par une source pour le temps d’échéance donné. Si dans ce délai une nouvelle valeur arrive, la valeur en attente précédente est supprimée et l’intervalle est réinitialisé. garde une trace de la valeur la plus récente et l’émet lorsque l’heure d’échéance donnée est dépassée. L’autocomplete est le cas classique du debounceTime import { fromEvent } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; // Exemple : écoute des frappes clavier dans un champ de saisie const searchInput = document.getElementById('search'); fromEvent(searchInput, 'input').pipe( debounceTime(500) // Attendre 500ms après la dernière saisie ).subscribe({ next: event => console.log(`Valeur finale : ${event.target.value}`), complete: () => console.log('Terminé') 3 }); 1.2.6. imbriquer les abonnements aux observables Une erreur courante commise dans les applications Angular consiste à imbriquer les abonnements observables: this.activatedRoute.params.subscribe( (params) => { this.cvService.findPersonneById(params.id).subscribe( (personne) => { this.personne = personne; } ); }); Cette syntaxe n’est pas recommandée: difficile à lire et peut entraîner des bogues des effets secondaires inattendus Par exemple, cette syntaxe rend difficile la désinscription correcte de tous ces observables. si observable1 émet plus d’une fois dans un court laps de temps, nous pour- rions vouloir annuler l’abonnement précédent à observable2 et en démarrer un nouveau basé sur les nouvelles valeurs de observable1. 1.2.7. opérateurs d’aplatissement/ flatteningoperators permettent d’émettre un flux à partir d’un autre et éviter les inscriptions imbriquées avec subscribe en existe plusieurs variantes et qui permettent de spécifiercomment gérer les flux récupérés : – Est-ce qu’on est encore intéressé par l’inscription précédente? – Est-ce que l’ordre des inscriptions est important ? 1.2.7.1. MergeMap (ordre : non, intérêt : tous) une combinaison de deux opérateurs merge et map. – map permet de mapper une valeur d’une source observable à un flux observable. Ces flux sont souvent appelés fluxinternes. – merge combine tous les flux internes observables renvoyés par map et émet simultanément toutes les valeurs de chaque flux – pendant que les valeurs de toute séquence combinée sont produites, elles sont émises dans la séquence résultante. – Utilisez cet opérateur si vous n’êtes pas concerné par l’ordre des émis- sions et que vous êtes intéressé par toutes les valeurs provenant de plusieurs flux combinés comme si elles étaient produites par un seul flux. params = [ {name: 'obs1', timer: 1000, iteration: 4 }, {name: 'obs2', timer: 1500, iteration: 4 }, ]; from(params).pipe( 4 mergeMap(param => timerObs(param.timer, param.name))).subscribe( data => console.log( timerObs(timer: number, name: string, iteration = 4){ return new Observable( (observer: Observer< string >) => { let i = 0; const x = setInterval( () => { observer.next(`observable ${name} : ${++i}`); if (i >= iteration) { clearInterval(x); // which will stop the timer observer.complete(); }}, timer); }); } // result: observable obs1 : 1, observable obs2 : 1, observable obs1 : 2, observable ob 1.2.7.2. SwitchMap (ordre : oui, intérêt : dernier et si actif et recoit une nouvelle valeur, il bascule) une combinaison de deuxopérateurs - switchAll et map. – map vous permet de mapper une valeur d’une source observable d’ordre supérieur à un flux observable interne – switch s’abonne à l’observable interne fourni le plus récemment émis par un observable d’ordre supérieur – Annule et se désabonne automatiquement du second observable lorsque lepremier émet une nouvelle valeur. – désabonne automatiquement du second observable si nous nousdésin- scrivons du premier. – S’assure que les deux observables se produisent en séquence, l’un après l’autre. – n’a qu’un seul abonnement actif à la fois à partir duquel les valeurs sont transmises à un observateur. from(params).pipe( switchMap(param => timerObs(param.timer, param.name))).subscribe( data => // observable obs2 : 1, observable obs2 : 2, observable obs2 : 3, observable obs2 : 4 const change$ = fromEvent(this.searchInput.nativeElement, 'keyup'); change$.pipe( map((event: KeyboardEvent) => this.search), debounceTime(300), switchMap((search) => { return this.rxjsService.getProducts(search) })).subscribe((input) => { this.products = input;} ) 1.2.7.3. ConcatMap (ordre : oui, intérêt : tous) une combinaison de deux opérateurs concat et map. concat combine tous les flux internes observables renvoyés par la map et émet séquentiellement toutes les valeurs de chaque flux d’entrée. 5 l’ordre des émissions est important et voir d’abord les valeurs émises par les flux qui passent parl’opérateur en premier. from(params).pipe( concatMap(param => timerObs(param.timer, param.name))).subscribe( data => // observable obs1 : 1, observable obs1 : 2, observable obs1 : 3, observable obs1 : 4, // observable obs2 : 1, observable obs2 : 2, observable obs2 : 3, observable obs2 : 4 1.2.7.4. ExhaustMap (ordre : oui, intérêt : premier et si actif ignore les nouveaux) une combinaison de deux opérateurs exhaust et map. – exhaust s’abonne à un observable interne et transmet des valeurs à un observateur s’il n’y a pas déjà d’abonnement actif, sinon il ignore simplement les nouveaux observables internes. – 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é. – Une fois le flux actif en cours terminé, l’opérateur attend qu’un autre observable interne s’abonne en ignorant les observables internes précédents. from(params).pipe( exhaustMap(param => timerObs(param.timer, param.name))).subscribe( d // observable obs1 : 1, observable obs1 : 2, observable obs1 : 3, observable obs1 : 4 1.2.7.5. combineLatest (ordre : oui-, intérêt : toute combinaison) fusionner plusieurs flux en prenant la valeur la plusrécente de chaque entrée observable et en émettant ces valeurs à l’observateur sousforme de sortie combinée (généralement sous forme de tableau) 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 au moins une valeur, il émet les valeurs mises en cache combinées à l’observateur Le flux résultant se termine lorsque tous les flux sont terminés et génère une erreur si l’un des flux génère une erreur. si un flux n’émet pas de valeur mais se termine, le flux résultantse termin- era au même moment sans rien émettre. si un flux d’entrée n’émet aucune valeur et ne se termine jamais, com- bineLatest n’émettra jamais et ne se terminera jamais const obs1$ = interval(1000); const obs2$ = interval(2000); combineLatest([obs1$, obs2$]).subscribe( data => console.log(data)); // resultat: [ 0, 0 ] [ 1, 0 ] [ 2, 0 ] [ 2, 1 ] [ 3, 1 ] 1.2.7.6. forkJoin (ordre : oui, intérêt : derniere valeur de chaque observable) 6 peut être appliqué à n’importe quel nombre d’Observables. si tous sont terminés, forkJoin émet la dernière valeur de chacun Un cas d’utilisation courant pour forkJoin est lorsque nous devons dé- clencher plusieurs requêtes HTTP en parallèle avec le HttpClient puis recevoir toutes les réponses en même temps dans un tableau de réponses const obs1$ = interval(1000).pipe(take(5)); const obs2$ = interval(2000).pipe(take(2)); forkJoin([obs1$, obs2$]).subscribe( data => console.log(data)); // resultat: [ 4, 1 ] forkJoin({ users: http.get('https://jsonplaceholder.typicode.com/users'), posts: http.get('https://jsonplaceholder.typicode.com/posts'), }).subscribe((usersAndPosts) => { this.posts = usersAndPosts.posts; this.users = usersAndPosts.users; }); 2. Gestion des erreurs: catchError permet de capturer les erreurs quise produisent dans un observable et de les traiter de manièreappropriée permet de continuer à émettre des valeurs depuis unobservable même si une erreur se produit, plutôt que de stoppercomplètement l’émission de valeurs. prend en argument une fonction qui prend en entrée l’erreur etretourne un observable pour gérer cette erreur. Si vous voulez retourner l’erreur dans votre flux utilisez l’opérateurthrowError. this.activatedRoute.params.pipe( switchMap((param) => this.cvService.getCvById(param['id'])), catchError((e) => { console.log(e); this.router.navigate([APP_ROUTES.cv]); return throwError(() => new Error(e)); }) ); Vous pouvez utilisez EMPTY pour spécifier que vous n’émettez rien et émet ensuite une notification de complétion du flux. Vous pouvez aussi émettre ce que vous voulez afin de continuer le flux catchError((e) => { return EMPTY; }); catchError((e) => { return of(generateFakeData()); }); 7 3. Les subjects un type particulier d’observable. En effet Unsubjectest en même temps un observable et un observer, il possède donc lesméthodes next, error et complete Pour broadcaster une nouvelle valeur, il suffit d’appeler, et elle sera diffusé aux Observateurs enregistrés const subject = new Subject(); subject.subscribe( next: value => console.log("value: ", value)); subject.next(1); 3.1. BehaviorSubject stocke la valeur “actuelle”: vous pouvez toujours obtenir directement la dernière valeur émiseà partir du BehaviourSubject. Il existe deux façons d’obtenir cette dernière valeur émise: – en accédant à la propriété value sur le BehaviourSubject: subject.value – vous y abonnez, le BehaviourSubject émettradirectement la valeur actuelle à l’abonné. Même si l’abonné s’abonne beaucoup plus tard que la valeur a été stockée. Vous pouvez créer un BehaviourSubject avec une valeur initiale qui sera doncémise directement pout la première inscription. const subject = new BehaviorSubject(0); subject.subscribe( next: value => console.log("value: ", value)); subject.next(1); 3.2. ReplaySubject comparable au BehaviorSubject dans la mesure où il peut envoyer les “anciennes” valeurs aux nouveaux abonnés particularité: pouvoir enregistrer une partie de l’exécution de l’observable et donc de stocker plusieurs anciennes valeurs et de les “rejouer” aux nou- veaux abonnés. Lors de la création du ReplaySubject, vous pouvez spécifier la quantité de valeurs que vous souhaitez stocker et pendant combien de temps vous souhaitez les stocker. const subject = new ReplaySubject(2); subject.next(1); subject.next(2); subject.next(3); subject.subscribe( next: value => console.log("value: ", value)); // result: value: 2, value: 3 const subject = new ReplaySubject(5, 500); 8 subject.next(1); subject.next(2); subject.next(3); setTimeout(() => { subject.subscribe((next) => console.log("value: ", next)); }, 1000); // result: none 3.3. AsyncSubject Alors que BehaviorSubject et ReplaySubject stockent tous deux des valeurs, AsyncSubject fonctionne un peu différemment. L’AsyncSubject est une variante de l’objet où seule la dernière valeur de l’exécution Observable est envoyée à ses abonnés, et uniquement lorsque l’exécution est terminée const subject = new AsyncSubject(); subject.next(1); subject.subscribe( next: value => console.log("value: ", value)); subject.next(2); subject.next(3); subject.complete(); // result: value: 3 4. Opérateur de Multicasting share shareReplay 4.1. share multicatser les valeurs émises par une sourceObservable pour les abonnés multicatser signifie que les données sont envoyées à plusieursdestinations le partage vous permet d’éviter plusieursexécutions de la source Observ- able lorsqu’il existe plusieursabonnements. utile si vous avez besoin d’empêcher desappels d’API répétés ou des opéra- tions coûteuses exécutées parObservables. const obs$ = interval(1000).pipe(take(3), tap(()=> console.log("executing observable")), sh obs$.subscribe( value => console.log("value1: ", value), error => console.log("error: ", er setTimeout(() => { obs$.subscribe( value => console.log("value2: ", value), error => console.log("error: ", }, 6000); Observable partagé = un Subject exposé par l’opérateur de partage L’opérateur de partage gère également un abonnement interne à lasource Observable. quand le nombre d’abonnés = 0, share se désabonnera de l’Observable source et réinitialisera lObservable interne (le Subject). L’abonné suivant déclenchera un nouvel abonnementàl’Observable source, : une nouvelle exécution del’Observable source 4.2. shareReplay Nous voulons donc qu’il partage à la fois la source Observableetrejoue les dernières émissions pour les abonnés retardataires. 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 const obs$ = interval(1000).pipe(take(3), tap(()=> console.log("executing observable")), sh obs$.subscribe( value => console.log("value1: ", value), error => console.log("error: ", er setTimeout(() => { obs$.subscribe( value => console.log("value2: ", value), error => console.log("error: ", e }, 3000); setTimeout(() => { obs$.subscribe( value => console.log("retard: ", value), error => console.log("error: ", }, 6000); Contrairement à share, shareReplay expose un ReplaySubject auxabonnés. ReplaySubject(). Étant donné que shareReplay ne garde pas la trace d’un nombred’abonnés par défaut, il n’est pas en mesure de se désabonner de lasource Observable. Ceci est faisable si vous utilisez l’option refCount Afin d’utiliser shareReplay tout en vous débarrassant des problèmes de fuite de mémoire, vous pouvez utiliser les options bufferSizeetrefCount : shareReplay({ bufferSize : 1, refCount : true }). shareReplay ne réinitialise pas le ReplaySubject internelorsque refCount atteint 0, mais se désabonne de la source Observable. Les abonnés en retard ne déclencheront pas une nouvelle exécutionde la source Observable et recevront jusqu’à N (bufferSize) émissions 5. exemples de hot observable: fromEvent subjects share(), shareReplay() 6. se désinscrire problème de subscribe(): reste valide même après la disparition de la variable ce qui sature la mémoire pour rien. 6.1. complete Lorsqu‘un observable se termine (méthode complete), tous lesabon- nements sont automatiquement désabonnés. Si vous savez qu’un observable se terminera, vous n’avez pas àvous soucier de nettoyer les abonnements. Cela est vrai pour tout observable créé à partir de plusieurs des sources observables intégrées dans Angular telles que les méthodes du HttpClient ou le service ActivatedRoute Il n’y a pas de convention standard qui indique ce que lesobservables peu- vent terminer, ou quand, il appartient donc audéveloppeur de faire les recherches nécessaires. 11 6.2. async pipe La première et la plus courante solution pour gérer lesabonnements est l’async pipe Plutôt que de vous abonner à des observables dans vos composantset de définir des propriétés sur votre classe pour les données del’observable, préférez l’utilisation de l’async pipe. L’async pipe nettoie ses propres abonnements quand et selon lesbesoins, vous déchargeant de cette responsabilité. Maintenant, celadevrait être votre premier choix et fonctionnera pour la plupartdes scénarios todos$: Observable; constructor(private todoService: TodoService) { this.todos$ = this.todoService.getTodos(); } {{todo | json}} 6.3. Et si on peut pas utiliser l’async pipe ? subscription takeUntilDestroyed Utiliser un signal 6.3.1