Angular
Angular
Introduction
AYMEN SELLAOUTI
1
Références
2
C’est quoi NodeJs?
3
C’est quoi NodeJs?
[Link]® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
[Link] uses an event-driven, non-blocking I/O model that makes it
lightweight and efficient. [Link]' package ecosystem, npm, is the
largest ecosystem of open source libraries in the world.
Environnement d’exécution JS
4
Moteur Javascript : Javascript Engine
Tout code JavaScript que vous
écrivez a besoin d’un moteur
Javascript pour l’exécuter.
Le moteur JavaScript possède
deux composants principaux:
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.
5
Moteur Javascript : Javascript Engine
6
V8 Javascript Engine
V8 est un moteur JavaScript développé par Google et utilisé par
chrome pour exécuter du code JavaScript.
Il est Open Source
Ecrit en C++
[Link] 7
Qu’est ce qu’un environnement
d’exécution JS
Un environnement d’exécution JS est l’endroit ou votre code JS va
être exécuté. C’est la ou vivra votre JavaScript Engine.
Il va, entre autre,. 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.
[Link] 8
Est-ce que JavaScript est Synchrone
ou Asynchrone ?
Js est SYNCHRONE.
Les fonctions asynchrones telles que setTimout ne font pas partie
de JS.
Elle font partie des API de votre runtime envirement.
Donc JS peut agir d’une façon ASYNCHRONE mais ceci n’est
pas innée, ce n’est pas dans son Core mais plutôt du au API offert par
le runtime.
Dans NodeJs c’est libuv qui permet cet aspet Asynchrone.
[Link] 9
Asynchrone
Un code asynchrone est un code non bloquant
Une fonction non bloquante est une fonction qui s’exécute en
parallèle avec le reste du code.
[Link] 10
Qu’est ce qu’un environnement
d’exécution JS
Si on gardait uniquement l’aspect
Synchrone de JS, ca sera un
problème énorme avec le browser.
La solution proposée par les API
est l’ajout e la composante
synchrone via l’event loop et le
callback queue.
Ceci va permettre d’effectuer des
traitements sans bloquer le stack
du moteur JS.
[Link] 11
Comment ca marche dans le browser ???
A part le moteur JS, votre
environnement possède des API’s
qui permettent à 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.
[Link] 12
Comment est né Node ?
Au départ on ne pouvait exécuter Js que dans un Browser. C’est lui qui fournissait
son environnement d’exécution.
13
Comment est né Node ?
2008
2009
14
Comment est né Node ?
[Link] 15
NodeJs un environnement d’exécution
Afin de s’exécuter, Javascript a besoin d’un environnement
d’exécution.
C’est comme un conteneur qui contient tout ce qu’il vous faut pour
exécuter du code Js.
Le noyau de l’environnement d’exécution est le moteur Js.
Il y a aussi les APIs Node
Libuv la bibliothèque qui permet de gérer les I/O Asynchrone
Les nodes Js Binding
16
NodeJs un environnement d’exécution
Moteur JavaScript NodeJs APIs
Bindings
NodeJs
Fs http
path crypto
17
Asynchrone
Un code asynchrone est un code non bloquant.
Dans sa présentation, NodeJs se présente comme étant un
environnement d’exécution JavaScript asynchrone et dirigé par les
événements.
Imaginez qu’on veuille lire deux fichiers indépendants.
« Time Line » du Modèle « bloquant »
18
18 1
[Link] 8
Est-ce que NodeJs est Multi thread ?
NodeJs est monothread au départ puis le multithrading a été introduit.
Vous avez encore V8 comme Moteur Js.
La gestion de l’aspect Asynchrone est offerte par la bibliothèque libuv
[Link] 19
libuv
libuv est une bibliothèque multiplatforme écrite en C
Son rôle est de gérer les opération asynchrone d’I/O
Elle permet à plusieurs langages de s’y connecter via les connecteurs
de bindings.
Elle offre la partie principale qui gérer les traitements Asynchrone de
NodeJs, l’EVENT LOOP.
[Link] 20
libuv
Gestion de fichiers
Réseau
Event Loop
[Link] 21
libuv
Processus Thread Thread 1 Thread 2 Thread 3 Thread 4
Node Principal
[Link] 22
libuv
libuv offre un thread pool.
Le nombre de thread est par défaut de 4 et il est extensible jusqu’à 128
Afin de le modifier, il faut utiliser la variable d’environnement
UV_THRADPOOL_SIZE
Le thread pool ne gère pas automatiquement toutes les
opérations asynchrones.
L’idée de base est d’éviter ca et de déléguer ca à l’OS tant qu’il peut le
faire.
[Link] 23
libuv
Le pool gère donc les opérations d'E/S asynchrones pour lesquelles le
noyau de votre OS a une prise en charge faible
Ceci s’applique pour les opérations de système de fichiers (fs) et les
opérations DNS (dns)
Il est également utilisé pour le travail asynchrone lié au processeur
dans les modules principaux de [Link], à savoir la compression (zlib) et
la cryptographie (crypto).
[Link] 24
Libuv
event loop
Stack Node Thread Pool
API OS
[Link]("hello node");
setTimeout(() => {
[Link]("second Hello");
}, 3000);
[Link]("kepler_data.csv") Timer Task Queue
.on("data", (data) => {
[Link]("file data: ", data)); I/O Task Queue
}
.on("end", () => [Link]("end")) Immediate Task Queue
.on("error", (error) => { Event Loop
[Link]("error : ", error); Close Task Queue
});
[Link]("bye node"); [Link] 25
Libuv
event loop
[Link]('cc node');
setTimeout(function () {
for (let i=1; i< 100000; i++) {
[Link]('procssing',i);
}
[Link]('end processing :D');
},0);
[Link]('bye node');
26
Libuv
event loop
Afin de bien travailler avec l’event loop éviter :
Le calcul complexe et couteux en CPU
Les objets JSON lourd
Le expressions régulières complexes.
[Link] 27
C’est quoi Angular?
28
C’est quoi Angular?
Framework JS
SPA
Rapide
Orienté Composant
29
Est-ce qu’Angular est stable ?????
30
SPA
Application Web
C
traditionnelle H
T J
S
M
L S S
H
C
T J
M S
L
S S
H
C
T J
M S
L
S S
C
S
C
S JS S
H S
T H JS
M T
M
L SPA L
API 31
Angular : Arbre de composants
32
Architecture Angular
33
Principaux concepts et notions
Component Template DataBinding
Méta données
Injection de
Service Route
dépendance
34
Les librairies d’Angular
Ensemble de modules JS
35
Les composants
Le composant est la partie principale d’Angular.
36
Template
37
Les méta data
38
Le Data Binding
Le data binding est le mécanisme qui permet de mapper
des éléments du DOM avec des propriétés et des méthodes
du composant.
39
Les directives
Lesdirectives Angular sont des classes avec la métadata @Directive. Elle
permettent de modifier le DOM et de rendre les Template dynamiques.
Directive d’attributs
40
Les services
Classes permettant d’encapsuler des traitements métiers.
41
Installation d’Angular
Deux méthodes pour installer un projet Angular.
42
Installation d’Angular
QuickStart
Deux méthodes
Télécharger directement le projet du dépôt Git
[Link]
Ou bien le cloner à l’aide de la commande suivante :
git clone [Link] quickstart
Se positionner sur le projet
Installer les dépendance à l’aide de npm : npm install
lancer le projet à l’aide de npm : npm start
43
Installation d’Angular
Angular Cli
Nous allons installer notre première application en utilisant angular Cli.
Si vous avez Node c’est bon, sinon, installer NodeJs sur votre machine. Vous devez avoir
une version de node >= 14.2
Une fois installé vous disposez de npm qui est le Node Package Manager. Afin de vérifier
si vous avez NodeJs installé, tapez npm –v.
Installer maintenant le Cli en tapant la : npm install -g @angular/cli
npm install -g @angular/cli@16.2.15 installe la version 16.2.15
npm view @angular/cli affiche la liste des versions de la cli
Installer un nouveau projet à l’aide de la commande ng new nomProjet
npx @angular/cli@17.3.9 new nomProjet
Afin d’avoir du help pour le cli tapez ng help
Lancer le projet en utilisant la commande ng serve
44
Angular versions
45
Installation d’Angular
Angular Cli
Positionnez vous maintenant dans le dossier
Tapez la commande suivante : ng new nomNewProject
lancez le projet à l’aide de npm : ng serve
Naviguez vers l’adresse mentionnée.
Vous pouvez configurer le Host ainsi que le port avec la commande
suivante : ng serve --host leHost --port lePort
46
Quelques commandes du Cli
Commande Utilisation
Component ng g component my-new-component
Directive ng g directive my-new-directive
Pipe ng g pipe my-new-pipe
Service ng g service my-new-service
Class ng g class my-new-class
Interface ng g interface my-new-interface
Module ng g module my-module
47
Arborescence d’un projet Angular
48
Ajouter Bootstrap
On peut ajouter Bootstrap de plusieurs façons :
1- Via le CDN à ajouter dans le fichier [Link]
2- En le téléchargeant du site officiel
3- Via npm
◦ Npm install bootstrap --save
49
Ajouter Bootstrap
Pour ajouter les dossiers téléchargés on peut le faire de deux façons :
1- En l’ajoutant dans [Link]
2- En ajoutant le chemin des dépendances dans les tableaux styles et scripts dans le fichier
[Link]:
"styles": [
"../node_modules/bootstrap/dist/css/[Link]",
"[Link]",
],
"scripts": [
"../node_modules/bootstrap/dist/js/[Link]"
],
50
Ajouter Bootstrap
Ajouter dans le fichier src/[Link] un import de vos bibliothèques.
@import "./../node_modules/bootstrap/";
51
Angular
Les composants
AYMEN SELLAOUTI
52
Objectifs
1. Comprendre la définition du composant
53
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 la.
Plus simplement, un composant est un fragment HTML géré par une classe JS
(component en angular et controller en angularJS)
Une application Angular est un arbre de composants
La racine de cet arbre est l’application lancée par le navigateur au lancement.
Un composant est :
Composable (normal c’est un composant)
Réutilisable
Hiérarchique (n’oublier pas c’est un arbre)
NB : Dans le reste du cours les mots composant et component représentent
toujours un composant Angular.
54
Angular : Arbre de composants
55
Quelques exemples
56
Quelques exemples
57
Quelques exemples
58
Quelques exemples
59
Quelques exemples
60
Quelques exemples
61
Premier Composant
Le décorateur @Component permet d’ajouter un
@Component({
comportement à notre classe et de spécifier que c’est un
selector: "app-root", Composant Angular.
standalone: true, selector permet de spécifier le tag (nom de la balise)
imports: [RouterOutlet, RouterLink], associé ce composant
templateUrl: "./[Link]", standalone permet de spécifier si le composant est
standalone ou pas
styleUrl: "./[Link]",
imports L’ensemble des dépendances du composant
}) templateUrl: spécifie l’url du template associé au
export class AppComponent { composant
title = "ng18"; styleUrls: tableau des feuilles de styles associé à ce
} composant
62
Création d’un composant
Deux méthodes pour créer un composant
Manuelle
Avec le Cli
Cli, Avec la commande ng generate component my-new-component ou son raccourci
ng g c my-new-component
63
Création d’un composant
La commande generate possède plusieurs options
OPTION DESCRIPTION
[Link] 64
Property Binding
Balises Attributs
Evènement
Attributs Méthodes
HTML TS
65
Property Binding
@Component({
selector: 'app-color',
templateUrl: './[Link]',
styleUrls: ['./[Link]'],
<div [[Link]]="color"> providers: [PremierService]
Color })
</div> export class ColorComponent implements OnInit {
color = 'red';
<input [(ngModel)]="color" constructor() { }
type="text"
class="form-control" ngOnInit() {}
> processReq(message: any) {
le contenu de la propriété color est {{color}} alert(message);
<button (click)="loggerMesData()">log data</button> }
<br> loggerMesData() {
<a (click)="goToCv()" >Go to Cv</a> [Link]('test');
}
HTML goToCv() {
const link = ['cv'];
[Link](link);
}
TS
66
Property Binding
Binding unidirectionnel.
Permet aussi 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.
Deux possibilités pour la syntaxe:
[propriété], exemple [hidden]=false
bind-propriété, exemple bind-hidden=false
67
Event Binding
Binding unidirectionnel.
Permet d’interagir du DOM vers le composant.
L’interaction se fait à travers les événements.
Deux possibilités pour la syntaxe :
(evenement), exemple (click)=changeBehavior()
on-evenement
68
Property Binding et Event Binding
import { Component } from '@angular/core';
@Component({
<hr>
selector: 'inter-interpolation', Nom : {{nom}}<br>
template : `[Link]` , Age : {{age}}<br>
styles: [] Adresse : {{adresse}}<br>
})
export class InterpolationComponent { //Property Binding
nom:string ='Aymen Sellaouti'; <input #name
age:number =35; [value]="getName()">
adresse:string ='Chez moi ou autre part :)';
getName(){
//Event Binding
return [Link]; <button
} (click)="modifier([Link])"
modifier(newName){ >Modifier le nom</button>
[Link]=newName;
} <hr>
}
Component Template
69
Exercice
Créer un nouveau composant. Ajouter y un Div et un input de type texte.
Fait en sorte que lorsqu’on écrive une couleur dans l’input, ca devienne la couleur
du Div.
Ajouter un bouton. Au click sur le bouton, il faudra que le Div reprenne sa couleur
par défaut.
Ps : pour accéder au contenu d’un élément du Dom utiliser #nom dans la balise et
accéder ensuite à cette propriété via le nom.
Pour accéder à une propriété de style d’un élément on peut binder la propriété
[[Link]été]
exemple
[[Link]]
70
Two way Binding
Binding Bi-directionnel
Permet d’interagir du Dom ver le composant et du composant vers le
DOM.
Se fait à travers la directive ngModel ( on reviendra sur le concept de
directive plus en détail)
Syntaxe :
[(ngModel)]=property
Afin de pouvoir utiliser ngModel vous devez importer le FormsModule dans
[Link]
71
Property Binding et Event Binding
import { Component} from
'@angular/core';
<hr>
@Component({ Change me if u can
selector: 'app-two-way', <input
templateUrl: './two-
[(ngModel)]="two">
[Link]',
styleUrls: ['./two- <br>
[Link]'] it's always me :d
}) {{two}}
export class TwoWayComponent {
two:any="myInitValue";
}
Component Template
72
Exercice
Le but de cet exercice est de créer un aperçu de carte visite.
Créer un composant
Préparer une interface permettant de saisir d’un coté les données à
insérer dans une carte visite. De l’autre coté et instantanément les
données de la carte seront mis à jour.
Préparer l’affichage de la carte visite. Vous pouvez utiliser ce thème
gratuit :
[Link]
73
Exercice
74
Exercice
75
Résumé : Property Binding
76
Angular 17
Le nouveau flux de control
Afin de continuer sur la logique de simplification de l’apprentissage
d’Angular, l’équipe Angular a proposé de nouveaux flux de
contrôle.
Les flux suivants ont été proposés :
@If
@for
@switch
77
Control flow
@if
@if est associé à une expression booléenne. Si cette expression est
fausse (false) alors l'élément et son contenu sont retirés du DOM, ou
jamais ajoutés).
@if(condition)
Si le booléen est true alors l’élément host est visible.
Si le booléen est false alors l’élément host est caché.
@if (isAuthenticated()) {
<button
(click)="deleteCv(cv)"
class="btn btn-danger">
Delete
</button>
}
78
Control flow
@if, @else if et @else
@if peut également être utilisé avec @else if et/ou @else selon le
besoin.
La syntaxe est très intuitive, demandé vous comment vous aurez fait
en Javasript et ajoutez un @ :D.
@if (connectedUser) {
Hello {{ [Link]}}
} @else {
<div>Merci de vous connectez</div>
}
79
Control flow
@if, @else if et @else
@if (!connectedUser) {
<div class="alert alert-danger">Merci de vous connectez</div>
} @else if (![Link]) {
<div class="alert alert-warning">
Hello {{ [Link] }}, merci d'activer votre compte
</div>
} @else {
<div class="alert alert-success">Hello {{ [Link] }}</div>
}
80
8
Control flow
1
@For
La directive structurelle @for permet de boucler sur un itérable et
d'injecter les éléments dans le DOM.
<ul>
<li *ngFor="let episode of episodes">{{ [Link] }}</li>
</ul>
<ul>
@for (episode of episodes; track [Link]) {
<li>{{ [Link] }}</li>
}
</ul>
Control flow <ul>
@For <li
*ngFor="
let episode of episodes;
@for fournit certaines informations sur la let i = index;
let isOdd = odd;
boucle en cours : let isFirst = first
"
$index: position de l'élément. [ngClass]="{ odd: isOdd, bgfonce: isFirst }"
>
Episode {{ i + 1 }}{{ [Link] }}
$odd: true si l'élément est à une position impaire. </li>
</ul>
$even: true si l'élément est à une position paire. <ul>
@for (episode of episodes; track [Link]) {
$first: true si l'élément est à la première position. <li
[ngClass]="{ odd: $odd, bgfonce: $first }"
>
$last: true si l'élément est à la dernière position. Episode {{ $index + 1 }} : {{ [Link] }}
</li>
}
</ul>
82
Control flow 8
3
@For
track
Avec @For la fonction de tracking est devenue obligatoire
La fonction de suivi créée via l'instruction track est utilisée pour
permettre au mécanisme de détection des changements
d’Angular de savoir exactement 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.
@for (episode of episodes; track [Link]) {
<li>{{ [Link] }}</li>
}
Control flow 8
4
@For
track
En principe, il devrait toujours y avoir quelque chose d'unique dans
les éléments sur lesquels vous itérer.
Dans le pire des cas, s'il n'y a rien d'unique dans les éléments du
tableau, vous pouvez utiliser $index de l'élément, c'est-à-dire la
position de l'élément dans le tableau.
@For
track
track peut prendre en paramètre une fonction à laquelle on passe
l’index et l’élément actuel de l’itération
Control flow
6
Control flow
7
@switch
<div [ngSwitch]="streamingService">
Avec @switch, vous pouvez <div *ngSwitchCase="'AppleTV'">Ted Lasso</div>
<div *ngSwitchCase="'Disney+'">Mandalorian</div>
créer des switch très simplement. <div *ngSwitchDefault>Peaky Blinders</div>
</div>
Vous avez les trois opérateurs : @switch(streamingService) {
@case ('Disney+') {
@switch, @case, @default <div>'Mandalorian'</div>
} @case ('AppleTV') {
@switch : définir l’élément sur <div>'Ted Lasso'</div>
} @default {
lequel switcher <div>'Peaky Blinders'</div>
}
@case : pour identifier le cas }
Control flow
8
Migration automatique
Si vous voulez migrer votre ancien code et utilisez le nouveau work
flow, angular vous fournie une commande qui le fait pour vous :
ng g @angular/core:control-flow
Cette fonctionnalité est encore en developer Preview (Anguar 17.2)
Exercice
Soit la classe Product :
Créer un composant qui contient la propriété products qui représente un
tableau de product.
Afficher la liste des products avec les règles suivantes :
Si la quantité du produit est égale à 0 afficher ‘Le produit est en rupture de
stock’
Si la quantité est > 0 et <= 3 afficher ‘il ne reste que n produits en vente’
Si il y a un discount afficher le prix initial barré et le prix après discount
S’il n’y a aucun produit en vente afficher le magasin est vide.
export class Product {
constructor(
public id = 0, public label = "", public price = 0, public quantity = 0, public discount = 0
) {}
} 89
Exercice
products: Product[] = [
new Product(1, 'Angular Book', 100, 5, 20),
new Product(2, 'Nest Book', 80, 2, 0),
new Product(3, 'Symfony Book', 55, 0, 0),
new Product(4, 'Js Book', 120, 20, 30),
];
90
Change Detection
C’est quoi ?
Change Detection est l’un des mécanisme les plus importants dans
Angular.
Il permet de suivre les changements d’état de votre application et
d’afficher les modifications dans votre vue.
Il garantit que l’interface utilisateur suit toujours d’une façon
synchrone l’état interne de votre application.
91
Change Detection
Chaque composant dans Angular est représenté par une structure
appelé View. Elle contient entre autre l’instance de la classe
Composant appelée componentInstance ainsi que la liste des
éléments du DOM représentant le Template.
[Link] 92
Change Detection
Ensuite, quand le compilateur traite le composant, il identifie les
élément qui nécessite un changement lors du changement d’état.
93
Change Detection
Pour chacun de ces éléments, il crée des objets Bindings. C’est une
structure de données qui informe sur deux choses :
Que voulons nous mettre à jour dans le Dom
Ou récupérer la nouvelle valeur
94
Change Detection
Ensuite, dès qu’un change
Detectin est déclenché,
Angular va parcourir
l’ensemble des Views
(Component) et évaluer la
nouvelle expression du
Binding et la comparer à la
précédente.
Si la valeur est modifiée, elle
met à jour le DOM.
95
Change Detection
Quand déclencher un Change Detection
Un Change Detection est déclenché dans ces cas d’utilisation
1. Initialisation des composants. Par exemple, lors du lancement d'une
application angular, Angular charge le composant principal et
déclenche [Link]() pour appeler la détection de
changement et le rendu de la vue.
2. Les event listener du DOM peuvent mettre à jour les données dans
un composant Angular et déclencher le Change Detection.
3. Les requêtes HTTP.
[Link] 96
Change Detection
Quand déclencher un Change Detection
4. Les MacroTasks, tells que setTimeout() ou setInterval(). En effet
vous pouvez mettre à jour les données dans la callback function d’une
macroTask comme setTimeout().
5. Les MicroTasks, comme [Link]() dont les callback peuvent
mettre à jour les données.
6. D’autres opérations asynchrones qui peuvent mettre à jour vos
données telles que [Link]() et [Link]().
[Link] 97
Change Detection
La stratégie par défaut
Ici, dans tous les composants de
l’arbre de composant, le change
detector alloué à chaque
composant, compare la valeur
courante et la valeur précédente des
propriétés.
Si la valeur change, il va marquer
une propriété isChanged à true.
98
Change Detection
[Link] 99
1
Change Detection
0
0
Problème
Le problème majeur d’Angular était son incapacité à détecter le
changement et ou il se produit exactement.
Ceci est la cause majeure du parcours de l’intégralité de l’arbre afin
d’identifier l’endroit exact ou le changement s’est effectué.
Ceci permet à angular de minimiser la mise à jour du DOM qui est
une opération très couteuse.
1
0
1
Change Detection
Quand un événement se déclenche dans votre application, Angular
parcourt tout votre arbre de composants pour chercher où les
modifications doivent être effectuées.
Ce parcours, Angular le fait de manière très rapide mais le processus
pourrait être encore plus rapide.
D’où l’intérêt d’utiliser les signaux, qui vont assister Angular en lui
indiquant où exactement il doit checker les changements.
Les changements peuvent être opérés par Angular dans une petite
partie d’un template (un bloc @if ou @for).
1
0
2
Performances
Les signaux permettent de réduire le nombre de calculs effectués
lors de la détection des changements dans une application Angular.
Cela se traduit par de meilleures performances d’exécution.
Avec les signaux, il sera possible de vérifier les changements
uniquement dans les composants concernés.
Les signaux vont permettre de rendre [Link] facultatif dans les
versions futures d'Angular. [Link] est une bibliothèque utilisée par
Angular pour détecter les changements et exécuter les tâches
asynchrones
AYMEN SELLAOUTI
103
1
0
4
Un signal est une primitive réactive qui représente une valeur et qui nous
permet de
Les signaux permettent de Les signaux boostent les Les signaux pèsent 2KB,
réagir aux changements performances de votre n’ont pas besoin de charger
d’état (State) n’importe où application en réduisant le des dépendances tierces et ne
dans notre code et pas travail que fait Angular représentent aucun coût de
seulement au sein d’un pour garder le DOM à jour démarrage lorsque votre
composant. avec les valeurs des données application se charge.
1
0
7
API
Angular propose trois principales primitives pour utiliser les signaux :
signal
computed
effect
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.
@Component({
selector: 'app-signal-api',
standalone: true,
imports: [],
styleUrl: './[Link]',
template: ` <h1>Hello World</h1> `,
})
export class SignalApiComponent {
lastname: WritableSignal<string> = signal('sellaouti');
} 108
Récupérer la valeur d’un signal
Afin de récupérer la valeur d’un signal il suffit de l’appeler comme
une fonction.
@Component({
selector: 'app-signal-api',
standalone: true,
imports: [],
styleUrl: './[Link]',
template: ` <h1>Hello {{ lastname() }}</h1> `,
})
export class SignalApiComponent {
lastname: WritableSignal<string> = signal('sellaouti');
}
109
Les méthodes de modifications de signal :
set() et update() @Component({
selector: 'app-signal-api',
Pour modifier la valeur d’un standalone: true,
imports: [],
signal, on peut passer par : template: `
<h1>Hello {{ lastname() }}</h1>
<input type="number" #input
La Méthode set, qui permet (change)="setCounter(+[Link])"
/>
d’affecter une nouvelle <h2 (click)="increment()">
valeur au signal. Click here. You clicked {{ counter() }} times
</h2>
`,
La méthode update, qui })
export class SignalApiComponent {
permet de calculer une lastname = signal('aymen');
counter = signal(0);
nouvelle valeur d’un signal increment() {
en fonction de sa valeur }
[Link]((currentValue) => currentValue + 1);
111
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
WritableSignal.
lastname = signal('aymen');
firstname = signal('sellaouti');
fullname = computed(() => `${[Link]()} ${[Link]()}`)
112
computed()
Pour identifier un signal qui a changé, et donc si on doit exécuté un computed,
Angular utilise [Link].
[Link]() permet de déterminer si deux valeurs sont identiques. Deux valeurs sont
considérées identiques 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
sont toutes les deux égales à +0
sont toutes les deux égales à -0
sont toutes les deux égales à NaN
[Link] 113
computed()
Comment ca marche ? @Component({
template: `
L’arbre de dépendance est <h3>Counter value {{ counter() }}</h3>
crée dynamiquement à <h3>Derived counter: {{ derivedCounter() }}</h3>
<button (click)="increment()">Increment</button>
chaque appel du computed. <button (click)="multiplier = 10">Set multiplier to 10</button>
`,})
export class ComputedProblemComponent {
Il faut donc faire très counter = signal(0);
multiplier: number = 0;
attention dans la définition derivedCounter = computed(() => {
de vos computed lorsqu’il y a if ([Link] < 10) {
return 0;
des traitement conditionnel. } else {
return [Link]() * [Link];
}
});
increment() {
[Link](`Updating counter...`);
[Link]([Link]() + 1);
}
}
[Link] 114
computed()
Exclure un signal de l’arbre de dépendence
Si pour une raison ou une autre 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.
Dans cet exemple le computed fullname ne sera mis à jour que si le
signal firstname change
lastname = signal('sellaouti');
firstname = signal('aymen');
fullname = computed(() => `${[Link]()} ${untracked([Link])}`);
115
computed()
Modifier un signal dans un computed
La fonction computed doit être sans effets de bord, ce qui signifie qu'elle ne
doit accéder qu'aux valeurs des signaux dépendants (ou à d'autres valeurs
impliquées dans le calcul) et éviter toute mise à jour.
Vous ne pouvez pas modifier un signal dans un computed, ceci provoquera
une erreur.
fullname = computed(() => {
[Link]('i am computing....');
[Link]('ccc');
return `${[Link]()} ${untracked([Link])}`;
});
116
computed()
Les computed, autres propriétés
Les computed sont paresseux (lazy), ce qui signifie que computed
n'est invoquée que lorsque quelqu'un s'intéresse (lit) sa valeur.
Cela permet d'optimiser les performances 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
[Link] 117
Writablesignals et signals
Par défaut, tous les signaux créés par la fonction Signal sont de type
WritableSignal. 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().
Les signaux créés par computed sont, par défaut, read only.
118
Exercice
Créer un composant TTC qui permet de calculer le prix TTC d’un produit selon le
nombre de pièces et la TVA. Sachant que l’utilisateur peut changer toutes les
valeurs et que par défaut la quantité est de 1 le prix est de 0 et la tva est de 18.
Si le nombre de pièces est entre 10 et 15 une remise de 20% est appliquée.
Si le nombre de pièces est supérieur à 15 une remise de 30% est appliquée.
119
L’API effect()
Dans certains cas d’utilisation, vous avez besoin d’être notifié par le
changement d’un signal pour effectuer un effet de bord, donc faire un
traitement sans pour autant créer un nouveau signal ou en modifier
d’autres.
Pensez à un log, à un calcul d’un nombre de click, faire un appel à une
API pour enregistrer une valeur,…
L’API effect est la afin de vous donner cette possibilité
L’effect va être réexécuté si l’un des signaux qu’il utilise émet une
nouvelle valeur.
120
L’API effect()
Vous pouvez créer un effet via la fonction effect.
Les effets s'exécutent toujours au moins une fois.
Lorsqu'un effet est exécuté, il suit tous les signaux qu’il contient.
Chaque fois que l'une de ces valeurs de signal change, l'effet se
reproduit.
L’effet est semblable aux computed, les effets gardent une trace de
leurs dépendances de manière dynamique et ne suivent que les
signaux qui ont été lus lors de l'exécution la plus récente.
constructor() { private logEffect = effect(() => {
effect(() => { [Link](`
[Link](`count:${[Link]()}`); The current count is:${[Link]()}
}); `);
} }); 121
L’API effect()
Les effets s'exécutent toujours de
manière asynchrone, pendant le export class EffectComponent {
processus de change detection. counter = signal(0);
constructor() {
Les effets seront exécutés le nombre [Link](1);
[Link](2);
minimum de fois. Si un effet dépend }
de plusieurs signaux et que plusieurs private logEffect = effect(() => {
d'entre eux changent simultanément, [Link](`
une seule exécution de l'effet sera The current count is:
${[Link]()}`);
programmée. });
Remarque : l'API effect() est toujours en }
aperçu développeur (17.3).
122
L’API effect()
Un effect doit être définit dans un contexte d’injection.
Ceci est faisable dans un component, un pipe, une directive ou le
constructeur d’un service.
Vous pouvez aussi injecter l’Injector et le passer à l’effect en
deuxième paramètre qui représente un objet d’options.
private logEffectWithInjector = effect(() => {
[Link](
`The current count is: ${[Link]()}`
);
},
{ injector: [Link] }
);
123
effect()
Exclure un signal de l’arbre de dépendence
Si pour une raison ou une autre vous voulez lire une valeur d’un signal
dans un effect mais sans l’intégrer dans l’arbre de dépendance, vous
pouvez utiliser l’Api untracked.
Untracked peut prendre en paramètre une fonction
/**
* Execute an arbitrary function in a non-reactive (non-tracking) context. The
* executed function can, optionally, return a value.
*/
export declare function untracked<T>(nonReactiveReadsFn: () => T): T;
124
Cycle de vie d’un composant
Le composant possède un cycle de vie géré par Angular. En effet,
Angular :
Crée le composant
L’affiche
Crée ses fils
Les affiche
Ecoute le changement des propriétés
Le détruit avant de l’enlever du DOM
[Link]
125
Comment ca marche réellement
<mon-app>
<compo-fils [binded]='prop'>
1. Angular lance l’application
2. Ils crée les classes pour chaque composant, il lance donc le Constructeur de mon-app component.
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 changements. C’est ici qu’il mettra à
jour le binding de mon-App et lancera l’appel à ngOnInit, suivi de la gestion du binding de
compo-fils puis l’appel de son ngOnInit.
126
Interfaces de gestion du cycle de vie d’un
composant
Afin de gérer le cycle de vie d’un composant, Angular nous offre un
ensemble d’interfaces à implémenter pour les différentes étapes du cycle
de vie.
L’ensemble des interfaces est disponible dans la librairie core
d’Angular
Chaque interface offre une seule méthode dont le nom suit la
convention suivante :
ng + NomDeL’Interface
Exemple : Pour l’interface OnInit nous avons la méthode ngOnInit
127
128
129
Interfaces de gestion du cycle de vie d’un
composant
ngOnChanges: Cette méthode est appelée lors d’un binding d’une
propriété d’une directive. La méthode reçoit en paramètre un objet
représentant les valeurs actuelles et les valeurs précédentes disponibles
pour ce composant. Elle est appelé si un composant possède des input
(@Input) et à chaque fois qu’elles changent.
ngOnInit: Cette méthode initialise le composant après qu’Angular ait
initialisé les propriétés du composant.
ngDoCheck: Appelé après chaque change détection.
130
Interaction entre composants
Parent Enfant
[propertyBinding]
@Input
Component Component
(eventBinding)
131
Pourquoi ?
Le père voit le fils, le fils ne voit pas le père
@Component({
selector: 'app-root',
template: `
Enfant <p>Je suis le composant père </p>
<forma-fils></forma-fils>
`,
styles:
})
export class AppComponent {
title = 'app works !';
}
132
Interaction du père vers le fils
Le père peut directement envoyer au fils des données par Property Binding
@Component({
selector: 'app-root',
template: `
Enfant <p>Je suis le composant père </p>
<forma-fils></forma-fils>
`,
styles:
})
export class AppComponent {
title = 'app works !';
}
133
Interaction du père vers le fils
Problème : Le père voit le fils mais pas ces propriétés !!! Solution : les rendre visible avec Input
@Component({
selector: 'app-root',
template: `
Enfant <p>Je suis le composant père </p>
<forma-fils></forma-fils>
`,
styles:
})
export class AppComponent {
title = 'app works !';
}
134
Interaction du père vers le fils
Problème : Le père voit le fils mais pas ces propriétés !!! Solution : les rendre visible avec Input
135
Two way Binding
[(ngModel)]
136
Signal Input
Pour que l’exemple précédent fonctionne, on avait deux méthodes :
Utiliser un setter
Utiliser le OnChange hook
La version 17.1 a vu Angular introduire le concept de Signal Input pour améliorer
l’interaction entre les composants.
Les Signal Input permettent de lier les valeurs des composants parents. Ces
valeurs sont exposées à l'aide d'un signal et peuvent changer au cours du cycle de
vie de votre composant.
import { Input, input } from '@angular/core';
isEvenn!: boolean;
// Sans Signal Input
@Input() isEven: number;
// Avec les signal Input
counter = input<number>();
137
Signal Input
Les Signal Input permettent d’exposer des // optional
paramètres au composant parent afin qu’il counter = input<number>();
puisse paramétrer le composant fils. // Required
counter = [Link]<number>();
Les Signal Input sont en lecture seule. // Valeur par défaut = 0
Seul Angular peut changer leur valeur suite à counter = [Link]<number>(0);
un changement côté parent.
Le Signal Input peut être optionnel ou counter = input(0,{
required alias: 'counter',
transform: (value: number) => value * 100,
Il peut avoir une valeur par défaut });
Faite en sorte que le composant fils affiche la couleur du background de son père
139
Interaction du fils vers le père
L’enfant ne peut pas voir le parent. Appel de l’enfant vers le parent. Que faire ?
Parent
import {Component} from '@angular/core';
@Component({
selector: 'bind-output',
Enfant template: `Bonjour je suis le fils`,
styleUrls: ['./[Link]']
})
export class OutputComponent {
valeur:number=0;
}
}
140
Interaction du fils vers le père
Solution : Pour entrer c’est un input pour sortir c’est surement un output. Externaliser un
évènement en utilisant l’Event Binding. import {Component, EventEmitter, Output} from
'@angular/core';
Parent @Component({
selector: 'bind-output',
template: `
<button (click)="incrementer()">+</button> `,
styleUrls: ['./[Link]']
Enfant })
export class OutputComponent {
valeur:number=0;
// On déclare un evenement
@Output() valueChange=new EventEmitter();
Au click sur l’event incrementer(){
valueChange je vais [Link]++;
emmètre une réponse [Link]
contenant ce que je veux [Link]);
}
envoyer à mon père
}}
141
Interaction du père vers le fils
La variable $event est la variable utilisée pour faire transiter les informations.
import {Component, EventEmitter, Output} from import { Component } from
'@angular/core'; '@angular/core';
Parent @Component({ @Component({
selector: 'bind-output', selector: 'app-root',
template: ` template: `
<button (click)="incrementer()">+</button> `, <h2> {{result}}</h2>
Enfant styleUrls: ['./[Link]'] <bind-output
}) (valueChange)="showValue($event)
export class OutputComponent { "></bind-output>
valeur:number=0; `,
Mon père va ensuite // On déclare un evenement styles: [``],
@Output() valueChange=new EventEmitter(); })
intercepter l’event et export class AppComponent {
incrementer(){
récupérer ce que je lui [Link]++; title = 'app works !';
ai envoyé à travers la [Link]( result:any='N/A';
variable $event et va [Link] showValue(value){
l’utiliser comme il veut ); [Link]=value;
} }
}} } Parent
Enfant
142
Signal Output
L'API output() remplace directement le décorateur @Output() traditionnel.
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 que
l'approche traditionnelle @Output et EventEmitter.
La syntaxe a été simplifiée. /* Sans les signal Output */
@Output() oldSelectCv = new EventEmitter<Cv>();
/* Avec les signal Output */
selectCv = output<Cv>();
<app-item (selectCv)="onSelectCv($event)" [cv]="cv" /> onClick() {
Pere if ([Link]) {
/* Cette partie du code reste la même */
[Link]([Link]);
}
Fils 143
Exercice
144
Exercice
Le but de cet exercice est de AppComponent
créer une mini plateforme de CvComponent
ListeComponent
recrutement. DetailComponent
La première étape est de créer ItemComponent
la structure suivante avec une
vue contenant deux parties :
Liste des Cvs inscrits
Détail du Cv qui
apparaitra au click
Il vous est demandé juste
d’afficher un seul Cv et de lui
afficher les détails au click.
Il faudra suivra cette architecture.
145
Exercice
Le but de cet exercice est de créer
une mini plateforme de
recrutement.
146
Exercice
AppComponent
CvComponent
ListeComponent
DetailComponent
ItemComponent
147
Exercice
AppComponent
CvComponent
ListeComponent
DetailComponent
ItemComponent
148
Exercice
Un cv est caractérisé par :
id
name
firstname
Age
Cin
Job
path
149
Angular
Les directives
AYMEN SELLAOUTI
150
Plan du Cours
1. Introduction
2. Les composants
3. Les directives
3 Bis. Les pipes
4. Service et injection de dépendances
5. Le routage
6. Form
7. HTTP
8. Les modules
9. Les tests unitaires
151
Objectifs
1. Comprendre la définition et l’intérêt des directives.
2. Voir quelques directives d’attributs offertes par angular et savoir les utiliser
4. Voir quelques directives structurelles offertes par angular et savoir les utiliser
152
Qu’est ce qu’une directive
Une directive est une classe permettant import { Directive } from '@angular/core';
d’attacher un comportement aux
éléments du DOM. Elle est décorée @Directive({
avec l’annotation @Directive. selector: '[appHighlight]',
standalone: true
})
Apparait par défaut dans un élément export class HighlightDirective {
comme un tag (comme le font les
attributs). constructor() { }
153
Qu’est ce qu’une directive
La documentation officielle d’Angular identifie trois types de directives :
154
Les directives d’attribut (ngStyle)
Cette directive permet de modifier l’apparence de l’élément cible.
155
Les directives d’attribut (ngStyle)
import { Component} from import { Component} from
'@angular/core'; '@angular/core';
@Component({
@Component({ selector: 'direct-direct',
selector: 'direct-direct', template: `
template: ` <p [ngStyle]="{'color':myColor,'font-
<p [ngStyle]="{'color':'red', family':myfont,'background-color' :
'font-family':'garamond', myBackground}">
'background-color' : 'yellow'}"> <ng-content></ng-content>
<ng-content></ng-content> </p>`,
</p> styleUrls: ['./[Link]']
`, })
styleUrls: ['./[Link]'] export class DirectComponent{
}) private myfont:string="garamond";
export class DirectComponent{ private myColor:string="red";
} private myBackground:string="blue"
}
156
Les directives d’attribut (ngStyle)
157
Les directives d’attribut (ngStyle)
import {Component, Input} from
'@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'direct-direct',
template: ` template: `
<direct-direct [myColor]="gray">J'ai <p [ngStyle]="{'color':myColor,
été stylé par ngStyle</direct-direct> 'font-familly':myfont,
`, 'background-color' : myBackground}">
styles: [` <ng-content></ng-content>
h1 { font-weight: normal; } </p>
p{color:yellow;background-color: red} `,
`], styleUrls: ['./[Link]']
}) })
export class AppComponent { export class DirectComponent{
private myfont:string="garamond";
} @Input() private myColor:string="red";
private myBackground:string="blue"
}
158
Les directives d’attribut (ngStyle)
159
Exercice
Nous voulons simuler un Mini Word pour gérer un paragraphe en utilisant ngStyle.
Préparer un input de type texte, un input de type number, et un select box.
Faite en sorte que lorsqu’on écrive une couleur dans le texte input, ca devienne la couleur du
paragraphe. Et que lorsque on change le nombre dans le number input la taille de l’écriture.
Finalement ajouter une liste et mettez y un ensemble de police. Lorsque le user sélectionne une
police dans la liste, la police dans le paragraphe change.
160
Les directives d’attribut (ngClass)
Cette directive permet de modifier l’attribut class.
161
Les directives d’attribut (ngClass)
import {Component, Input} from '@angular/core'; // Tableau
@Component({ <div [ngClass]="['colorer', 'arrierplan'] "
selector: 'direct-direct',
class="encadrer">
template: `
// Objet
<div ngClass="colorer arrierplan" class="encadrer">
test ngClass <div [ngClass]="{ colorer: isColoree,
</div> arrierplan: isArrierPlan} "
`, class="encadrer">
styles: [`
.encadrer{ border: inset 3px black; }
.colorer{ color: blueviolet; }
.arrierplan{background-color: salmon; }
`]
})
export class DirectComponent{
private myfont:string="garamond";
@Input() private myColor:string="red";
private myBackground:string="blue«
private isColoree:boolean=true;
private isArrierPlan:boolean=true
}
162
Exercice
163
Customiser un attribut directive
Afin de créer sa propre « attribut directive » il faut utiliser un HostBinding sur
la propriété que vous voulez binder.
Exemple : @HostBinding('[Link]')
bg:string="red";
164
Exercice
Un truc plus sympas on va créer un simulateur d’écriture arc en ciel.
Créer une directive
Créer un hostbinding sur la couleur et la couleur de la bordure.
Créer un tableau de couleur dans votre directive.
Faite en sorte qu’en appliquant votre directive à un input, à chaque
écriture d’une lettre (event keyup) la couleur change en prenant
aléatoirement l’une des couleurs de votre tableau. Pensez à utiliser
[Link]() qui vous retourne une valeur entre 0 et 1.
165
Exercice
166
Customiser une attribut directive
Nous pouvons aussi utiliser le input afin de rendre notre directive paramétrable
Tous les paramètres de la directive peuvent être mises en input puis récupérer à
partir de la cible.
Exemple
<direct-direct [myColor]="gray">
167
Les directives structurelles
Une directive structurelle permet de modifier le DOM.
*ngIf
*ngFor
[ngSwitch]
168
Les directives structurelles *ngIf
Prend un booléen en paramètre.
Exemple
<p *ngIf="true">
Je suis visible :D</p>
<p *ngIf="false">
Le *ngIf c'est faché contre
moi et m'a caché :(
</p>
169
Exercice
Teston *ngIf en créant un composant contenant un bouton et un
paragraphe.
170
Les directives structurelles *ngFor
Permet de répéter un élément plusieurs fois dans le DOM.
171
Angular
Les pipes
AYMEN SELLAOUTI
172
Références
173
Plan du Cours
1. Introduction
2. Les composants
3. Les directives
3 Bis. Les pipes
4. Service et injection de dépendances
5. Le routage
6. Form
7. HTTP
8. Les modules
9. Les tests unitaires
174
Objectifs
1. Définir les pipes et l’intérêt de les utiliser
175
Qu’est ce qu’un pipe
Un pipe est une fonctionnalité qui permet de formater et de
transformer vos données avant de les afficher dans vos Templates.
Exemple l’affichage d’une date selon un certain format.
Il existe des pipes offerts par Angular et prêt à l’emploi.
Vous pouvez créer vos propres pipes.
{{ variable | nomDuPipe }}
177
Les pipes disponibles par défaut (Built-in
pipes)
La documentation d’angular vous offre la liste des pipes prêt à l’emploi.
[Link]
uppercase
lowercase
titlecase
currency
date
json
percent
…
178
Paramétrer un pipe
Afin de paramétrer les pipes ajouter ‘:’ après le pipe suivi de votre paramètre.
{{ maDate | date:"MM/dd/yy" }}
{{ nom | sli[Link] }}
179
Pipe personnalisé
Un pipe personnalisé est une classe décoré avec le décorateur @Pipe.
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
180
Exemple de pipe
import { Pipe, PipeTransform } from
<li>
'@angular/core';
<ol *ngFor="let team of
teams">
@Pipe({
{{team | team}}
name: 'team’,
</ol>
standalone: true,
</li>
})
export class TeamPipe implements PipeTransform {
ngOnInit() {
transform(value: any, args?: any): any { [Link] = ['milan', 'barca', 'roma'];
switch (value) { }
case 'barca' : return ' blaugrana';
case 'roma' : return ' giallorossa';
case 'milan' : return ' rossoneri';
}
}
181
Pipe pure et impure
Par défaut un pipe t considéré comme une fonction pure
Une fonction est dite pure si elle :
Ne provoque pas de side effect.
Renvoie la même valeur pour les mêmes paramètres.
Lorsque le pipe est pur, la méthode transform() est invoquée
uniquement lorsque ses arguments d'entrée changent.
@Pipe({
name: ‘myPipe’,
standalone: true,
pure: true
}) 182
Exercice
Créer un composant contenant un input de type texte et une ol
A chaque fois que vous écrivez dans l’input, afficher le contenu dans
un paragraphe (utilisez ngModel).
Dans le composant initialiser un tableau avec 100 valeurs entre 20 et
30.
Dans le ol afficher la valeur de chacun des éléments du tableau et en
faça la valeur de cet élément lorsque on lui applique la fonction suivante:
f(x) = 2f(x-1) + 3f(x-2) si n> 1 et f(n) = 1 si n== 0 ou 1
Tester l’input. Que remarquez vous lorsque vous écrivez dans l’input ?
183
Memo
Il existe une bibliothèque memo-decorator qui permet de mémoriser
des fonctions pures.
Elle permet en l’utilisant de cacher les résultats des fonctions pures et
de les réutiliser.
Pour l’installer utiliser la commande npm i memo-decorator
185
Exercice
Créer un pipe appelé defaultImage qui retourne le nom d’une image par
défaut que vous stockerez dans vos assets au cas ou la valeur fournie au
pipe est une chaine vide ou ne contient que des espaces.
186
Angular
Service et injection de
dépendances
AYMEN SELLAOUTI
187
Références
188
Plan du Cours
1. Introduction
2. Les composants
3. Les directives
3 Bis. Les pipes
4. Service et injection de dépendances
5. Le routage
6. Form
7. HTTP
8. Les modules
9. Les tests unitaires
189
Objectifs
1. Définir un service
3. Injecter un service
190
Qu’est ce qu’un service ?
Un service est une classe qui permet d’exécuter un traitement.
191
Qu’est ce qu’un service ?
f(){};
g(){};
192
Qu’est ce qu’un service ?
Un service est un médiateur entre la vue et la logique
193
Qu’est ce qu’un service ?
Un service peut donc :
194
Création d’un service
Via CLI
ng g s nomDuService
195
Premier Service
@Injectable()
export class FirstService {
constructor() { }
196
Injection de dépendance (DI)
L’injection de dépendance est un patron de conception.
Classe A1{
Classe A2{ Classe A3{
ClasseB b;
ClasseB b; ClasseC c;
ClasseC c;
… …
…
} }
}
INJECTOR
198
Injection de dépendance (DI)
199
Injection de dépendance (DI)
Comment les injecter ?
200
Injection de dépendance (DI)
L’injection de dépendance utilise les étapes suivantes :
201
Injection de dépendance (DI)
Que peut on injecter
Toute dépendance de votre classe, à savoir :
Des instances de classes
Des constantes
202
Injection de dépendance (DI)
Avantages
L’intérêt de l’injection de dépendance est donc :
Couplage lâche
Facilement remplacer une implémentation d'une dépendance
à des fins de test
Prendre en charge plusieurs environnements d'exécution
Fournir de nouvelles versions d'un service à un tiers qui utilise
votre service dans sa base de code, etc.
[Link] 203
Injection de dépendance (DI)
Comment ca fonctionne
Injection de dépendance => Il nous faut donc une dépendance
Afin de Lier cette dépendance au système d’injection de
dépendance d’Angular nous devons répondre à deux questions
Comment Angular va créer la dépendance ?
Quand est ce qu’Angular doit utiliser cette dépendance ?
Le moyen permettant de spécifier au système d’injection de
dépendance d’Angular comment créer la dépendance est la fonction
Provider factory.
Une Provider factory est simplement une fonction simple
qu'Angular peut appeler afin de créer une dépendance.
204
Injection de dépendance (DI)
Comment ca fonctionne
Cette fonction peut être créée implicitement par Angular en
utilisant quelques conventions simples (le cas le plus répondu) ou
par vous même.
Ceci implique que pour toute dépendance de votre application
quelque soit son type, il existe une Provider Factory qui sait
comment la créer et qui le fait.
205
Injection de dépendance (DI)
Comment ca fonctionne
206
Injection de dépendance (DI)
Associer votre Factory Provider à Angular
Token
Maintenant il reste à dire à Angular quand utiliser ce provider.
Donc, nous devons répondre à cette interrogation : Comment
Angular sait-il quoi injecter, et donc quelle Provider factory
appeler pour créer quelle dépendance ?
Pour ce faire, vous pouvez utilisez des Tokens.
Un Token peut avoir plusieurs formes et il a pour rôle d’identifier
une Provider Factory.
207
Injection de dépendance (DI)
Associer votre Factory Provider à Angular
Angular injection token
La première forme de Token est l’Angular injection token
C’est une instance de la class InjectionToken
Son rôle est d’identifier le service dans le système d’injection de
dépendance
208
Injection de dépendance (DI)
Associer votre Factory Provider à Angular
Configurer le Provider
Maintenant que nous avons notre Provider Factory et notre Token,
nous devons configurer Angular pour qu’ils les prennent en
considé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
209
Injection de dépendance (DI)
Associer votre Factory Provider à Angular
Configurer le Provider
export const TODOS_SERVICE_TOKEN =
new InjectionToken<TodoService>("TODO_SERVICE_TOKEN");
function todoServiceProviderFactory(http:HttpClient): TodoService {
return new TodoService(http);
}
providers: [
{
provide: TODOS_SERVICE_TOKEN,
useFactory: todoServiceProviderFactory,
deps: [HttpClient]
}
]
[Link]
210
Injection de dépendance (DI)
Associer votre Factory Provider à Angular
Injecter la factory
Il reste une dernière étape, à savoir comment injecter notre
dépendance dans notre classe.
On utilise le décorateur @Inject au niveau du constructeur et on
lui passe le Token du Provider Factory que nous voulons injecter.
constructor(
@Inject(TODOS_SERVICE_TOKEN) private todoService: TodoService
) {}
211
Les providers personnalisés
Les 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.
212
Les providers personnalisés
Les autres formes de Tokens
providers: [
{
provide: TodoService,
useFactory: todoServiceProviderFactory,
deps: [HttpClient]
}
],
constructor(
@Inject(TodoService) private todoService: TodoService
) {}
213
Les providers personnalisés
useClass
Une autre option s’offre à vous, et au lieu de spécifier la
Fonction du Provider Factory avec useFactory, vous pouvez
utilisez la clé useClass.
En utilisant useClass, Angular saura que la valeur que nous
transmettons est un constructeur valide, qu'Angular peut
simplement appeler en utilisant l’opérateur new.
214
Les providers personnalisés
useClass
providers: [
{
provide: TodoService,
useClass: TodoService,
deps: [HttpClient]
}
],
constructor(
@Inject(TodoService) private todoService: TodoService
) {}
215
Les providers personnalisés
useClass
Une autre fonctionnalité très pratique de useClass est que
pour ce type de dépendances, 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.
Cela signifie qu'avec les dépendances useClass, nous n'avons
même plus besoin du décorateur Inject, ce qui explique
pourquoi vous le voyez rarement.
216
Les providers personnalisés
useClass
providers: [
{
provide: TodoService, Le Token est déterminé par
useClass: TodoService, Angular en utilisant le Type
deps: [HttpClient] TodoService
}
],
217
@Injectable
C’est un décorateur permettant de rendre une classe injectable
Une classe est dite injectable si on peut y injecter des dépendances
@Component, @Pipe, et @Directive sont des sous classes de
@Injectable(), ceci explique le fait qu’on peut y injecter directement des
dépendances.
Si vous n’aller injecter aucun service dans votre service, cette
annotation n’est plus nécessaire.
Remarque : Angular conseille de toujours mettre cette annotation.
218
Les providers personnalisés
useClass
En utilisant le décorateur @Injectable, votre Provider
devient encore plus simple puisqu’on n’a plus à spécifier les
dépendances qui seront directement déterminé au niveau
du constructeur par le Système d’Injection de dépendance
d’Angular
providers: [ providers: [ providers: [
{ { {
provide: TodoService, provide: TodoService, TodoService,
useClass: TodoService, useClass: TodoService, }
deps: [HttpClient] } ],
} ],
],
219
Les providers personnalisés
useClass
La syntaxe useClass est aussi utile pour injecter dynamiquement une classe.
Imaginez que le service de log dépend de l’environnement de développement.
@Module({ export const appConfig: ApplicationConfig = {
providers: [ providers: [
{ {
provide: LoggerService,
provide: LoggerService,
useClass:
useClass:
[Link] === 'development'
[Link] === 'development' ? DevelopmentLoggerService
? DevelopmentLoggerService : ProductionLoggerService,
: ProductionLoggerService, }
} ],
], };
}) export class AppModule {}
220
Exercice
Nous supposons que votre API est encore en phase de teste ou est en
maintenance. Vous voulez continuer à travailler avec votre
CvComponent.
Créer un service FakeCvService
Fait en sorte d’avoir une fonction getCvs qui retourne un tableau de
cvs
Dans votre cvComponent provider ce fake service à la place du vrai
en vous basant sur une configuration.
Vérifier le bon fonctionnement
Remarque: pour créer un observable, vous pouvez utilisez l’opérateur
de création of.
221
Les providers personnalisés
multi
La plupart des dépendances de notre système correspondront à une
seule valeur, comme par exemple une classe.
Cependant, il y a des occasions où nous voulons avoir plusieurs
instances 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
d’instance.
const AuthentificationInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: AuthentificationInterceptor,
multi: true
};
222
Injection de dépendance (DI)
223
Injection de dépendance (DI)
224
Standalone Component
Les composants autonomes
Configurez l’injection de dépendance
Afin de fournir un provider pour toute l’application, vous pouvez
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.
Vous pouvez aussi récupérer des providers offert par un module
avec la fonction importProvidersFrom(moduleCible).
A partir de la version 15, vous avez certaines fonctions spécifiques
pour les modules les plus utilisés comme le http avec sa fonction
provideHttpClient(), ou provideRouter(APP_ROUTES).
225
Injection de dépendance (DI)
import { Component, OnInit } from '@angular/core';
import {Cv} from './cv';
import {CvService} from "../[Link]";
@Component({
selector: 'app-cv',
templateUrl: './[Link]',
styleUrls: ['./[Link]'],
providers:[CvService] // on peut aussi l’importer ici
})
export class CvComponent implements OnInit {
selectedCv : Cv;
constructor(private monPremierService:CvService) { }
ngOnInit() {
}
}
226
Les providers personnalisés
la fonction inject (après Angular 14)
La fonction inject vous 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.
Vous pouvez maintenant l’utiliser dans vos composants, directives
et pipes.
Le premier intérêt est le type safety avec le décorateur @Inject.
Il facilite aussi l’héritage en externalisant le dépendance du
composant.
227
Chargement automatique du service
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 et sa propriété providedIn. Vous pouvez charger le service
dans toute l’application via le mot clé root.
Si vous voulez charger le service dans un module particulier vous l’importer et
vous le mettez à la place de ‘root’. import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class CvService {
constructor() { }
}
228
Chargement automatique du service
providedIn
La clé providedIn peut prendre les valeurs suivantes :
root : L'injecteur au niveau de l'application, c’est ce que vous trouvez
dans la plupart des applications.
platform : Un injecteur de plateforme singleton spécial partagé par
toutes les applications de la page.
any : Fournit une instance unique dans chaque module chargé d’une
manière lazy tandis que tous les modules chargés en eager partagent
une instance. Cette option est obsolète.
providedIn?: Type<any> | 'root' | 'platform' | 'any' | null;
229
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.
230
Exercice
Créons un service de Todo, le Model et le composant qui va avec. Un Todo est
caractérisé par un nom et un contenu.
Ce service permettra de faire les fonctionnalités suivantes :
Logger un Todo
Ajouter un Todo
Supprimer un Todo
231
Autres providers
Dans certains cas d’utilisation, l’utilisation standard des
providers ne convient pas, imaginer l’un des cas suivants :
Vous souhaiter créer une instance personnalisée au lieu de laisser le
container le faire pour vous.
Vous voulez injecter une bibliothèque externe
Vous voulez mocker une classe pour le teste
Vous voulez injecter des instances différentes selon le contexte
…
Angular nous permet de définir des providers particuliers
selon votre besoin.
232
Les providers personnalisés
useValue
La syntaxe useValue est utile pour injecter
Une valeur constante,
Une bibliothèque externe
Remplacer une implémentation réelle par un objet fictif.
providers: [
{
useValue: [{ lundi: ‘Angular' }, { mardi: 'Still Angular' }],
provide: 'TODOS_LIST',
},
TodoService,
],
233
Les providers personnalisés
useValue
Si vous injecter une classe, l’utilisation de l’injection via le
constructeur reste d’actualité.
Sinon, pour injecter ce provider utiliser la syntaxe @Inject,
qui prend en paramètre le Token.
providers: [ constructor(
{ @Inject('TODOS_LIST') todoList,
provide: 'TODOS_LIST', ){
useValue: [{ lundi: 'nestJs' }, { mardi: 'Still NestJs' }], [Link]('Fake Todo List', todoList);
}, }
TodoService,
], @Controller('todo')
export class TodoController {
@Inject('TODOS_LIST') todoList;
constructor() {}
234
Exercice
Provider la fonction uuid
Faite en sorte de l’utiliser dans le TodoService
235
Les providers personnalisés
useExisting
Avec useExisting, vous créer un alias pour un provider déjà
existant.
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 de
nombreuses méthodes sur le service et que nous ne voulons
en utiliser que quelques-unes. Cela aide à réduire la taille de
l'interface.
236
Les providers personnalisés
useExisting
class CarService { export abstract class CarSizeService {
getWeight(): number {...} abstract getHeight: number;
abstract getWidth: number;
getColor(): string {...} }
getName(): string {...}
getWidth(): number {...}
getHeight(): number { … }
getModel(): string {...}
getYear(): number {...}
...
}
providers: [{ provide: CarSizeService, useExisting: CarService}]
237
DI Hiérarchique
Dans Angular vous disposez de plusieurs endroits où vous pouvez
définir les fournisseurs pour vos dépendances :
Module
Composant
Directive !
238
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 Injector
Hierarchy)
Une hiérarchie d’injecteur niveau module.
239
DI Hiérarchique
Hiérarchie d’injecteur niveau composant
Le système d’injection de dépendance d’Angular est hiérarchique.
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 :
Il l’injecte
ok
Injection d’un Vérification de
service dans un l’injecteur à ce
Tant que il ne le
composant niveau
Vérifie dans le trouve pas et qu’on est
père pas arrivé dans la
racine
240
DI Hiérarchique
Hiérarchie d’injecteur niveau Module
Si Angular ne trouve pas de provider au niveau de la hiérarchie des
composants, il va aller voir dans la hiérarchie de module.
Le ModuleInjector peut être configuré de deux manières en utilisant :
La propriété @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 [Link] de manière récursive.
241
242
Exercice
Ajouter les services suivants afin d’améliorer la gestion de notre
plateforme d’embauche.
Un premier service CvService qui gérera les Cvs. Pour le moment
c’est lui qui contiendra la liste des cvs que nous avons.
Ajouter aussi un composant pour afficher la liste des cvs embauchées
ainsi qu’un service EmbaucheService qui gérer les embauches.
Au click sur le bouton embaucher d’un Cv, le cv est ajouté à la liste
des personnes embauchées et une liste des embauchées apparait.
243
Exercice
244
Exercice
245
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Resolution Modifier
Le mécanisme de DI d’Angular est configurable
Il existe un ensemble de décorateurs qui vous permettent d’adapter le
comportement par défaut.
@Optional
@SkipSelf
@Self
@Host
246
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Résolution Modifier : @Optional
@Optional indique qu'une dépendance peut être absente.
Ceci va dire à Angular de retourner null s’il ne trouve pas la
dépendance au lieu de déclencher une erreur.
Ceci est utile lorsque vous voulez permettre à un composant de
fonctionner même si une dépendance spécifique n'est pas disponible.
Afin de prévenir ca, ajouter le décorateur @Optional devant l’injection.
Cependant, il faut gérer le cas ou l’injection ne se fait pas.
247
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Résolution Modifier : @Optional
constructor(
@Optional()private logger: LoggerService
){
if ([Link]) {
[Link]('logged From logger service :D');
} else {
[Link]('Logged from the external console log')
}
}
248
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Résolution Modifier : @Self
Si vous voulez considérer uniquement que le provider qui est
fourni au niveau du composant et pas dans la hiérarchie de DI,
vous pouvez utiliser le décorateur @Self.
constructor(
@Optional() @Self() private logger: LoggerService
){
if ([Link]) {
[Link]('logged From logger service :D');
} else {
[Link](
`No provider in my Providers array,
So I’am Logged from the external console log`
)
}
} 249
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Résolution Modifier : @SkipSelf
Par défaut la hiérarchie de DI commence par chercher un provider
au niveau du Composant lui-même.
Imaginez que vous voulez provider un service pour vos fils, mais que
vous voulez récupérer une instance dans la hiérarchie qui commence par
le composant parent.
Le décorateur @SkipSelf vous permet de le faire.
250
DI Hiérarchique
Configurer le Mechanism de DI en utilisant
les Résolution Modifier : @Host
Le décorateur @Host arrête la recherche au niveau du composant hôte.
Ce qui ressemble à @Self mais avec certaines spécificités.
Le composant hôte est généralement le composant demandant la
dépendance.
Cependant, lorsque ce composant est projeté ( avec ng-content ) dans un
composant parent, ce composant parent devient l'hôte et il est utilisé en
second lieu, donc si l’instance ne se trouve pas dans le composant lui-
même on cherche dans le composant dans lequel il est projeté.
Si vous faite une injection dans une directive, le composant appelant la
directive devient l’hôte. Cependant il faut utiliser la clé viewProviders
251
Les services Angular
Angular vous fournit certains service prêt à l’empoi et vous permettant
certaines fonctionnalités.
Le Core Angular vous fournit 2 services très utiles :
Title
Meta
252
Les services Angular
Title
Le service Title vous permet de manipuler la balise title de la page
active.
Angular étant une SPA, vous ne pouvez pas modifier directement la
balise title dans votre ‘page’
Pour se faire injecter le service Title dans votre page component
Ce service vous offre deux méthode :
setTitle pour modifier le titre
getTitle pour récupérer le titre
253
[Link]({
Les services Angular name: 'author', content: 'aymen'
Meta });
255
Angular
Routing
AYMEN SELLAOUTI
256
Objectifs
1. Définir le routeur d’Angular
2. Définir une route
3. Déclencher une route à partir d’un composant
4. Ajouter des paramètres à une route
5. Récupérer les paramètres d’une route à partir du composant.
6. Préfixer un ensemble de routes
7. Gérer les routes inexistantes
257
Qu’est ce que le routing
Tout système de routing permet d’associer une route à un traitement
Angular SPA. Pourquoi parle-on de route ??
Séparer différentes fonctionnalités du système
Maintenir l’état de l’application
Ajouter des règles de protection
Que risque t-on d’avoir si on n’utilise pas un système de routing ?
260
Préparer l’emplacement d’affichage des
vues correspondantes aux routes
@Component({ Header
selector: "app-root", <hr>
standalone: true, <router-outlet />
imports: [RouterOutlet], <hr>
templateUrl: "./[Link]", Footer
styleUrl: "./[Link]", HTML
})
export class AppComponent {
title = "ng18";
}
TS
261
Syntaxe minimaliste d’une route
Une route est un objet.
path permet de spécifier l’URI. Cette url ne doit pas commencer par un /
262
Exercice
Configurer votre routing
263
Déclencher une route routerLink
L’idée intuitive pour déclencher une route est d’utiliser la balise a et
son attribut href. Est-ce que ca risque de poser un problème ?
L’utilisation de <a href > va déclencher le chargement de la page ce
qui est inconcevable pour une SPA.
La solution proposée par le router d’Angular est l’utilisation de la directive
routerLink qui comme son nom l’indique liera la directive à la route que
nous souhaitons déclencher sans recharger la page.
Exemple :
<li ><a [routerLink]="[‘todo’]" routerLinkActive="active">Gérer les
cvs</a></li>
264
Déclencher une route routerLink
routerLinkActive="active" va associer la classe active à l’uri cible ainisi qu’à
tous ces ses ancetres.
Par exemple si on a l’uri ‘cv/liste’ la classe active sera ajouté à cet uri ainsi
qu’à l’uri ‘cv’ et ‘’.
Pour identifier uniquement l’uri cible, ajouter la directive suivante :
[routerLinkActiveOptions]="{exact: true}”
265
Exercice
Faites en sorte d’avoir un composant Header dans votre application qui permet
d’afficher l’ensemble de vos liens.
En cliquant sur un lien, le composant qui lui est associé doit être affiché.
266
Déclencher une route à partir du
composant
Afin de déclencher une route à travers le composant on utilise le service
Router et sa méthode navigate.
267
Déclencher une route à partir du
composant
import { Component} from '@angular/core';
import {Router} from "@angular/router";
@Component({
selector: 'app-home',
templateUrl: './[Link]',
styleUrls: ['./[Link]']
})
export class HomeComponent{
constructor(private router:Router) { }
onNaviger(){
[Link](['/about/10']);
}
268
Le service Router
Le service Router d'Angular fournit des fonctionnalités de
navigation de route pour votre application.
Il permet de naviguer vers des routes définies en utilisant la méthode
navigate() ou navigateByUrl(), de s'abonner aux événements de
navigation en utilisant la propriété events, et de récupérer des
informations sur la route active en utilisant la propriété url ou la
propriété routerState.
269
Exercice
Créer un composant appelé RouerSimulator
Dans ce composant créer une liste déroulante contenat le nom des différentes
routes de votre application.
270
Exercice : RouterSimulator
271
Les paramètres d’une route
Afin de spécifier à notre router qu’un segment d’une route est un
paramètre, il suffit d’y ajouter ‘:’ devant le nom de ce segment.
Exemple
/cv/:id permet de dire que la root contient au début cv ensuite un
paramètre de root appelé id.
272
Récupérer les paramètres d’une route
ActivatedRoute
Afin de récupérer les paramètres d’une route au niveau d’un composant,
Angular nous fournit un service qui gère la route active, c’est le
ActivatedRoute
L'objet ActivatedRoute dans Angular est un objet qui contient des
informations sur la route actuellement activée.
Il est généralement utilisé pour accéder aux informations de route
telles que les paramètres de route, les données de route et les
paramètres de requête.
Il est également utilisé pour souscrire aux changements de route.
273
Récupérer les paramètres d’une route
ActivatedRoute
params: Retourne un observable des paramètres de route actuels.
queryParams: Retourne un observable des paramètres de requête
actuels.
data: Retourne un observable des données de route associées à la
route actuelle.
snapshot: Retourne un instantané de la route actuelle.
url: Retourne un observable de l'URL de la route actuelle.
parent: Retourne l'instance de ActivatedRoute de la route parente.
firstChild: Retourne l'instance de ActivatedRoute du premier enfant
de la route actuelle.
274
Récupérer les paramètres d’une route
ActivatedRoute
children: Retourne un tableau d'instances de ActivatedRoute des
enfants de la route actuelle.
paramMap: Retourne un observable qui contient les paramètres de
route sous forme de map.
queryParamMap: Retourne un observable qui contient les
paramètres de requête sous forme de map.
275
Récupérer les paramètres d’une route
ActivatedRoute
276
Récupérer les paramètres d’une route
ActivatedRoute / snapshot
La propriété snapshot de l'objet ActivatedRoute contient un instantané de
l'état de la route actuelle.
Elle contient des informations telles que l'URL actuelle, les paramètres
de route actuels,…
Elle est généralement utilisée pour accéder aux informations de route
dans un composant lors de son initialisation.
Il est important de noter que l'instantané de la route ne change pas
lorsque la route change. Il représente un état figé de la route lors de son
instanciation.
277
Récupérer les paramètres d’une route
ActivatedRoute / snapshot
Voici quelques propriétés courantes de l'API snapshot :
url: Retourne 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.
queryParams: Retourne un objet qui contient les paramètres de requête
actuels.
fragment: Retourne la partie de l'URL après le symbole "#".
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.
278
Récupérer les paramètres d’une route
ActivatedRoute / snapshot
279
Récupérer les paramètres d’une route
ActivatedRoute / snapshot
Donc pour accéder à votre propriété, passez par l’objet snapshot
Avec snapshot, vous avez deux méthodes pour récupérer les
paramètres:
Via la propriété params qui retourne un tableau d’objet des paramètres
Via la propriété paramMap
Appeler sa méthode get
Passez lui le nom de la propriété souhaitée.
[Link]('id')
280
Exercice
Reprendre le composant qui permet
de changer la couleur de la DIV
281
Passer le paramètre à travers le tableau de
routerLink
Une autre méthode permet de passer le paramètre de la route est en
l’ajoutant comme un autre attribut du tableau associé au routerLink
import { Component, OnInit } from '@angular/core';
import {Router} from "@angular/router";
@Component({
selector: 'app-home',
templateUrl: './[Link]',
styleUrls: ['./[Link]']
})
export class HomeComponent{
constructor(private router:Router) { }
id:number=10;
onNaviger(){[Link](['/about',[Link]]);}
}
282
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.
L’une des propriétés de cet objet est aussi un objet dont la clé est
queryParams dont le contenu est aussi un objet content les
identifiants des queryParams et leurs valeurs.
284
Récupérer Les queryParameters
Les queryParameters sont récupérable de la même façon que les
paramètres. Soit d’une façon statique avec snapshot via la
propriété queryParams ou sa propriété queryParamMap et sa
méthode get.
Soit dynamiquement via l’observable queryParams
[Link]('id')
285
Route Fils
Certains composants ne sont visible qu’à l’intérieur d’autres
composants.
287
Route Fils
const CV_ROUTE: Routes = [
{
path: 'cv',
children: [
{path: '', component: CvComponent },
{path: 'detail/:id', component: DetailCvComponent },
{path: 'addPersonne', component: FormPersonneComponent },
]
}
];
288
Exercice
Modéliser un système de routage qui utilise ses propriétés.
289
Route fils / définition dans un parent
Supposons que nous voulons avoir un Template central avec des
données fixe et des parties variables dans le même template.
290
Route Fils
Afin de mettre en place ce processus nous procédons comme suit :
Nous définissons le préfixe avec la propriété path. On lui associe
le composant Père.
Nous y ajoutons la propriété children qui contiendra le tableau
des routes. Chaque route de ce tableau sera préfixé avec la route
définie dans path.
Nous ajoutons la balise <router-outlet></router-outlet> dans le
Template père.
291
Route Fils
292
Master Detail Implementation
Le pattern "master-detail" dans Angular est un modèle
d'architecture utilisé pour afficher des données dans une interface
utilisateur.
Il consiste à avoir une vue "maître" qui affiche une liste
d'é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 les
différents éléments et de voir les détails correspondants sans avoir à
charger une nouvelle page.
293
Exercice
Master Detail Implementation
Nous voulons implémenter le pattern Master Details pour notre
liste de cvs.
Créer un nouveau composant MasterDetailsCv.
Il devra permettre l’affichage de la liste des cvs. Au click un détail du
cv sélectionné devra apparaitre.
294
Exercice
Master Detail Implementation
295
Redirection
Afin de rediriger une route il suffit d’ajouter une propriété dans
l’objet route qui est redirectTo. Cette propriété permet d’indiquer
vers quelle route le path doit être redirigé. Si la route n’a pas encore
été matché, alors les routes commençant par ce path seront
redirigées.
Une autre propriété peut être utilisé qui est la propriété pathMatch.
Cette propriété permet de définir comment le matching des path est
exécuté. Avec la valeur ‘full’, elle spécifie au routeur de ne faire la
redirection que si le path exact est matché.
296
Redirection : exemple
const APP_Routes:Routes =[
{path:'',component:HomeComponent},
{path:'about',redirectTo:'', pathMatch:'full'},
{path:'about/:param',component:AboutComponent},
{path:'about/:param',component:AboutComponent,children:FILS_ROUTE},
]
297
Redirection : gestion d’erreurs de rooting
Afin de rediriger une route inexistante vers une page d’erreur, il
suffit de garder la même syntaxe de redirection et de mettre dans la
propriété path ’**’.
298
Exemple
const APP_ROUTE: Routes = [
{path: '', redirectTo: 'cv', pathMatch: 'full'},
{path: ‘cv', component: CvComponent},
{path: 'lampe', component: ColorComponent},
{path: 'login', component: LoginComponent},
{path: 'error', component: ErrorPageComponent},
{path: '**', component: ErrorPageComponent }
];
299
Exercice
Ajouter les fonctionnalités suivante à votre cvTech:
Une page détail qui va afficher les détails d’un cv.
Un bouton dans chaque cv qui au click vous envoi vers la page
détails.
Dans la page détail, un bouton delete qui au click supprime ce cv et
vous renvoi à la liste des cvs.
300
Router Resolver
Analysons la page DetailsCv
301
Router Resolver
Le Router Resolver d'Angular est un mécanisme qui permet de
résoudre des données avant qu'une route ne soit chargée.
Il permet de charger les données nécessaires pour afficher une
vue spécifique en utilisant un service qui est lié à la route.
Il est utilisé lorsque vous avez besoin de charger des données
avant d'afficher une vue, comme pour afficher des données d'un
utilisateur avant d'afficher sa page de profil.
Il est également utilisé pour éviter les erreurs de chargement de
vue en garantissant que les données nécessaires sont chargées
avant de naviguer vers une route.
302
Router Resolver
Le Router Resolver est 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 fournir
comme données.
Vous devez implémenter la fonction Resolve qui devra retourner
un objet de T ou une Promise<T> ou un Observable<T>.
Elle prend en paramètre un objet de type ActivatedRouteSnapshot
qui vous permet de récupérer les paramètre de votre route à
travers le paramètre paramMap et sa méthode get.
303
Router Resolver
304
Router Resolver
@Injectable({
providedIn: 'root',
})
export class CvResolver implements Resolve<Cv> {
resolve(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<Cv> | Cv {
const cvId = [Link]('id');
}
} 305
Router Resolver
Maintenant, afin de passez le résultat de votre resolver à votre
route, ajoutez une propriété resolve à votre route dans votre fichier
de routing et passer lui un object avec comme clé le nom que vous
voulez donner à votre propriété et comme valeur le resolver.
{
path: ':id',
component: DetailsCvComponent,
resolve: {
cv: CvResolver
}
},
306
Router Resolver
Maintenant, dans votre composant, injecter le service
ActivatedRoute.
Pour accéder à la valeur de retour du resolver (si c’est une Promise
ou un Observable, il attendra la valeur émise), accéder à la propriété
snapshot qui contient une propriété data qui contiendra le
champ.
Si vous voulez un accès dynamique, utilisez l’Observable data de
l’ActivatedRoute.
[Link]['cv'];
307
Exercice
Appliquez le resolver pour les composants detailsComponent et
CvComponent
308
Route Provider
A partir d’Angular 14
A partir d’Angular 14, la clé provider a été introduite dans l’objet
route.
Ceci permettra de provider des provider pour la route et ses enfants
{
path: 'routerProvider',
component: RouterPoviderComponent,
providers: [
{
provide: LoggerService,
useExisting: NewLoggerService
}
]
},
309
Route Provider
A partir d’Angular 14
Ceci va créer un nouvel Injector qui sera appelé juste après l’element
Injector
{
path: 'routerProvider',
component: RouterPoviderComponent,
providers: [ LoggerService ]
},
[Link] 310
[Link] 311
Le cycle de vie du routeur
Pour chaque navigation, une série d'étapes a lieu avant que le routeur
n'affiche les nouveaux composants à l'écran. C'est ce qu'on appelle le cycle de
vie de navigation du routeur.
Le résultat d'une navigation réussie est que les nouveaux composants seront
rendus à l'aide de <router-outlet>, et une arborescence de structures de données
ActivatedRoute sera créée en tant qu'enregistrement interrogeable de la
navigation.
Durant ce cycle de vie, des events vous permettent de vous y greffer.
Vous pouvez activer la visualisation de ces events en développement via l’option
enableTracing [Link](ROUTES, {
enableTracing: true
})
[Link] 312
Router Events
Le routeur d’Angular déclenche plusieurs événements vous permettant
de 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écutez
le traitement. [Link]((event) => {
if (event instanceof NavigationStart) {
[Link]('Navigation Start:D ', event);
} else if (event instanceof NavigationEnd) {
[Link]('Navigation END :( ', event);
}
}); 313
Router Events
Les événements du routeur Angular incluent:
NavigationStart: déclenché lorsqu'une nouvelle navigation est démarrée. Il
peut être utilisé pour déclencher un loader.
RoutesRecognized: déclenché lorsque le routeur a reconnu les routes
correspondant à 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 être
utilisé pour arrêter le loader
NavigationError: déclenché lorsqu'une erreur survient lors de la navigation.
Peut être utilisé pour arrêter le loader ou envoyer des log au serveurs.
314
Router Events
GuardsCheckStart: déclenché lorsque le routeur démarre la
vérification des gardes de route.
ChildActivationStart: déclenché lorsque l'activation d'un
composant enfant démarre.
ActivationStart: déclenché lorsque l'activation d'un composant
démarre.
GuardsCheckEnd: déclenché lorsque le routeur a terminé la
vérification des gardes de route.
ResolveStart: déclenché lorsque le routeur démarre la résolution des
données pour une route..
315
Router Events
ResolveEnd: déclenché lorsque le routeur a terminé la résolution des
données pour une route.
ActivationEnd: déclenché lorsque l'activation d'un composant est
terminée.
ChildActivationEnd: déclenché lorsque l'activation d'un composant
enfant est terminée.
316
Exercice
Utiliser la bibliothèque ngx-ui-loader afin de créer un loader qui se déclenche
pour les transitions de vos routes.
317
Router
Ajouter des données à votre Route
Nous avons vu que nous pouvons ajouter des données à votre route à
travers le resolver.
Ceci n’est pas l’unique manière de le faire.
Le resolver sert lorsque les données sont dynamique, cependant
lorsque les données que vous voulez passez à votre route sont statique,
vous pouvez passez par la propriété data de votre route.
Ceci peut être utile lorsque le composant peut être utilisé dans deux
contextes différents {
path: 'cv',
component: CvComponent,
data: {
blackListName: ['Sellaouti']
}
},
318
Récupérer les paramètres d’une route
ActivatedRoute / L’observable params
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.
Afin de récupérer les paramètres d’une root au niveau d’un composant
d’une façon dynamique on doit utiliser la propriété params qui est un
Observable.
Affecter le paramètre à une variable du composant en s’inscrivant avec la
méthode subscribe à l’observable params de notre ActivatedRoute.
Cette variable retourne un tableau de l’ensemble des paramètres.
[Link](params=>{[Link]=params['param']});
319
Récupérer les paramètres d’une route
import {RouterModule, Routes} from
'@angular/router'; import { Component, OnInit } from '@angular/core';
import {HomeComponent} from import {ActivatedRoute} from '@angular/router';
"./app/home/[Link]"; @Component({ 1
import {AboutComponent} from selector: 'app-about',
"./app/about/[Link]"; templateUrl: './[Link]',
styleUrls: ['./[Link]']
const APP_Routes:Routes =[
{path:'',component:HomeComponent},
})
export class AboutComponent {
{path:'about/:param',component:AboutCo
mponent}, monParam:any; 2
] 4 constructor(private router:ActivatedRoute) {
; [Link](params=>{[Link]=par
ams['param']});
export const routing =
[Link](APP_Routes);
} 3
}
[Link]
320
Les navigationExtras
A partir de la version 7.2 d’Angular, on peut passer des
informations via le routeur d’une façon dynamique en utilisant les
navigationExtras.
Une première façon de passer ces informations est à travers la
méthode navigate de votre router.
En effet, le second paramètre de cette méthode est un objet de type
NavigationExtras. Cet objet a une propriété state dans laquelle vous
pouvez passer les informations que vous voulez. [Link](['cv'], {
state: {
id: 5
}
});
321
Les navigationExtras
Afin de récupérer cet objet dans le composant cible, vous devez
utiliser le router et appelez sa méthode getCurrentNavigation()
Cette méthode permet de récupérer un Objet de type Navigation qui
possède un objet extras de type NavigationExtras qui lui-même
possède la propriété state contenant les informations envoyées.
const routeState =
[Link]()?.[Link];
if (routeState) {
[Link](routeState['id']);
}
322
Configuration du RouterModule
[Link](
routes, {
scrollPositionRestoration: 'enabled',
paramsInheritanceStrategy: 'always',
malformedUriErrorHandler: (error: URIError, urlSerializer:
UrlSerializer, url: string) => [Link]("/page-not-
found")
})
323
Angular
Form
AYMEN SELLAOUTI
324
Plan du Cours
1. Introduction
2. Les composants
3. Les directives
3 Bis. Les pipes
4. Service et injection de dépendances
5. Le routage
6. Form
7. HTTP
8. Les modules
9. Les tests unitaires
325
Approche de gestion de FORM
1. Approche basée Template
2. Approche réactive
326
Objectifs
1. Créer un formulaire
2. Ajouter des validateurs
3. Appréhender les classes Css générées par le formulaire
4. Manipuler l‘objet ngForm
5. Manipuler les controles du formuliare
327
Approche basée Template/ Template
Driven Approach
1 Importer le module FormsModule dans [Link]
328
Angular Form
Form control
329
Approche basée Template/ Template
Driven Approach
export class
TmeplateDrivenComponent{
<form
onSubmit(formulaire: NgForm){
(ngSubmit)="onSubmit(formulaire)"
#formulaire="ngForm"> [Link](formulaire);
}
}
Template [Link]
330
Approche basée Template
Validation
Afin de valider les propriétés des différents contrôles, Angular utilise des attributs et
des directives
- required
- email
331
Approche basée Template
NgForm
En détectant le formulaire, Angular décore les différents éléments du formulaire avec
des classes qui informe sur leur état :
dirty : informe sur le fait que l’une des propriétés du formulaire a été modifié ou
non
332
Exercice
Créer un formulaire d’authentification contenant les champs suivants :
Email
Password
Envoyer
Si un champ est invalide alors il devra avoir une bordure rouge.
Les deux champs sont obligatoires
Le password doit avoir au moins 4 caractères.
Un champ vide et non encore modifié ne peut avoir de bordure rouge que s’il a
été touché.
Le bouton « envoyer » ne doit être cliquable que si le formulaire est valide.
Utiliser le binding sur la propriété disabled.
333
Exercice
334
Approche basée Template
Accéder aux propriétés d’un champ (contrôle)
du formulaire
Pour accéder à l’objet form et ces propriétés nous avons utilisé
#notreForm=«ngForm »
335
Exercice
Ajouter un petit message d’erreur qui devra s’afficher sous le
champs de l’email s’il est invalide. Ce champ ne devra apparaitre que
si l’utilisateur accède ou modifie le champ email.
Ajouter un champ d’erreur pour signaler l’erreur à l’utilisateur.
336
Approche basée Template
Associer des valeurs par défaut aux champs
Pour associer des valeurs par défaut aux champs d’un formulaire
associé à Angular il faut le faire à partir du composant.
337
Exercice
Ajouter la valeur par défaut « myUserName » au champ username.
338
Approche basée Template
ngModelChange et ngModelOptions
A chaque changement dans vos input, un événement
ngModelChange est déclenché.
Vous pouvez aussi manipuler quand cet événement est déclenché à
travers le binding de la directive ngModelOptions.
Elle prend en paramètre un objet contenant les propriétés suivantes :
updateOn : Définit l'événement sur lequel la valeur de
contrôle du formulaire et la validité sont mises à jour.
La valeur par défaut est ‘change'.
Valeurs possibles : `change` | `blur` | `submit`.
339
Approche basée Template
ngModelChange et ngModelOptions
name : une alternative à la définition de l'attribut name sur
l'é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, et agit comme
si ce n'était pas dans le formulaire. La valeur par défaut est
false. Si aucun formulaire parent n'existe, cette option n'a aucun
effet.
340
Approche basée Template
Récap
Difficilement maintenable quand le formulaire grandit
Au fur et à mesure que nous ajoutons de plus en plus de balises de validation à un
champ ou lorsque nous commençons à ajouter des validations inter-champs
complexes, la lisibilité et la maintenabilité du formulaire diminuent.
L'avantage de cette façon de gérer les formulaires est sa simplicité initiale, et
c'est probablement suffisant pour créer des formulaires de petite à moyenne taille.
C'est aussi très similaire à ce qui a été fait dans AngularJs avec ng-model, donc ce
modèle de programmation sera déjà familier à beaucoup de développeurs.
En revanche, la logique de validation du formulaire ne peut pas être
facilement testée et les modèles peuvent devenir complexes assez rapidement.
341
Approche basée Template
Grouping form
Afin de grouper l’ensemble des contrôles (propriétés/champs) d’un
formulaire, on peut utiliser la technique du « grouping form
controls ».
Il suffit d’ajouter la directive ngModelGroup dans la div qui
englobe les propriétés à grouper.
<div
ngModelGroup= "user"
#userData= "ngModelGroup"
>
342
Exercice
Grouper les données de votre utilisateur dans un ngModelGroup
Essaye de voir s’il contient les mêmes classes qu’un contrôle simple,
e.g. ng-dirty, ng-valid.
343
Reactive form
Les Reactive Form sont une deuxième méthode de gérer vos
formulaires avec Angular.
Au contraire des Template Driven Form, ses formulaires sont
générés programmatiquement 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 personnalisés.
344
Reactive form
Créer un formulaire
Commencer par ajouter le module ReactiveFormModule.
Afin de créer un formulaire avec l’approche réactive, vous devez
créer un 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 formulaire. Ce sont les FormControl.
Chaque FormControl définit un champ du formulaire. Il prend
en paramètre, la valeur initiale, un Validator ou un tableau de
Validator et en troisième paramètre, un AsyncValidator ou un
tableau d’AsyncValidator
345
Reactive form
Créer un formulaire
ngOnInit() {
[Link] = new FormGroup({
name: new FormControl(null),
firstname: new FormControl(null),
age: new FormControl(null),
});
346
Reactive form
Créer un formulaire
348
Reactive form
Créer un formulaire
Vous pouvez aussi passer par le service FormBuilder et sa
méthode group.
Elle prend en paramètre un objet avec comme clé le nom du
formControl et comme valeur un tableau avec comme première
propriété la valeur initiale du champ.
Ceci est moins verbeux et plus simple à gérer pour les grands
formulaires.
constructor(
[Link] = [Link]({
private formBuilder: FormBuilder
email: [null],
) {}
username: [null],
userCategory: ['employee'],
});
349
Reactive form
Associer le FormGroup à votre form
Une fois le formulaire définit, nous devons l’associer au form de votre
template.
Pour ce faire, vous devez ajouter la directive formGroup (qui se
trouve dans le module ReactiveFormsModule) au niveau de la balise
form et la binder à votre objet de type FormGroup au niveau de
votre fichier TS.
350
Reactive form
Associer les FormControl à vos input
Afin d’associer votre FormControl à votre champ input, utiliser la
directive formControlName et associer le à l’identifiant du
FormControl
351
Reactive form
Récupérer les FormControl dans le HTML
Afin de récupérer les FormControl dans le HTML, utiliser la
méthode get de votre form group et passer lui l’identifiant du
FormControl à récupérer.
<button
class="btn btn-primary"
[disabled]="[Link]('password').valid"
(click)="process()">
Submit
</button>
352
Reactive form
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.
<div
*ngIf="[Link]('name')?.errors && [Link]('name')?.touched"
class="alert alert-danger“
>
353
Reactive form
Récupérer les FormControl dans le HTML
Une deuxième méthode pour avoir un code moins verbeux est de
créer des getters pour vos champs.
355
Exercice
356
Reactive form
Les Validateurs
Un validateur est une fonction qui retourne false si un champ
est valide selon une certaine condition sinon elle retourne une
information sur l’erreur.
Afin de valider votre formulaire, passer en deuxième paramètre de
FormControl une référence à une méthode de la classe
Validators ou un tableau de ces méthodes.
357
Reactive form
Les Validateurs
Afin de valider votre formulaire, passer en deuxième paramètre de
FormControl une référence à une méthode de la classe
Validators ou un tableau de ces méthodes.
358
Reactive form
Les Validateurs
Validateurs synchrones : Ce sont des fonctions qui contrôle votre
élément et retournent un ensemble d’erreurs de validation ou null.
C’est ce qu’on passe en deuxième paramètre lors de l’instanciation d’un
FormControl.
Validateurs asynchrones : Ce sont des fonctions asynchrones qui
contrôle votre élément et retournent une Promise ou un Observable
qui émettent un ensemble d’erreurs de validation ou null. C’est ce
qu’on passe en troisième paramètre lors de l’instanciation d’un
FormControl.
Pour des raisons de performance, Angular commence par les validateurs
synchrones, s’ils passent il déclenche les validateurs asynchrones.
359
Reactive form
Les Validateurs offerts par Angular
Vous pouvez utiliser des validateurs offerts par angular ou créer vos
propres validateurs.
Les validateurs de base sont les mêmes que ceux de l’approche
basée Template.
[Link] 360
Reactive form
Les Validateurs offerts par Angular
new FormGroup({
email: new FormControl(null, [[Link], [Link]]),
username: new FormControl(null, [[Link](3)]),
age: new FormControl(null, [
[Link], [Link]('[0-1]?\d{1,2}')
]),
Méthode 1
});
formGroup: FormGroup = new FormGroup({
name: new FormControl(null, {
validators: [[Link], [Link](3)],
asyncValidators: [],
updateOn: "change"
}),
age: new FormControl(null),
Méthode 2
});
[Link] 361
Exercice
Ajouter dans votre cvTech un composant contenant un formulaire. Ce formulaire
devra vous permettre d’ajouter un utilisateur.
Ajouter les validateurs nécessaires.
Après l’ajout fowarder le user vers la liste des cvs.
362
Reactive form
Manipulez les valeurs de votre form
Afin de mettre à jour la valeur d’un FormControl ou d’un
FormGroup, vous pouvez utiliser la méthode setValue() qui met à
jour la valeur du contrôle de formulaire et valide la structure de la
valeur fournie par rapport à la structure du contrôle.
Vous pouvez utiliser la méthode patchValue si vous modifiez
uniquement une partie.
[Link]({ name: "Sellaouti", firstname: "Aymen" });
[Link] 363
Reactive form
Manipulez les valeurs de votre form
En deuxième paramètre de ces deux fonctions, vous pouvez passer
un objet d’options avec deux propriétés:
onlySelf: Angular vérifie l'état de validation du formulaire,
chaque fois qu'il y a un changement de valeur. La validation
commence à partir du contrôle dont la valeur a été modifiée et
se propage au FormGroup de niveau supérieur. Il s'agit du
comportement par défaut. Si vous ne souhaitez pas qu‘Angular
vérifie la validité de l'ensemble du formulaire, chaque fois
que vous modifiez la valeur à l'aide de setValue ou patchValue,
définissez onlySelf à true.
[Link] 364
Reactive form
Manipulez les valeurs de votre form
En deuxième paramètre de ces deux fonctions, vous pouvez passer
un objet d’options avec deux propriétés:
emitEvent: Les formes Angular émettent deux événements.
L'un est ValueChanges et l'autre est StatusChanges.
L'événement ValueChanges est émis chaque fois que la valeur du
formulaire est modifiée. L'événement StatusChanges est émis
chaque fois qu'angular calcule l'état de validation du formulaire.
C'est le comportement par défaut Nous pouvons empêcher que
cela se produise, en définissant l'emitEvent à false
[Link] 365
Reactive form
Manipulez les valeurs de votre form
[Link](
{ firstname: "Aymen" },
{
emitEvent: false,
onlySelf: true,
}
);
[Link] 366
Reactive form
Suivre les modifications de vos FormControls
et de vos FormGroup
FormControl et FormGroup, vous fournissent deux Observables
permettant le suivi des changements de valeur et de status.
ValueChanges est un événement déclenché par les formulaires
Angular chaque fois que la valeur de FormControl, FormGroup ou
FormArray change. L'observable obtient la dernière valeur du
contrôle. Il nous permet de suivre les modifications apportées à la
valeur en temps réel et d'y répondre. Par exemple, nous pouvons
l'utiliser pour valider la valeur, calculer les champs calculés, ...
[Link] 367
Reactive form
Suivre les modifications de vos FormControls
et de vos FormGroup
statusChanges est un événement déclenché par les formulaires
Angular chaque fois que Angular calcule le statut de validation
de FormControl, FormGroup ou FormArray. Il renvoie un
observable afin que vous puissiez vous y abonner. L'observable
obtient le dernier état du contrôle.
[Link] 368
Exercice
Afin de protéger les informations personnelles des mineurs, faites en
sortes que lorsque l’age de la personne possédant le Cv est inférieur
à 18 ans, il ne puisse pas renseigner le path de l’image.
369
Exercice
Etant donné que notre formulaire est assez volumineux et pour
permettre une meilleure expérience utilisateur, nous voulons faire en
sorte que si l’utilisateur saisisse un formulaire valide et qu’il sorte de
la page sans l’envoyer, il puisse le retrouver rempli lorsqu’il retourne
à la page d’ajout d’un cv.
370
Reactive form
Customiser vos validateurs
Un custom validator est une fonction de type ValidatorFn qui prend en
paramètre un control de type AbstractControl (qui contient une
propriété value représentant la valeur du champ à valider) et retourne
null si la valeur est valide ou un objet de type ValidationErrors s’il y a
des erreurs de validation.
export declare interface ValidatorFn {
(control: AbstractControl): ValidationErrors | null;
}
export declare type ValidationErrors = {
[key: string]: any;
};
371
Reactive form
Customiser vos validateurs
Le Validator, renvoyée par la fonction de création de validateur, doit
suivre ces règles :
Un seul argument d'entrée est attendu, qui est de type
AbstractControl. La fonction validateur peut obtenir la valeur à
valider via la propriété [Link]
La fonction de validation doit renvoyer null si aucune erreur n'a été
trouvée dans la valeur du champ, ce qui signifie que la valeur est valide
Si des erreurs de validation sont trouvées, la fonction doit renvoyer
un objet de type ValidationErrors.
372
Reactive form
Customiser vos validateurs
L'objet ValidationErrors peut avoir comme propriétés les multiples
erreurs trouvées (généralement une seule) et comme valeurs les détails
de chaque erreur.
Si nous voulons simplement indiquer qu'une erreur s'est produite, sans
fournir plus de détails, nous pouvons simplement renvoyer true
comme valeur d'une propriété error dans l'objet ValidationErrors.
373
Reactive form
Customiser vos validateurs
export function createPasswordStrengthValidator(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const value = [Link];
if (!value) {
return null;
}
const hasUpperCase = /[A-Z]+/.test(value);
const hasLowerCase = /[a-z]+/.test(value);
const hasNumeric = /[0-9]+/.test(value);
const passwordValid = hasUpperCase && hasLowerCase && hasNumeric;
return !passwordValid ? {passwordStrength: true} : null;
};
}
374
Reactive form
Customiser vos validateurs
Validateurs Asynchrones
Dans certains cas d’utilisation, la validation de votre champ ne se fait
pas d’une façon asynchrone.
Le cas le plus répandu est la validation par votre backend.
Imaginez que vous avez un champ email et qu’il ne doit pas être
redondant. La solution et d’avoir une API qui vérifie ca et qui vous
retourne la réponse.
Ce traitement étant Asynchrone, le Validateur synchrone ne fait plus
affaire.
Il faut donc créer un validateur ASYNCHRONE.
375
Reactive form
Customiser vos validateurs
Validateurs Asynchrones
Le Validator, renvoyée par la fonction de création de validateur, doit
suivre ces règles :
Un seul argument d'entrée est attendu, qui est de type
AbstractControl. La fonction validateur peut obtenir la valeur à valider
via la propriété [Link]
La fonction de validation doit renvoyer une Promise<null>, un
Observable<null> si aucune erreur n'a été trouvée dans la valeur du
champ, ce qui signifie que la valeur est valide
Si des erreurs de validation sont trouvées, la fonction doit renvoyer
une Promise ou un Observable d’un objet de type ValidationErrors.
376
Reactive form
Customiser vos validateurs
Validateurs Asynchrones
377
Reactive form
Customiser vos validateurs
Validateurs Asynchrones
findUserByEmail(value: any): Observable<ValidationErrors | null> {
return new Observable<ValidationErrors | null>(
(observer) => {
setTimeout(
() => {
const date = new Date();
if ([Link]() % 2) {
[Link](null);
} else {
[Link]({userExists: true});
}
}, 1500);
}
);
}
}
378
Exercice
Le champ Cin étant unique, créer un validateur asynchrone permettant
de gérer cette contrainte et tester le.
379
Reactive form
Customiser vos validateurs
Valider un form
Afin de valider un form, vous devez faire la même chose que pour le
validateur d’un FormControl sauf que dans votre fonction, vous devez
prendre en premier paramètre un objet de type FormGroup
381
Exercice
Le champ Cin étant composé de 8 caractères numériques, il présente
une corrélation entre l’age de la personne et les deux premiers
caractères.
Si la personne a un age >= 60 ans, les deux première caractères
numériques doivent être entre 00 et 19.
Sinon ca doit être supérieur à 19.
Créer le validateur permettant de faire ca.
382
Reactive form
Imbriquer des formulaires
La partie TS
Afin d’imbriquer des formulaires, au lieu de passer un FormControl à
votre propriété, passez un objet de type FormGroup.
De même si vous utilisez le service FormBuilder, pour votre formulaire
imbriqué, utilisez un group.
[Link] = [Link]({
credentials: [Link]({
email: [null, {validators: [[Link], [Link]]}],
password: [null, {validators: [[Link], createPasswordStrengthValidator()]}],
}),
username: [null, {validators: [[Link]]}],
age: [null, {validators: [[Link], [Link]('[0-1]?\d{1,2}')]}],
});
383
Reactive form
Imbriquer des formulaires
La partie TS
Afin d’imbriquer des formulaires, au lieu de passer un FormControl à
votre propriété, passez un objet de type FormGroup.
De même si vous utilisez le service FormBuilder, pour votre formulaire
imbriqué, utilisez un group.
[Link] = new FormGroup({
credentials: new FormGroup({
email: new FormControl(null, [[Link], [Link]]),
password: new FormControl(null, [[Link], createPasswordStrengthValidator]),
}),
username: new FormControl(null, [[Link]]),
age: new FormControl(null, [[Link], [Link]('[0-1]?\d{1,2}')]),
});
384
Reactive form
Imbriquer des formulaires
La partie HTML
Dans la partie HTML, grouper tous les éléments de votre
formulaire imbriqué dans un container (div par exemple)
Ajouter la propriété formGroupName avec l’identifiant de votre
formGroup.
<div formGroupName="credentials">
<div>
<label for="email">Email</label>
<input class="form-control" type="email" id="email“ formControlName="email“ />
</div>
<div>
<label for="username">Password</label>
<input class="form-control" type="text" id="password“ formControlName="password"/>
</div>
</div> 385
Reactive form
Imbriquer des formulaires
Accéder aux FormControl
Pour accéder aux FormControl des formulaires imbriqués, vous
devez spécifier le path complet avec des points comme séparateur.
<div class="field-message"
*ngIf="[Link]('[Link]').errors?.passwordStrength“
>
Your password must have lower case, upper case and numeric characters.
</div>
386
Reactive form
FormArray
Dans les Reactive Form, un formulaire est défini à l'aide des API
FormControl et FormGroup, ou à l'aide de l'API FormBuilder dans la
plupart des cas, ou tous les champs d'un formulaire sont bien
connus à l'avance, permettant de définir un modèle statique.
Imaginons d'autres situations plus avancées mais encore fréquemment
rencontrées où le formulaire est beaucoup plus dynamique et où
tous les champs du formulaire ne sont pas connus à l'avance
Prenons le cas d’un formulaire dynamique dans lequel des contrôles
de formulaire sont ajoutés ou supprimés du formulaire par
l'utilisateur, en fonction de son interaction avec l'interface utilisateur.
387
Reactive form
FormArray
Un FormArray, tout comme un FormGroup, est également un
conteneur de FormControl.
Contrairement à un FormGroup, un conteneur FormArray ne nous
oblige pas à connaître tous les contrôles à l'avance, ainsi que
leurs noms.
En fait, un FormArray peut avoir un nombre indéterminé de
FormControl, en commençant par zéro ! Les contrôles peuvent
ensuite être ajoutés et supprimés dynamiquement en fonction de la
façon dont l'utilisateur interagit avec l'interface utilisateur.
388
Reactive form
FormArray
Chaque contrôle aura alors une position numérique dans le
tableau des contrôles de formulaire, au lieu d'un nom unique.
Les contrôles de formulaire peuvent être ajoutés ou supprimés du
modèle de formulaire à tout moment lors de l'exécution à l'aide de
l'API FormArray.
Pensez à spécifier un getter pour votre FormArray
this.fg2 = [Link]({ get skills() {
name: [null], return [Link]('skills') as FormArray;
age: [0], }
skills: new FormArray([]),
});
389
Reactive form
FormArray
Afin de déclarer un FormArray dans un FormGroup, il suffit de
définir une propriété de type un objet FormArray et de
l’initialiser à un tableau vide ou contenant des FormControl ou
FormGroup selon votre cas d’utilisation.
[Link] = new FormGroup({
credentials: new FormGroup({
email: new FormControl(null, [[Link], [Link]]),
password: new FormControl(null, [[Link], createPasswordStrengthValidator]),
}),
username: new FormControl(null, [[Link]]),
age: new FormControl(null, [[Link], [Link]('[0-1]?\d{1,2}')]),
skills: new FormArray([]),
});
390
Reactive form
FormArray
Afin de déclarer un FormArray avec le service FormBuilder, il suffit d’appeler
la méthode array de votre FormBuilder et de lui passer un tableau vide ou
contenant des group selon votre cas d’utilisation.
[Link] = [Link]({
credentials: [Link]({
email: [null, {validators: [[Link], [Link]]}],
password: [null, {validators: [[Link], createPasswordStrengthValidator()]}],
}),
username: [null, {validators: [[Link]]}],
age: [null, {validators: [[Link], [Link]('[0-1]?\d{1,2}')]}],
skills: [Link]([])
});
391
Reactive form
FormArray
Dans la partie Template, vous devez identifier un container groupant tous ses
éléments.
Ajouter y la directives formArrayName et associez la au champ FormArray.
Boucler sur les éléments contenu dans votre FormArray. Si c’est des
FormGroup, binder la propriété formGroupName (formControlName si
vous avez uniquement des FormControl) avec l’indice du FormGroup dans le
<divarray.
formArrayName="skills">
Skills <button (click)="addSkill()" class="btn btn-success">Add Skill</button>
<div
*ngFor="let skillControl of [Link]; let i = index"
[formGroupName]=“i">
<input type="text" class="form-control" formControlName="name">
</div> 392
Reactive form
FormArray
Voici les méthodes les plus couramment utilisées disponibles dans l'API FormArray :
controls : il s'agit d'un tableau contenant tous les FormControl qui font partie
du FormArray
length : C'est la longueur totale du tableau
at(index) : renvoie le FormControl à une position de tableau donnée
push(control) : ajoute un nouveau FormControl à la fin du tableau
insert(index, control, option) : ajoute un nouveau FormControl à la position
index
removeAt(index) : supprime un FormControl à une position donnée du tableau
getRawValue() : Obtient les valeurs de tous les contrôles de formulaire, via la
propriété [Link] de chaque FormControl.
setValue(value, option, emitEvent): Définit la valeur de FormArray. Il accepte un
tableau qui correspond à la structure du contrôle.
393
Reactive form
FormArray
Ajout dynamique d’éléments
Afin d’ajouter un élément dynamiquement, vous pouvez utiliser l’API
FormArray et sa méthode push.
Préparer l’élément à ajouter et pusher le dans votre Array
addSkill(): void {
const skillFormGroup = [Link]({
name: [null, [Link]]
});
[Link](skillFormGroup);
}
394
Reactive form
Avantages
Les Reactives Forms sont beaucoup plus propres, et se concentrent
uniquement sur la logique de présentation. De même pour la
logique de validation.
Toutes les règles de validation métier ont été déplacées vers la classe
du composant. Ceci facilite les tests unitaires.
La définition dynamique du formulaire devient plus facile avec les
FormArray.
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.
395
Programmation Asynchrone
396
Les promesses
Ce sont des objets qui représentent une complétion ou l’échec d’une
opération asynchrone.
([Link]
les_promesses)
Le fonctionnement des promesses est le suivant :
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
397
Promesse
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(3);
}, 5000);
});
[Link](
function (x) {
[Link]('resolved with value :', x);
}
)
398
Qu’est ce que la programmation réactive
1. Nouvelle manière d’appréhender les appels asynchrones
2. Programmation avec des flux de données asynchrones
Programmation réactive =
Flux de données (observable) + écouteurs d’événements(observer).
399
Qu’est ce que la programmation réactive
La programmation réactive nous permet donc de réagir à divers
événements tel qu'une écoute sur un click, un timer ou un
interval qui déclenche un traitement, une requête http.
Elle va nous permettre d’uniformiser la gestion de ses
événements.
Actuellement pour une requête http c’est un fetch, pour un event
dans le navigateur c’est un addEventListener, …
L’idée est donc de normaliser tout ca.
Ceci nous permettra de les fusionner, les combiner …
400
Le pattern « Observer »
Le patron de conception observer permet à un objet de garder la
trace d'autres objets, intéressés par l'état de ce dernier.
401
Observables, Observers et subscriptions
Observable
inscription Données
traitement
402
Fonctionnement
403
Promesse Vs Observable
Promesse Observable
Un promesse gère un seul événement Un observable gère un « flux » d’événements.
Non annulable. Annulable.
Lazy : le traitement n'est déclenché qu'à la première
Traitement immédiat.
utilisation du résultat.
Une centaine d'opérateurs de transformation natifs
Deux méthodes uniquement (then/catch).
(map, reduce, merge, filter, …).
Operateurs tels que retry, replay
404
Observable
const observable = new Observable(
(observer) => {
let i = 5;
setInterval(() => {
if (!i) {
[Link]();
}
[Link](i--);
}, 1000);
});
[Link](
(val) => {
[Link](val);
}
);
405
asyncPipe
async Pipe est un pipe qui permet d’afficher directement un
observable.
{{ valeurSourceAsynchrone | async }}
L’asyncPipe s’inscrit automatiquement à l’observable et affiche le
dernier résultat envoyé.
Quand le composant est détruit l’asyncPipe se désinscrit
automatiquement de l’observable.
406
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.
Par exemple : of(1, 2, 3) crée un observable qui va émettre 1, 2, et 3, l'un
après l'autre.
Un opérateur pipeable est 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é.
Syntaxe : [Link](opertaeur1(), operateur2(), …).
407
Les operateurs de création
Les opérateurs de création sont utilisés pour créer de nouveaux
observables.
Ils sont divisés en opérateurs de création et en opérateurs de
création de jointure.
La principale différence entre eux réside dans le fait que les opérateurs
de création de jointure créent des observables à partir d'autres
observables, alors que les opérateurs de création créent des
observables à partir d'objets qui diffèrent des observables.
408
Les operateurs de création
from
from est utilisé pour convertir des types d'objets JavaScript
comme un tableau, une promesse ou un objet itérable en une
séquence observable de valeurs.
L'opérateur émet également une chaîne sous forme de séquence de
caractères.
from([1, 2, 3]).subscribe({
next: (data) => [Link]('[from]', data),
complete: () => [Link]('[from] complete'),
});
409
Les operateurs de création
of
l’opérateur of est utilisé afin de convertir un argument en un
observable
il ne fait aucun aplatissement ou conversion et émet chaque
argument sous le même type qu'il reçoit
of est couramment utilisé lorsque vous avez simplement besoin de
renvoyer une valeur là où un observable est attendu ou de
démarrer une chaîne observable.
of([1, 2, 3], new Date(), {
name: 'sellaouti',
firstname: 'aymen',
}).subscribe({
next: (data) => [Link]('[of]', data),
})
410
Les operateurs de création
of
411
Les operateurs de création
timer
L'opérateur timer est un opérateur de création utilisé pour créer un
observable qui commence à émettre les valeurs après un délai d'attente,
et la valeur continuera d'augmenter après chaque appel.
Il prend en entrée en premier paramètre quand déclencher l’event.
En second paramètre en cas de volonté de répétition chaque
combien de millisecondes reprendre l’émission.
timer(1000).subscribe((val) => [Link]('[timer] : '+ val));
412
Les operateurs de création
fromEvent
L'opérateur fromEvent est utilisé pour émettre un observable en se
basant sur un événement.
Il prend en paramètre l’élément cible puis l’événement à écouter.
export class FromEventComponent implements AfterViewInit {
@ViewChild('btn') button!: ElementRef;
onClickButton$!: Observable<Event>;
ngAfterViewInit() {
[Link]$ = fromEvent([Link], 'click');
}
}
413
Quelques opérateurs utiles de l’Observable
opérateur pipable
map
414
Exercice
Ecrire un composant qui affiche une suite d’images non stop.
Utiliser un observable comme source d’images.
Vous devez uniquement utiliser des opérateurs pour faire le travail.
La taille de l’image, la liste des images et le temps entre chaque image
doit être paramétrable.
415
Quelques opérateurs utiles de l’Observable
opérateur pipable
filter
416
Quelques opérateurs utiles de l’Observable
opérateur pipable
take
L'opérateur 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.
417
Quelques opérateurs utiles de l’Observable
opérateur pipable
throttleTime
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 : Permettre à l’utilisateur de déclencher
une fois un évènement dans un intervalle de temps
donné.
Appeler une fonction une seule fois dans un intervalle
de temps particulier au survol de la souris.
418
Quelques opérateurs utiles de l’Observable
opérateur pipable
debounceTime
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é.
De cette façon, debounceTime garde
une trace de la valeur la plus récente et
émet cette valeur la plus récente lorsque
l'heure d'échéance donnée est dépassée.
L’autocomplete est le cas classique
du debounceTime
419
Les opérateurs d’aplatissement/ flattening
operators
Une erreur courante commise dans les [Link](
applications Angular consiste à imbriquer les (params) => {
abonnements observables. [Link]([Link])
.subscribe(
Cette syntaxe n'est pas recommandée car elle (personne) => {
est difficile à lire et peut entraîner des bogues [Link] = personne;
des effets secondaires inattendus. },
(erreur) => {
Par exemple, cette syntaxe rend difficile la if (![Link]) {
désinscription correcte de tous ces observables. [Link](['cv']);
}
De plus, si observable1 émet plus d'une fois }
dans un court laps de temps, nous pourrions );
vouloir annuler l'abonnement précédent à }
observable2 et en démarrer un nouveau basé sur );
les nouvelles données reçues d'observable1.
420
Les opérateurs d’aplatissement/ flattening
operators
L'opérateur d’aplatissement sont des opérateur qui permettent
d’émettre un flux à partir d’un autre.
Ils permettent d’éviter les inscriptions imbriquées avec subscribe.
Il en existe plusieurs variantes et qui permettent de spécifier
comment 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 ?
421
Les opérateurs d’aplatissement/ flattening
operators
MergeMap
L'opérateur mergeMap est essentiellement une combinaison de
deux opérateurs - merge et map.
La partie map vous permet de mapper une valeur d'une source
observable à un flux observable. Ces flux sont souvent appelés flux
internes.
La partie merge combine tous les flux internes observables renvoyés
par la map et émet simultanément toutes les valeurs de chaque flux
d'entrée.
422
Les opérateurs d’applatissement/ flattening
operators
MergeMap
Au fur et à mesure que les valeurs de
toute séquence combinée sont produites,
ces valeurs sont émises dans le cadre de la
séquence résultante.
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.
[Link] 423
Les opérateurs d’applatissement/ flattening
operators
MergeMap
timerObs(timer: number, name: string, iteration = 4) { params = [
return new Observable((observer: Observer<string>) => { {name: 'obs1', timer: 1000, iteration: 4 },
let i = 0; {name: 'obs2', timer: 1500, iteration: 4 },
const x = setInterval(() => { ];
if (i >= iteration) { constructor(private rxjsService: RxjsService) {
[Link](); from([Link]).pipe(
clearInterval(x); mergeMap((param) => [Link]([Link],
} [Link]))
[Link](`observable ${name} ${++i}`); ).subscribe(
}, timer); (data) => [Link](data)
}); )
} }
424
Les opérateurs d’applatissement/ flattening
operators
SwitchMap
L'opérateur switchMap est essentiellement une combinaison de deux
opérateurs - switchAll et map.
La partie de map vous permet de mapper une valeur d'une source
observable d'ordre supérieur à un flux observable interne.
La partie switch s'abonne à l'observable interne fourni le plus
récemment émis par un observable d'ordre supérieur, en se désabonnant
de tout observable interne précédemment souscrit.
L'opérateur switchMap effectue toutes les actions suivantes :
Annule et se désabonne automatiquement du second observable lorsque le
premier émet une nouvelle valeur.
Se désabonne automatiquement du second observable si nous nous
désinscrivons du premier.
S'assure que les deux observables se produisent en séquence, l'un après l'autre.
425
Les opérateurs d’applatissement/ flattening
operators
SwitchMap
switchMap n'a qu'un seul
abonnement actif à la fois à partir
duquel les valeurs sont transmises à un
observateur.
Une fois que l'observable d'ordre
supérieur émet une nouvelle valeur,
switchMap exécute la fonction pour
obtenir un nouveau flux observable
interne et commute les flux. Il se
désabonne du flux actuel et s'abonne au
nouvel observable interne.
[Link] 426
Les opérateurs d’applatissement/ flattening
operators
SwitchMap
Une erreur courante commise dans les [Link](
applications Angular consiste à imbriquer les (params) => {
abonnements observables. [Link]([Link])
.subscribe(
Cette syntaxe n'est pas recommandée car elle (personne) => {
est difficile à lire et peut entraîner des bogues [Link] = personne;
des effets secondaires inattendus. },
(erreur) => {
Par exemple, cette syntaxe rend difficile la if (![Link]) {
désinscription correcte de tous ces observables. [Link](['cv']);
}
De plus, si observable1 émet plus d'une fois }
dans un court laps de temps, nous pourrions );
vouloir annuler l'abonnement précédent à }
observable2 et en démarrer un nouveau basé sur );
les nouvelles données reçues d'observable1.
427
Les opérateurs d’applatissement/ flattening
operators
SwitchMap
timerObs(timer: number, name: string, iteration = 4) { params = [
return new Observable((observer: Observer<string>) => { {name: 'obs1', timer: 1000, iteration: 4 },
let i = 0; {name: 'obs2', timer: 1500, iteration: 4 },
const x = setInterval(() => { ];
if (i >= iteration) { constructor(private rxjsService: RxjsService) {
[Link](); from([Link]).pipe(
clearInterval(x); switchMap((param) => [Link]([Link],
} [Link]))
[Link](`observable ${name} ${++i}`); ).subscribe(
}, timer); (data) => [Link](data)
}); )
} }
428
Les opérateurs d’applatissement/ flattening
operators
SwitchMap
export class SwitchMapComponent implements AfterViewInit {
search: string = '';
@ViewChild('searchInput') searchInput!: ElementRef;
constructor(private rxjsService: RxjsService) {}
ngAfterViewInit (): void {
const change$ = fromEvent<KeyboardEvent>([Link], 'keyup');
change$.pipe(
map((event: KeyboardEvent) => [Link]),
debounceTime(300),
switchMap((search) => {
return [Link](search)
})
)
.subscribe(
(input) => {//…Do what you want with your products}
)
}
}
429
Les opérateurs d’applatissement/ flattening
operators
ConcatMap
L'opérateur concatMap est essentiellement une combinaison de deux
opérateurs - concat et map.
La partie map vous permet de mapper une valeur d'une source observable à
un flux observable. Ces flux sont souvent appelés flux internes.
La partie 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.
Utilisez cet opérateur si l'ordre des émissions est important et que vous
souhaitez d'abord voir les valeurs émises par les flux qui passent par
l'opérateur en premier.
430
Les opérateurs d’applatissement/ flattening
operators
ConcatMap
[Link] 431
Les opérateurs d’applatissement/ flattening
operators
ConcatMap
timerObs(timer: number, name: string, iteration = 4) { params = [
return new Observable((observer: Observer<string>) => { {name: 'obs1', timer: 1000, iteration: 4 },
let i = 0; {name: 'obs2', timer: 1500, iteration: 4 },
const x = setInterval(() => { ];
if (i >= iteration) { constructor(private rxjsService: RxjsService) {
[Link](); from([Link]).pipe(
clearInterval(x); concatMap((param) => [Link]([Link],
} [Link]))
[Link](`observable ${name} ${++i}`); ).subscribe(
}, timer); (data) => [Link](data)
}); )
} }
432
Les opérateurs d’applatissement/ flattening
operators
ExhaustMap
L'opérateur exhaustMap est essentiellement une combinaison de deux
opérateurs - exhaust et map.
La partie map vous permet de mapper une valeur d'une source observable
d'ordre supérieur à un flux observable interne.
La partie exhaust s'abonne ensuite à 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.
exhaustMap n'a qu'un seul abonnement actif à la fois à partir duquel
les valeurs sont transmises à un observateur. Lorsque l'observable d'ordre
supérieur émet un nouveau flux observable interne, si le flux actuel n'est
pas 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.
433
Les opérateurs d’applatissement/ flattening
operators
ExhaustMap
[Link] 434
Les opérateurs d’applatissement/ flattening
operators
ExhaustMap
timerObs(timer: number, name: string, iteration = 4) { params = [
return new Observable((observer: Observer<string>) => { {name: 'obs1', timer: 1000, iteration: 4 },
let i = 0; {name: 'obs2', timer: 1500, iteration: 4 },
const x = setInterval(() => { ];
if (i >= iteration) { constructor(private rxjsService: RxjsService) {
[Link](); from([Link]).pipe(
clearInterval(x); exhaustMap((param) => [Link]([Link],
} [Link]))
[Link](`observable ${name} ${++i}`); ).subscribe(
}, timer); (data) => [Link](data)
}); )
} }
435
Les opérateurs d’applatissement/ flattening
operators
combineLatest
combineLatest permet de fusionner plusieurs flux en prenant la valeur la plus
récente de chaque entrée observable et en émettant ces valeurs à l'observateur sous
forme de sortie combinée (généralement sous forme de tableau).
Les opérateurs mettent en cache la dernière valeur pour chaque observable
d'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 internes sont terminés et
génère une erreur si l'un des flux internes génère une erreur.
D'autre part, si un flux n'émet pas de valeur mais se termine, le flux résultant
se terminera au même moment sans rien émettre.
De plus, si un flux d'entrée n'émet aucune valeur et ne se termine jamais,
combineLatest n'émettra jamais et ne se terminera jamais.
436
Les opérateurs d’applatissement/ flattening
operators
combineLatest
const cv$ = [Link](cvId)
.pipe(startWith(null));
437
Les opérateurs d’applatissement/ flattening
operators
combineLatest
const cv$ = [Link](cvId)
.pipe(startWith(null));
438
Les opérateurs d’applatissement/ flattening
operators
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.
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.
439
Les opérateurs d’applatissement/ flattening
operators
forkJoin
forkJoin({
users: [Link]<User[]>('[Link]
posts: [Link]<Post[]>('[Link]
}).subscribe((usersAndPosts) => {
[Link] = [Link];
[Link] = [Link];
});
forkJoin([
[Link]<User[]>('[Link]
[Link]<Post[]>('[Link]
]).subscribe(([users, posts]) => {
[Link] = posts;
[Link] = users;
});
440
Exercice
Sachant que pour sélectionner une personne dont le nom contient une chaine donnée, loopback
utilise la syntaxe suivante : {"where":{"name":{"like":"%${name}%"}}}
Ceci doit être fourni dans les paramètres de votre requête avec la clé filter. Tester le sur votre
swagger.
Ajouter un champ input dans la page Cv. A chaque caractère saisie, la liste des choix doit
automatiquement changer et n’afficher que les cvs qui contiennent la chaine saisie. En sélectionnant
un des choix, rediriger l’utilisateur vers les détails du cv sélectionné.
Faite en sorte d’avoir le code le plus récative possible.
441
Les opérateurs RxJs
Gestion des erreur
catchError
L'opérateur catchError de RxJs permet de capturer les erreurs qui
se produisent dans un observable et de les traiter de manière
appropriée.
Il permet de continuer à émettre des valeurs depuis un
observable même si une erreur se produit, plutôt que de stopper
complètement l'émission de valeurs.
Il prend en argument une fonction qui prend en entrée l'erreur et
retourne un observable pour gérer cette erreur.
Si vous voulez retourner l’erreur dans votre flux utilisez l’opérateur
throwError.
442
Les opérateurs RxJs
Gestion des erreur
catchError
[Link](
switchMap((param) => [Link](param['id'])),
catchError((e) => {
[Link](e);
[Link]([APP_ROUTES.cv]);
return throwError(() => new Error(e));
})
);
443
Les opérateurs RxJs
Gestion des erreur
catchError
Vous pouvez utilisez l’opérateur EMPTY de rxjs pour spécifier que
vous n’émettez rien et qui émet ensuite une notification de
complétion du flux.
Vous pouvez aussi émettre ce que vous voulez afin de continuer le
flux
444
Exercice
Reprenez les différentes fonctionnalités et faites en sorte que le code
devienne réactif.
Utilisez l’async pipe à chaque fois que vous le pouvez.
Utilisez catchError pour gérer les erreurs.
445
Quelques opérateurs utiles de l’Observable
[Link]
[Link]
[Link]
446
Les subjects
Un subject est un type particulier d’observable. En effet Un subject
est en même temps un observable et un observer, il possède donc les
méthodes next, error et complete.
Pour broadcaster une nouvelle valeur, il suffit d'appeler la méthode
next, et elle sera diffusé aux Observateurs enregistrés pour écouter le
Subject.
Observer
Observable
Next
(subscription) Error
Complete
447
Les subjects
personneClickSubject
Observable CvComponent
Details
Observer
Observver 3
448
Les behaviourSubject
Les BehaviourSubject sont une variante du Subject.
Le BehaviourSubject a la particularité de stocker la valeur "actuelle". Cela
signifie que 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. Vous pouvez soit
obtenir la valeur en accédant à la propriété value sur le BehaviourSubject,
Soit vous y abonner. Si vous vous y abonnez, le BehaviourSubject émettra
directement 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.
449
Les ReplaySubject
Le ReplaySubject est comparable au BehaviorSubject dans la mesure
où il peut envoyer les "anciennes" valeurs aux nouveaux abonnés.
Il a cependant la particularité supplémentaire de pouvoir enregistrer
une partie de l'exécution de l’observable et donc de stocker
plusieurs anciennes valeurs et de les "rejouer" aux nouveaux
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.
450
Les 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.
451
Exercice
Actuellement, dans notre application, nous utilisant une fonction
isAuthenticated qui retourne true ou false si le user est authentifié ou
non.
D’un autre coté nous ne sauvgardons aucune information sur le user
connecté.
Nous voulons créer un store pour la gestion des utilisateurs nous
permettant de garder la trace du user authentifié ainsi que de gérer l’état
de l’utilisateur, à savoir qu’il est authentifié ou non.
Choisissez la bonne structure
452
Exercice
Faites en sortes d’avoir deux tabs dans la liste des cvs.
Le premier tab affichera les cvs des juniors (age < 40)
Le second tab affichera les cvs des seniors (age >= 40)
453
Opérateur de Multicasting
share
L'opérateur share va multicatser les valeurs émises par une source
Observable pour les abonnés.
Les multicatser signifie que les données sont envoyées à plusieurs
destinations.
En tant que tel, le partage vous permet d'éviter plusieurs
exécutions de la source Observable lorsqu'il existe plusieurs
abonnements.
share est particulièrement utile si vous avez besoin d'empêcher des
appels d'API répétés ou des opérations coûteuses exécutées par
Observables.
454
Opérateur de Multicasting
share
const sharedSource$ = interval(1000).pipe(
tap((ind) => [Link]('Processing: ', ind)),
map(() => [Link]([Link]() * 100)),
take(2),
share()
);
sharedSource$.subscribe((x) => [Link]('subscription 1: ', x));
sharedSource$.subscribe((x) => [Link]('subscription 2: ', x));
setTimeout(
() => sharedSource$.subscribe((x) => [Link]('subscription 3: ', x)),
1500
);
455
Opérateur de Multicasting
share
Lorsque vous vous abonnez à un Observable partagé, vous vous abonnez
en fait à un Subject exposé par l'opérateur de partage.
L'opérateur de partage gère également un abonnement interne à la
source Observable.
Le Subject interne est la raison pour laquelle plusieurs abonnés
reçoivent la même valeur partagée, car ils reçoivent des valeurs du sujet
exposé par l'opérateur de partage.
share tient un compte des abonnés.
Une fois que le nombre d'abonnés atteint 0, share se désabonnera de
l'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 de
l'Observable source.
456
Opérateur de Multicasting
share
const sharedSource$ = interval(1000).pipe(
tap((ind) => [Link]('Processing: ', ind)),
map(() => [Link]([Link]() * 100)),
take(2),
share()
);
sharedSource$.subscribe((x) => [Link]('subscription 1: ', x));
sharedSource$.subscribe((x) => [Link]('subscription 2: ', x));
setTimeout(
() => sharedSource$.subscribe((x) => [Link]('subscription 3: ', x)),
4500
);
457
Opérateur de Multicasting
shareReplay
Dans certains cas, ce dont vous avez vraiment besoin, c'est d'un
partage capable de se comporter comme le ferait un ReplaySubject.
Nous voulons donc qu’il partage à la fois la source Observable et
rejoue les dernières émissions pour les abonnés retardataires.
Donc, il ne conserve pas le nombre d'abonnés par défaut, mais vous
pouvez utiliser l'option refCount avec une valeur true pour activer ce
comportement.
Contrairement à share, shareReplay expose un ReplaySubject aux
abonnés. ReplaySubject().
458
Opérateur de Multicasting
shareReplay
const sharedSource$ = interval(1000).pipe(
tap((ind) => [Link]('Processing: ', ind)),
map(() => [Link]([Link]() * 100)),
take(2),
shareReplay()
);
sharedSource$.subscribe((x) => [Link]('subscription 1: ', x));
sharedSource$.subscribe((x) => [Link]('subscription 2: ', x));
setTimeout(
() => sharedSource$.subscribe((x) => [Link]('subscription 3: ', x)),
4500
);
459
Opérateur de Multicasting
shareReplay
Étant donné que shareReplay ne garde pas la trace d'un nombre
d'abonnés par défaut, il n'est pas en mesure de se désabonner de la
source 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 bufferSize et
refCount : shareReplay({ bufferSize : 1, refCount : true }).
shareReplay ne réinitialise jamais son ReplaySubject interne
lorsque refCount atteint 0, mais se désabonne de la source Observable.
Les abonnés en retard ne déclencheront pas une nouvelle exécution
de la source Observable et recevront jusqu'à N (bufferSize) émissions.
460
Opérateur de Multicasting
shareReplay
const sharedSource$ = interval(1000).pipe(
tap((ind) => [Link]('Processing: ', ind)),
map(() => [Link]([Link]() * 100)),
take(2),
shareReplay({ bufferSize: 2, refCount: true })
);
sharedSource$.subscribe((x) => [Link]('subscription 1: ', x));
sharedSource$.subscribe((x) => [Link]('subscription 2: ', x));
setTimeout(
() => sharedSource$.subscribe((x) => [Link]('subscription 3: ', x)),
4500
);
461
Hot Vs Cold Observable
Les Cold Observables commencent à émettre des valeurs uniquement quand on s’y inscrit. Les
Hot observables, par contre émettent toujours.
Les Cold Observables diffusent un flux par inscrit, ils sont unicast. Chaque nouvelle 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.
462
Quelques exemples de Hot Observable
fromEvent
Les Subject RxJs
Quelques opérateurs telque share() et shareReplay()
463
Exercice
Modifier l’affichage des détails d’une personne au click. Enlever tous
les outputs et remplacer les par l’utilisation d’un subject.
464
Se désinscrire
La méthode subscribe permet de s’inscrire à un observable.
465
Se désinscrire
complete
Lorsqu‘un observable se termine (méthode complete), tous les
abonnements 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 les
observables peuvent terminer, ou quand, il appartient donc au
développeur de faire les recherches nécessaires.
466
Se désinscrire
async pipe
La première et la plus courante solution pour gérer les
abonnements est l’async pipe.
Plutôt que de vous abonner à des observables dans vos composants
et de définir des propriétés sur votre classe pour les données de
l'observable, préférez l’utilisation de l’async pipe.
L’async pipe nettoie ses propres abonnements quand et selon les
besoins, vous déchargeant de cette responsabilité. Maintenant, cela
devrait être votre premier choix et fonctionnera pour la plupart
des scénarios.
467
Se désinscrire
async pipe
export class TestUnsbscribeComponent {
todos$: Observable<Todo[]>;
firstTodo$: Observable<Todo>;
constructor(private todoService: TodoService) {
[Link]$ = [Link]();
[Link]$ = [Link](1);
}
}
469
Se désinscrire
Et si on peut pas utiliser l’async pipe ?
export class TestUnsbscribeComponent implements OnDestroy{
todos$: Observable<Todo[]>;
firstTodo$: Observable<Todo>;
subscription: Subscription;
nb = 0;
constructor(private todoService: TodoService, private testService: TestService ) {
[Link]$ = [Link]();
[Link]$ = [Link](1);
[Link] = [Link]$.subscribe(() => [Link] ++);
}
ngOnDestroy(): void {
[Link]();
}
}
470
Se désinscrire
Et si on peut pas utiliser l’async pipe ?
export class TestUnsbscribeComponent implements OnDestroy{
todos$: Observable<Todo[]>;
firstTodo$: Observable<Todo>;
subscription: Subscription = new Subscription();
nbClick = 0;
nbUpdates = 0;
constructor(private todoService: TodoService, private testService: TestService ) {
[Link]$ = [Link]();
[Link]$ = [Link](1);
[Link]([Link]$.subscribe(() => [Link] ++));
[Link]([Link]$.subscribe(() => [Link] ++));
}
ngOnDestroy(): void {
[Link]();
}
471
Se désinscrire
Utiliser l’opérateur takeUntilDestroyed
L’opérateur takeUntilDestroyed de @angular/core/rxjs-interop
vous permet de gérer l’unsubscription.
Cette fonctionnalité est disponible à partir d’angular 16
[Link]$.pipe(
map((element) => element * 3),
filter((element) => element % 2 == 0),
takeUntilDestroyed()
);
472
Se désinscrire
Utiliser un signal
Une autre façon de se désabonner, et peut-être une manière plus
élégante, consiste à utiliser des signaux.
Les signaux sont un moyen d'utiliser d'autres flux pour contrôler les
flux auxquels vous êtes peut-être abonné.
Les signaux sont obtenus avec l'opérateur takeUntil dans le pipe et
tout autre observable. L'autre observable peut être un Subject ou un
autre observable ; tout ce qui émet un next.
473
Se désinscrire
Et si on peut pas utiliser l’async pipe ?
export class TestUnsbscribeComponent implements OnDestroy{
todos$: Observable<Todo[]>;
signal$ = new Subject();
nbClick = 0;
constructor(private todoService: TodoService, private testService: TestService ) {
[Link]$ = [Link]();
[Link]$
.pipe(takeUntil([Link]$))
.subscribe(() => [Link] ++);
}
ngOnDestroy(): void {
[Link]$.next('i am emiting stop emiting on your side :S');
[Link]$.complete();
}
}
474
4
signaux et rxJs
7
5
Interopérabilité
Les signaux ne sont pas la pour enlever RxJs mais plutôt pour limiter
son utilisation la ou il est le plus approprié de l’utiliser.
RxJS se marie mieux avec les tâches asynchrones alors que les signaux
sont synchrones.
Nous pouvons utiliser des signaux et des observables ensemble, et
nous pouvons les convertir l’un en l’autre.
Deux fonctions pour faire cela sont disponibles dans le tout
nouveau package : @angular/core/rxjs-interop
toObservable qui convertit un signal en un observable
toSignal qui convertit un observable en un signal. Elle prend en
premier paramètre l’observable et en second paramètre un objet
ToSignalOptions permettant de définir la valeur initiale.
4
signaux et rxJs
7
6
Interopérabilité
cvs = toSignal(
[Link](),
{ initialValue: [] }
);
Angular
HTTP
AYMEN SELLAOUTI
477
Objectifs
1. Mise en place du service HttpClient
2. Utiliser le service HttpClient
3. Comprendre le principe d’authentification via les tokens
4. Utiliser les protecteurs de routes (les guards)
5. Utiliser les Interceptors afin d’intercepter les requêtes Http
6. Déployer votre application en production
478
HTTP
Angular est un Framework FrontEnd
Pas d’accès à la BD
Le Service HttpClient
479
Installation de HTTP
Afin d’utiliser le service HttpClient, vous devez le configurez en
utilisant la méthode provideHttpClient, que la plupart des
applications incluent dans la clé providers de votre [Link].
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
],
};
480
Installation de HTTP
Une fois configuré, vous pouvez injecter votre services la ou vous
en avez besoin.
http = inject(HttpClient);
481
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ération à
la place.
fetch est une API plus moderne et est disponible dans quelques
environnements où XMLHttpRequest n'est pas pris en charge.
Il présente quelques limitations, telles que le fait de ne pas produire
d'événements de progression du téléchargement.
providers: [
provideHttpClient(
withFetch()
)
], 482
Interagir avec une API Get Request
Afin d’exécuter une requête get le module http nous offre une
méthode get.
Cette méthode retourne un Observale.
Cet observable a un objet contenant 3 callback function comme
paramètres.
Une en cas de réponse next
Une en cas d’erreur error
La troisième en cas de fin du flux de réponse. complete
483
Interagir avec une API Get Request
[Link](API_URL).subscribe({
next:(response:Response)=>{
//ToDo with DATA
},
error: (err:Error)=>{
//ToDo with error
},
complete: () => {
[Link]('Data transmission complete');
}
});
484
Interagir avec une API POST Request
Afin d’exécuter une requête POST le module http nous offre une
méthode post.
Cette méthode retourne un Observale.
Diffère de la méthode get avec un attribut supplémentaire : body
Cette observable a 3 callback function comme paramètres.
Une en cas de réponse
Une en cas d’erreur
La troisième en cas de fin du flux de réponse.
485
Interagir avec une API POST Request
[Link](API_URL,dataToSend).subscribe({
next:(response:Response)=>{
//ToDo with DATA
},
error: (err:Error)=>{
//ToDo with error
},
complete: () => {
[Link]('Data transmission complete');
}
});
486
Documentation
[Link]
487
Exercice
Accéder au site [Link]
Utiliser l’API des posts pour afficher la liste des posts. En attendant le
chargement des données afficher un message « loading… ».
Ajouter un input. A chaque fois que vous écrivez un élément dans cet
input il sera ajouté dans la liste.
488
Les headers
Afin d’ajouter des headers à vos requêtes, le HttpClient vous offre la classe
HttpHeaders.
Cette classe est une classe immutable (read Only).
[Link]
Elle propose une panoplie de méthode helpers permettant de la manipuler.
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.
[Link]
489
Les paramètres
Afin d’ajouter des paramètres à vos requêtes, le HttpClient vous offre la classe
HttpParams.
Cette classe est une classe immutable (read Only).
Elle propose une panoplie de méthode helpers permettant de la manipuler.
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 HttpParams permettant un
chainage d’appel.
[Link]
490
Authentification
491
S’authentifier avec Loopback
Loopback offre un mécanisme d’’authentification prêt à l’emploi.
Avec l’api de la classe user il vous permet d’ajouter des users et de vous
connectez. Il permet aussi avec son module Loopback acl de créer des
restrictions sur l’api.
Une fois vos uri protégée, vous devez vous connecter pour pouvoir les
utiliser.
En vous connectant, il vous offrira un token. Vous devez l’utiliser à
chaque appel de votre api.
492
Installer loopback
Nous allons installer la version lts :
npm install -g loopback-cli
Vérifier votre version avec lb –v
Créer votre première application avec la commande lb
Renseigner le nom de votre projet
Renseigner le type de votre projet
C’est fait
493
Loopback : création du modèle
Créer votre modèle avec lb model
Suivez les étapes, ajouter votre modèle et ajouter les champs qui le
compose.
494
Loopback : création du modèle
495
{
"name": "personne",
"base": "PersistedModel",
"idInjection": true,
"options": {
Modèle généré
"validateUpsert": true
},
"properties": {
"cin": {
"type": "number",
"required": true
},
"name": {
"type": "string",
"required": true
},
"firstname": {
Dans ce cas nous allons utiliser "type": "string",
"required": true
une base de données MySql avec },
"age": {
un id en auto-increment. "type": "number",
"required": true
},
"path": {
"type": "string",
"required": true
},
"job": {
"type": "string",
"required": true
}
},
496
Associer votre modèle à une base de
données MySql
Installer le mysql-connector
npm install loopback-connector-mysql --save
Configurer votre datasource dans le fichier [Link] et ajouter
la configuration de votre base de données mySql.
Associer la datasource à votre modèle dans le fichier model-
[Link].
497
Associer votre modèle à une base de
données MySql
{
"db": {
"name": "db",
"connector": "memory" .
}, .
"personneDb": { .
"host": "localhost",
"port": 3306, "Note": {
"url": "", "dataSource": "db"
"database": "test_pdo", },
"password": "", "personne": {
"name": "personneDb", "dataSource": "personneDb",
"user": "root", "public": true
"connector": "mysql" }
} [Link]
}
[Link]
498
Lancer votre application
Pour lancer votre application exécuter la commande node .
499
Exercice
Associer votre application à votre API Loopback. Tester les fonctionnalités d’ajout de suppression
et de modification et de sélection de l’ensemble des personnes.
Sachant que pour sélectionner une personne dont le nom contient une chaine donnée, loopback
utilise la syntaxe suivante :
{"where":{"name":{"like":"%${name}%"}}}
Ceci doit être fourni dans les paramètres de votre requête avec la clé filter. Tester le sur votre
swagger.
Ajouter un champ input. A chaque caractère saisie, la liste des choix doit automatiquement changer
et n’afficher que les cvs qui contiennent la chaine saisie. En sélectionnant un des choix, rediriger
l’utilisateur vers les détails du cv sélectionné.
500
Ajouter une acl avec loopback
Nous voulons ajouter des contraintes d’accès à notre application.
Nous voulons que seul les utilisateurs connectés puissent ajouter et supprimer des personnes.
Ajouter les ACL en utilisant la commande lb acl.
501
Ajouter une acl avec loopback
Vérifier que votre modèle a été modifié :
"acls": [
{
"accessType": "WRITE",
"principalType": "ROLE",
"principalId": "$unauthenticated",
"permission": "DENY"
}
502
Ajouter une acl avec loopback
Essayer d’ajouter une personne avec une requête de type POST. Utiliser directement votre swagger.
503
Ajouter le token dans la requête
Si la ressource demandé est contrôlé avec un token, vous devez y insérer le token afin d’être
authentifié au niveau du serveur.
Pour ajouter un token vous pouvez le faire via un objet HttpParams. Cet objet possède une
méthode set à laquelle on passe le nom du token ‘access_token’ suivi du token.
Vous devez ensuite l’ajouter comme paramètre à votre requête.
const params = new HttpParams()
.set('access_token', [Link]('token'));
return [Link]([Link], personne, {params});
[Link]
504
Ajouter le token dans la requête
Une seconde méthode consiste à ajouter dans le header de la requête avec comme name
‘Authorization’ et comme valeur ‘bearer’ à laquelle on concatène le Token.
Pour se faire, créer un objet de type HttpHeaders.
Utiliser sa méthode append afin d’y ajouter ses paramètres.
Ajouter la à la requête.
505
Sécuriser vos routes
Dans vos applications, certaines routes ne doivent être accessibles que si vous êtes authentifié. Ce
cas d’utilisation se répète souvent et s’est un sous cas de la sécurisation de vos routes.
Angular a pris en considération ce cas en fournissant un mécanisme via l’utilisation des Guard.
506
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.
Le routeur supporte plusieurs types de guards, par exemple :
CanActivate permettre ou non l’accès à une route.
CanActivateChild permettre ou non l’accès aux routes filles.
CanDeactivate permettre ou non la sortie de la route.
507
Guard
A partir d’Angular 14 et la possibilité d’utiliser la fonction inject dans tous les
contexte d’injection, Angular préconise les fonctionnals Guards.
508
Guard / canActivate 3
{
path: 'lampe',
component: ColorComponent,
canActivate: [AuthGuard]
},
509
Exercice
Ajouter les guards nécessaires afin de sécuriser vos routes.
510
Guard / canActivateChild
Le guard canActivateChild est un service Angular qui permet de
protéger les routes enfants en vérifiant si l'utilisateur a les autorisations
appropriées pour accéder à ces routes.
Il retourne true si l'utilisateur a les autorisations requises et false sinon.
Cela permet de contrôler l'accès aux pages enfants d'une route donnée.
511
Exercice
Toutes les routes commençant par /cv ne doivent être accessible que
pour les personnes authentifiées.
512
Guard / canDeactivate
Le guard canDesactivate est un service Angular qui permet de
bloquer la sortie d’une route.
Il retourne true si l'utilisateur a les autorisations requises pour quitter
la route et false sinon.
Il est généralement utilisé pour s’assurer de ne pas quitter une page au
cas ou il y a des données non encore enregistrées ou s’il y a un upload
en cours et que vous ne voulez pas quitter la page tant que ce n’est pas
encore terminé.
Il ne s’applique pas aux ‘children Routes’.
513
Guard / canDeactivate
Il est fortement couplé au composant
@Component({
selector: 'app-form', canDeactivate(
template: ` component: FormComponent,
<form> currentRoute: ActivatedRouteSnapshot,
<input [(ngModel)]="formData" name="formData"> currentState: RouterStateSnapshot,
</form> nextState?: RouterStateSnapshot
<button (click)="save()">Save</button> ): Observable<boolean> | Promise<boolean> | boolean {
` if (![Link] && [Link] !== '') {
}) return confirm('Are you sure you want to leave? You have
export class FormComponent implements OnInit, unsaved changes.');
CanDeactivate<FormComponent> { }
formData: string; return true;
saved = false; }
save() { }
[Link] = true;
} 514
Exercice
Faite en sorte que lorsque vous êtes dans le TodoComponent, si un
des champs est rempli et que l’utilisateur veux quitter la page, afficher lui
un message de confirmation pour lui demander s’il est sur de vouloir
quitter la page.
515
Les intercepteurs
A chaque fois que nous avons une requête à laquelle nous devons ajouter le token, nous devons
refaire toujours le même travail.
Pourquoi ne pas intercepter les requêtes HTTP et leur associer le token s’il est la à chaque fois ?
Un intercepteur Angular (fournit par le client HTTP) va nous permettre 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.
En implémentant cette interface, chaque intercepteur va devoir implémenter la méthode intercept.
516
Les intercepteurs
import { HttpInterceptorFn } from "@angular/common/http";
export const newInterceptor: HttpInterceptorFn = (req, next) => {
return next(req);
};
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<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
return [Link](request);
}
}
517
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
bootstrapApplication(AppComponent, {providers: [
provideHttpClient(
withInterceptors([firsInterceptor, secondInterceptor]),
)
]});
518
Les intercepteurs
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
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
// DI-based interceptors must be explicitly enabled.
withInterceptorsFromDi(),
),
{provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true},
]}
519
Les intercepteurs : changer la requête
Les instances HttpRequest et HttpResponse sont immuables. Ceci
implique que les intercepteurs ne peuvent pas les modifier
directement.
Solution : les cloner, à l'aide de la méthode clone() et en spécifiant
quelles propriétés doivent être mutées dans la nouvelle instance.
520
Cloner une requête
const newReq = [Link]({
headers: new HttpHeaders()
// faites ce que vous voulez ici ajouter des headers, des params …
});
// Chainer la nouvelle requete avec [Link]
return [Link](newReq);
521
Intercepter les réponses
Afin d’intercepter les réponses ou les erreurs, il faut récupérer la
réponse et vérifier s’il y a une erreur. Dans ce cas, il faut faire le
traitement souhaité.
return next(req).pipe(
tap((response) => {
if ([Link] === [Link]) {
[Link]( [Link], "returned a response with status", [Link] );
}
}),
catchError((e) => {
[Link]("Error !!!"); [Link]({ e });
return EMPTY;
})
);
522
Angular
Les variables d’environnement
Avec Angular, vous pouvez définir plusieurs configurations propre à chaque
environnement que vous définissez.
Avant Angular 15, ces variables été prêtes à la création du projet.
Avant la version 15.1 d’Angular il faut les créer manuellement
A partir de la version 15.1, vous pouvez les générer à partir du CLI avec la
commande ng generate environments
Cette commande va créer un dossier environments sous le dossier src avec deux
fichiers, [Link] et [Link]
523
Angular
Les variables d’environnement
Maintenant, dans le fichier [Link], et dans chaque bloc de l’environnement
cible, vous allez dire à Angular quel fichier doit remplacer le fichier de base.
Dans cet exemple, on spécifie à Angular d’utiliser le fichier
[Link] lorsqu’on fait appel à [Link], lorsqu’on lance le
serveur en devellopement.
"development": { export const environments = {
… test: 'prod',
"fileReplacements": [ }
{ [Link]
"replace": "src/environments/[Link]",
"with": "src/environments/[Link]"
} export const environments = {
] test: 'prod',
} }
[Link]
},
}, 524
Angular
Les variables d’environnement
Vous devez toujours appelez le fichier de base dans [Link]
C’est en utilisant les remplacement qu’Angular saura quel fichier appeler.
import { environments } from '../environments/environments';
@Component({
selector: 'app-root',
templateUrl: './[Link]', export const environments = {
styleUrls: ['./[Link]'], test: 'prod',
}) }
[Link]
export class AppComponent {
constructor() {
[Link]('env', [Link]);
export const environments = {
}
test: 'prod',
}
}
[Link]
525
Angular
Les variables d’environnement
Pour spécifiez quel environnement utiliser, ajouter l’option --configuration que
ce soit au build ou au serve.
ng build --configuration=staging
import { environments } from '../environments/environments';
@Component({
selector: 'app-root', export const environments = {
templateUrl: './[Link]', test: 'prod',
styleUrls: ['./[Link]'], }
[Link]
})
export class AppComponent {
constructor() { export const environments = {
[Link]('env', [Link]); test: 'prod',
} }
} [Link]
526
Exercice
Créer deux fichiers de configuration prod et dev et faite en sorte de
les utiliser et de les tester.
527
Angular
Les stratégies de compilation
Il existe deux techniques de compilation dans Angular:
Just-In-Time (JIT) Compilation: La compilation JIT se produit à
l'exécution du code. Le compilateur Angular est intégré au navigateur et
compile le code à chaque fois que l'application est lancée. Cela signifie que le
code est compilé en temps réel.
Ahead-of-Time (AOT) Compilation: La compilation AOT se produit
à la génération du code, avant que l'application ne soit exécutée. Le
compilateur Angular compile le code en un fichier JavaScript statique qui est
exécuté directement par le navigateur sans nécessiter de compilation
supplémentaire.
528
Angular
Les stratégies de compilation
[Link] 529
Angular
Les stratégies de compilation
Pourquoi choisir le AOT
Rendu plus rapide Avec AOT: le navigateur télécharge une version pré-compilée
de l'application. Le navigateur charge le code exécutable afin qu'il puisse restituer
l'application immédiatement, sans attendre de compiler l'application au préalable.
Moins de requêtes asynchrones Le compilateur intègre des modèles HTML
externes et des feuilles de style CSS dans le JavaScript de l'application, éliminant
ainsi les requêtes ajax distinctes pour ces fichiers source.
Taille de téléchargement du framework Angular plus petite Il n'est pas
nécessaire de télécharger le compilateur Angular si l'application est déjà compilée.
Le compilateur est à peu près la moitié d'Angular lui-même, donc l'omettre réduit
considérablement la charge utile de l'application.
Cependant ceci n’est pas toujours valide pour les grandes applications puisque
on charge le code compilé qui est plus lourd. PENSEZ AU LAZY LOADING
[Link] 530
Angular
Les stratégies de compilation
Pourquoi choisir le AOT
Détecter les erreurs de modèle plus tôt Le compilateur AOT détecte
et signale les erreurs de liaison de modèle pendant l'étape de
construction avant que les utilisateurs ne puissent les voir.
Meilleure sécurité AOT compile les modèles et composants HTML
dans des fichiers JavaScript bien avant qu'ils ne soient servis au client.
Sans modèles à lire et sans évaluation HTML ou JavaScript risquée
côté client, il y a moins de possibilités d'attaques par injection.
[Link] 531
Déploiement
Afin de déployé votre application, il vous suffit d’utiliser la commande
suivante :
ng build --prod
Un dossier dist sera créer contenant votre projet
Pour tester localement votre projet, télécharger un serveur HTTP
virtuel avec la commande suivante :
Npm install http-server –g
Lancer maintenant votre projet à l’aide de cette commande :
http-server dist/NomDeVotreProjet
[Link] 532
Déploiement
Afin de voir le comportement de votre build dans des conditions qui
s’approche des conditions réelles de déploiement, lancez la commande
ng build --watch
Ensuite ouvrez un second terminal et lancer votre serveur local.
Si vous utilisez http-server lancez la commande
http-server dist/NomDeVotreProjet
Dans votre serveur d’application, faite en sorte d’avoir une redirection de
toutes les routes de votre application différentes de la page d’accueil.
Ceci est différent d’un serveur à un autre
Testons le avec Github
[Link] 533
Déployer votre application sur GitHub
Pages (angularCli >= 8.3)
Créer un repository et mettez y votre projet
installer angular-cli-ghpages : ng add angular-cli-ghpages@next
Ajouter cette configuration dans votre fichier [Link] si ca n’a pas été fait automatiquement
"deploy": {
"builder": "angular-cli-ghpages:deploy",
}
Lancer la commande ng deploy --base-href=/the-repositoryname/
Accéder à votre page [Link]
En cas de mise à jour, relancer le même code.
[Link]
534
Déployer votre application sur GitHub
Pages (angularCli >= 8.3)
Ajouter cette configuration dans votre fichier [Link] et utiliser uniquement ng deploy
"deploy": {
"builder": "angular-cli-ghpages:deploy",
"options": {
"baseHref": "[Link]
}
}
535
Mesurer les performances
Afin d’optimiser le chargement de votre application, vous devez
faire en sorte d’avoir le bundle le plus optimisé possible.
Afin d’analyser votre bundle, vous pouvez utiliser l’outil
source-map-explorer :
[Link]
Afin d’installer cet outil lancez la commande :
npm install source-map-explorer --save-dev
Lancez le build en incluant source-map : ng build --source-map
Vous allez remarquez l’existence de fichiers ‘.map’
[Link] 536
Mesurer les performances
Maintenant pour visualiser la sourceMap de votre code lancer la
commande :
source-map-explorer dist/**/*.js
Préférez ajouter la commande dans votre fichier [Link]
"explorer":"source-map-explorer dist/**/*.js",
"explorer:main":"source-map-explorer dist/**/main*.js"
[Link] 537
Mesurer les performances
538
Mesurer les performances
Vous pouvez utiliser le plugin lighthouse
Il vous permet de générer un rapport sur votre application sur
plusieurs catégories
[Link]
nefehnmjammfjpmpbjk?hl=fr
539
Mesurer les performances
Maintenant pour visualiser la sourceMap de votre code lancer la
commande :
source-map-explorer dist/**/*.js
Préférez l’ajout de la commande dans votre fichier [Link]
"explorer":"source-map-explorer dist/**/*.js",
"explorer:main":"source-map-explorer dist/**/main*.js"
[Link] 540
Mesurer les performances
Dans [Link], vous pouvez configurer source-map avec la
propriété sourceMap.
Elle prend comme paramètre un booléen ou un objet avec les 4
options possibles "development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": {
"hidden": false,
"scripts": true,
"styles": true,
"vendor": false
},
[Link] 541
Mesurer les performances
Quelques techniques pour améliorer votre
Bundle Size
En analysant le contenu de votre bundle, vous allez remarquer que
certaines bibliothèques prennent beaucoup d’espace.
Si l’une de vos bibliothèque prend beaucoup d’espace, que’elle est
implémenté en CommonJS et que vous l’utilisez pour juste une ou
deux fonctionnalités, essayer de les implémentez.
Si elle présente une grande complexité, essayez de chercher une
autre bibliothèque moins lourde.
Préférez les Bibliothèques ES6 et +.
[Link] 542
Mesurer les performances
Quelques techniques pour améliorer votre
Bundle Size
Vous devez changer votre code afin qu’il s’aligne et qu’il soit optimal
pour le tree-shaking.
Le tree-shaking est le mécanisme qui permet d’éliminer le code
mort; C’est-à-dire le code qui ne sert à rien.
Webpack lors de l’opération du bundling va détecter le dead code et
le marquer comme ‘module non utilisé’.
Ensuite des outils comme UglifyJS, vont uglifyer votre code et en
même temps éliminer le dead code.
L’idée est donc d’aider Webpack à trouver ce code inutile.
[Link] 543
Mesurer les performances
Quelques techniques pour améliorer votre
Bundle Size
[Link] 544
Mesurer les performances
Quelques techniques pour améliorer votre
Bundle Size
N’importez jamais toutes les fonctions de votre bibliothèques.
Ceci impliquera que vous avez besoins de toutes la bibliothèque. Ceci
implique que leur code, même si vous ne l’utilisez jamais, sera inclut
dans votre bundle et ne sera pas comptabilisé comme dead code.
[Link] 546
Mesurer les performances
Quelques techniques pour améliorer votre
Bundle Size
Pour la partie css. Si vous importez des fichiers css au niveau de vos
composants plusieurs fois, ceci impliquera la duplication de ses
styles.
N’importez que ce qui est spécifique dans votre composant.
[Link] 547
Angular
Les modules
AYMEN SELLAOUTI
548
Qu’est ce qu’un module
C’est une classe avec une décoration NgModule
Un module est un conteneur qui englobe un ensemble de fonctionnalités
liées.
Les applications Angular sont modulaire.
Un application Angular contient au moins un Module : AppModule.
Un Module Angular peut contenir des composants, des provider de service…
Une application simple est généralement composé d’un seul module. Par contre
dès que votre application grandit penser à la séparer en Modules.
Chaque module vie séparée des autres modules. Par défaut il n’expose rien,
tous ce qui est à l’intérieur du module reste uniquement dans le module tant
qu’on ne l’exporte pas.
Lorsqu’on importe un module, on importe réellement tous ce qu’il exporte.
[Link] 549
Rôle d’un module
Organise votre application en des briques fonctionnelles
550
Définition d’un module
L’annotation (decorator) @NgModule identifie
un module Angular. import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
L’annotation prend en paramètre un objet spécifiant à
import { AppComponent } from './[Link]';
Angular comment compiler et lancer l’application.
imports : tableau contenant les modules utilisés. @NgModule({
imports: [ BrowserModule ],
declarations : tableau contenant les classes de vue appartenant
declarations: [ AppComponent ],
à ce module, i.e. composants, directives et pipes de l’application. bootstrap: [ AppComponent ]
exports : tableau des classes de vues à exporter. })
providers : déclaration des services export class AppModule { }
bootstrap : indique le composant exécuter au lancement de
l’application et elle ne concerne que le module racine.
551
declarations
Dans la partie declarations, faite en sorte que chaque composant,
directive ou pipe soit associé à un et un seul module. Ne déclarer pas un
même composant dans deux modules différents.
Ne déclarer que les composants, directives et les pipes dans cette partie.
Tous les composants, directives et les pipes déclarés sont privés par
défaut. Ils ne sont accessible que pour les composants, directives et les pipes
déclarés dans le même module.
Pour utiliser un de ces éléments à l'extérieur du module, il faudra penser à
les exporter.
552
Routing
Vous pouvez créer les routes de vos modules de la même manière
que pour le routing de l’AppModule.
553
Exemple pour le TodoModule
import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core";
import { NgModule } from "@angular/core"; import { Route, RouterModule } from "@angular/router";
import { FormsModule } from "@angular/forms"; import { NF404Component } from "../nf404/[Link]
nent";
import { TodoComponent } from "./[Link]"; import { TodoComponent } from "./[Link]";
import { TodoRouting } from "./[Link]";
const routes: Route[] = [
@NgModule({ { path: "todo", component: TodoComponent },
declarations: [TodoComponent], { path: "**", component: NF404Component },
imports: [CommonModule, FormsModule, TodoRouting], ];
// Si vous n’avez pas besoin de TodoComponent à l’exterieur,
ne l’exporter pas @NgModule({
exports: [TodoComponent], imports: [[Link](routes)],
}) exports: [RouterModule],
export class TodoModule {} })
export class TodoRouting {}
554
Exercice
Implémentez le CvModule.
555
Shared Module
FirstModule SecondModule
SharedModule
FirstComponent FirstComponent
SecondComponent SecondComponent
GenericDirective GenericDirective
SpecificFeature1Component SpecificFeature2Component
556
Shared Module
@NgModule({
declarations: [
FirstComponent,
SecondComponent,
GenericDirective,
],
imports: [
CommonModule
]
exports: [
FirstComponent,
SecondComponent,
GenericDirective,
]
})
export class SharedModule {
}
557
Lazy Loading
Par défaut, tout les modules que vous
déclarer au niveau du AppModule sont
chargé au lancement de l’application.
Ceci pose un problème au niveau du
Bundle généré de votre application.
Une grande application aura une taille assez
conséquente ce qui peut provoquer un
problème au chargement de l’application et
donc un problème d’expérience utilisateur.
L’idée du lazyLoading et de charge au
départ le module principale et puis de ne
charger un module que si on appelle l’une de
ses routes.
Ceci va nous faire gagner en performance.
558
Lazy Loading
Afin d’implémenter le Lazy Loading, on doit suivre les étapes suivantes :
1. Le module à charger doit lui-même gérer sa partie routing
2. Au niveau du routing principal (AppRoutingModule), créer une route, ajouter lui un path ‘ca sera
le préfixe de toutes les routes du module’, et ajouter une nouvelle clé qui est loadChildren.
Cette clé va informer angular et lui demander de ne charger le module associé que lorsque on
appelle le path défini.
3. Ce paramètre prend ou une chaine de caractère qui spécifie le module à charger ou une
callback function.
4. Finalement enlever les imports des modules lazy loaded au niveau du AppModule
{
{ path: "cv",
path: "cv", loadChildren: () => import('./cv/[Link]').then(
loadChildren: "./cv/[Link]#CvModule", m => [Link]
}, ),
}, 559
Exercice
Testez le lazy loading avec le CvModule
560
Lazy Loading
Injecteur fils
Lorsque vous utilisez le lazy loading, un Injecteur est
automatiquement crée lors du chargement de votre module. C’est le
‘child injector’ ou injecteur fils.
Lorsque vous importer un module X, généralement tous les modules
qui l’importent se partage les mêmes instances providées par ce
module.
Cependant, lorsque un module Y lazy loaded importe le module X, il
n’utilise pas le module Injector maos son propre injector
Ceci implique la création d’une nouvelle instance des services qu’il
provide.
561
Lazy Loading
Injecteur fils
Le pattern forRoot/ forChild
Dans certains cas, le fait d’avoir plusieurs instances ne dérange pas,
mais ceci n’est pas toujours le cas. Prenons l’exemple du RouterModule
ou on veut avoir une instance commune de notre RouterService pour
toute l’application.
Afin de gérer ca on ne va plus importer le Module entier mais une
partie du module.
La partie import peut accepter un module ou une classe de type
ModuleWithProvider.
Ceci vous permet de spécifier ce que vous voulez provider exactement
562
Lazy Loading
Injecteur fils
Le pattern forRoot/ forChild
563
Lazy Loading
Injecteur fils
Le pattern forRoot/ forChild
Un deuxième avantage du forRoot / forChild Pattern est de pouvoir
avoir des modules configurables.
Votre méthode forChild peut avoir des paramètres que vous
injecter pour les récupérer et paramétrer vos services.
564
Guard / canLoad
Le guard canLoad est un service Angular qui permet de conditionner
le chargement d’un module.
Il est généralement associé à un module chargé en utilisant le
lazyLoading.
Ceci permet d’optimiser le chargement du module. Si vous n’avez pas
le droit d’accéder au module, pourquoi le charger ?
@Injectable({
providedIn: 'root'
})
export class CanLoadAuthGuard implements CanLoad {
constructor(private router: Router) {}
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> {
return true;
}
} 565
Guard / canLoad
Le Guard canLoad peut être depricated à cause de sa non gestion des
lazy loaded Component.
Une alternative qui peut remplacer le canLoad est le canMatch
Guard proposé dans la version 14 d’Angular.
566
Exercice
Votre CvModule étant lazy loaded, faite en sorte qu’il ne soit chargé
que si la personne est authentifié.
567
Guard / canMatch
[Link] 568
Guard / canMatch
Le Guard canMatch permet de spécifier si le router doit matcher
(et non activer) une route ou non.
Ceci diffère des autres guard. En effet, dans ce cas le routeur ira voir
les autres routes et continuera le processus de matching.
569
Guard / canMatch
@NgModule({
imports: [
[Link]([
{
path: 'team/:id',
component: TeamComponent,
loadChildren: () => import('./team').then(mod => [Link]),
canMatch: [CanMatchTeamSection]
},
{
path: '**',
component: NotFoundComponent
}
])
],
providers: [CanMatchTeamSection, UserToken, Permissions]
})
class AppModule {} 570
Guard / canMatch
{
path: 'team/:id',
component: TeamComponent,
loadChildren: () => import('./team').then(mod => [Link]),
canMatch: [(route: Route, segments: UrlSegment[]) => true]
},
{
path: 'team/:id',
component: TeamComponent,
loadChildren: () => import('./team').then(mod => [Link]),
canMatch: [(route: Route, segments: UrlSegment[]) => {
const authService = inject(AuthService);
return [Link]();
}]
},
571
Exercice
Créer deux routes home, une pour les personnes authentifiés et l’autre
pour les visiteurs.
Faite en sorte d’avoir la même route mais d’afficher le bon composant
en se basant sur le fait qu’il soit authentifié ou non.
Imaginez le même scénario pour un DashboardComponent
572
Preloading Lazy Loading
Le problème qu’on peut identifier avec le lazy loading est le fait
qu’en passant d’un module à un autre on aura toujours un chargement
des nouveaux modules.
Si vos modules sont très volumineux ou que la connexion du client
est mauvaise, il y aura plusieurs latences. Ceci va provoquer un
problème avec l’utilisateur.
La question qui se pose est : Y a-t-il un moyen de personnaliser les
stratégies de chargement ???
573
Preloading Lazy Loading
La méthode forRoot de votre RouterModule prend en second
paramètre un objet vous permettant de configurer la stratégie de
chargement avec la propriété preloadingStrategy.
Cette propriété prend en paramètre, par défaut NoPreloading.
La deuxième valeur qu’elle peut prendre est PreloadAllModules.
Elle demande à Angular de précharger tous les lazyLoaded
Modules une fois le Module principal chargé.
import { PreloadAllModules } from "@angular/router";
@NgModule({
imports: [[Link](routes, {
preloadingStrategy: PreloadAllModules
})],
exports: [RouterModule],
}) 574
Preloading Lazy Loading
Créer votre propre startégie
Avec PreloadAllModules, tous les modules sont préchargés, ce qui peut
en fait créer un goulot d'étranglement si l'application a un grand nombre
de modules à charger.
Une meilleure stratégie serait de charger sélectivement les modules
requis au démarrage. Par exemple, module d'authentification, module
principal, module partagé, etc.
Pour précharger sélectivement un module, nous devons utiliser une
stratégie de préchargement personnalisée.
Créez d'abord une classe qui implémente l’interface PreloadingStrategy.
La classe doit implémenter la méthode preload().
C’est cette méthode qui détermine s'il faut précharger le module ou non.
575
Preloading Lazy Loading
Créer votre propre startégie
La signature de la fonction preload prend en paramètre un objet Route
représentant la route ciblé et en deuxième paramètre une fonction load qui retourne
un Observable.
Si vous retourne la méthode load, le module sera préchargé.
Si vous ne voulez pas le précharger retourner un Observable de null.
@Injectable({providedIn: 'root'})
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if ([Link]["preload"]) {
return load();
}
else {
of(null);
}
}
} 576
Lazyload Standalone Component
Vous pouvez faire du lazy loading pour les standalone component
avec la clé loadComponent.
{
path: 'track',
loadComponent: () => import('./track-by/[Link]')
.then(c => [Link])
}
577
Angular
Optimisation
AYMEN SELLAOUTI
578
Architecture de la couche Vue
Lors de la construction d'une application Angular, l'une des questions les
plus fréquentes auxquelles nous sommes confrontés dès le début est :
comment structurons-nous notre application ?
Une première réponse est : Arbre de composant.
Cependant, d’autres questions se posent :
Est-ce que tous les composants sont pareils?
Comment les composants doivent-ils interagir ?
Devons nous injecter des services dans n'importe quel composant
?
Comment puis-je rendre mes composants réutilisables dans toutes les vues ?
579
Architecture de la couche Vue
Smart Components vs Presentational
Components
Un design pattern répond à ces questions en divisant les composants
essentiellement en deux types
Composants intelligents : également appelés parfois composants
applicatif ou composants conteneur.
Composants de présentation : également appelés parfois
composants purs ou composants muets
580
Architecture de la couche Vue
Smart Components vs Presentational Components
Presentational Component
Ce composant a uniquement pour rôle comme son nom l’indique, la
présentation des données sans avoir d’informations sur la source de ces
derniers.
Ceci vous permet d’avoir des composants indépendants et
réutilisables.
Afin de paramétrer ce type de composant, utiliser le @Input
Afin d’informer d’une action ou envoyer les données modifiées, utiliser
le @Output.
Essayer d’utiliser ce type de composant autant que vous le pouvez.
581
Architecture de la couche Vue
Smart Components vs Presentational Components
Smart Component
Ces composants sont smarts. Ils gèrent donc la partie
fonctionnelle.
Ces composants sont chargés d'interagir avec la couche de service
et de récupérer les données.
Ils sont aussi chargés de transmettre aux composants de
présentation les informations nécessaires.
Ils doivent aussi gérer les données et les évènements envoyés par
les Presentational Component.
582
Change Detection
C’est quoi ?
Change Detection est l’un des mécanisme les plus importants dans
Angular.
Il permet de suivre les changements d’état de votre application et
d’afficher les modifications dans votre vue.
Il garantit que l’interface utilisateur suit toujours d’une façon
synchrone l’état interne de votre application.
583
Change Detection
Chaque composant dans Angular est représenté par une structure
appelé View. Elle contient entre autre l’instance de la classe
Composant appelée componentInstance ainsi que la liste des
éléments du DOM représentant le Template.
[Link] 584
Change Detection
Ensuite, quand le compilateur traite le composant, il identifie les
élément qui nécessite un changement lors du changement d’état.
585
Change Detection
Pour chacun de ces éléments, il crée des objets Bindings. C’est une
structure de données qui informe sur deux choses :
Que voulons nous mettre à jour dans le Dom
Ou récupérer la nouvelle valeur
586
Change Detection
Ensuite, dès qu’un change
Detectin est déclenché,
Angular va parcourir
l’ensemble des Views
(Component) et évaluer la
nouvelle expression du
Binding et la comparer à la
précédente.
Si la valeur est modifiée, elle
met à jour le DOM.
587
Change Detection
588
Change Detection
Quand déclencher un Change Detection
Un Change Detection est déclenché dans ces cas d’utilisation
1. Initialisation des composants. Par exemple, lors du lancement d'une
application angular, Angular charge le composant principal et
déclenche [Link]() pour appeler la détection de
changement et le rendu de la vue.
2. Les event listener du DOM peuvent mettre à jour les données dans
un composant Angular et déclencher le Change Detection.
3. Les requêtes HTTP.
[Link] 589
Change Detection
Quand déclencher un Change Detection
4. Les MacroTasks, tells que setTimeout() ou setInterval(). En effet
vous pouvez mettre à jour les données dans la callback function d’une
macroTask comme setTimeout().
5. Les MicroTasks, comme [Link]() dont les callback peuvent
mettre à jour les données.
6. D’autres opérations asynchrones qui peuvent mettre à jour vos
données telles que [Link]() et [Link]().
[Link] 590
Change Detection
Quand déclencher un Change Detection
La liste précédente contient les scénarios les plus courants dans lesquels
l'application peut modifier les données.
Angular exécute le Change Detection à chaque fois qu'il détecte la possibilité
d’un changement de données.
Le résultat de la détection des changements est que le DOM est mis à jour avec
de nouvelles données.
Angular détecte les changements de différentes manières. Pour l'initialisation
des composants, Angular appelle explicitement la détection des
changements.
Pour les opérations asynchrones, Angular utilise une zone pour détecter les
changements aux endroits où les données auraient pu muter et exécute
automatiquement la détection des changements.
591
Change Detection
Zones et contexte d’exécution
Afin de détecter les éléments susceptible de déclencher
un Change Détection, Angular utilise [Link].
[Link] peut suivre et intercepter les tâches asynchrones.
Une zone a généralement 3 phases :
Elle commence dans un état stable
Elle devient instable lorsque une tâche est déclenchée dans la zone
Elle redevient stable lorsque les taches sont finalisées.
Angular suit plusieurs API de navigateur de bas niveau au démarrage
pour pouvoir détecter les changements dans l'application
[Link] 592
VBOaoVk1E9fTBZdq1CfnRa2TtL_Kb&index=12&ab_channel=StepanSuvorov
Change Detection
[Link] c’est quoi ?
//Comment savoir quand est ce que le setTimeout commence et quand ca se termine
//sans toucher le setTimeout
const oldSetTimeout = setTimeout;
setTimeout = (handler, timer) => {
[Link]('START');
oldSetTimeout(_ => {
handler();
[Link]('FINISH');
}, timer);
}
//----------------------------------------------------------------
setTimeout(_ => {
[Link]('some action');
}, 3000);
[Link]
593
VBOaoVk1E9fTBZdq1CfnRa2TtL_Kb&index=12&ab_channel=StepanSuvorov
Change Detection
Zones et contexte d’exécution
Ceci est donc délégué à [Link] qui suit les APIs comme
EventEmitter, DOM event listeners, XMLHttpRequest, l’API fs dans
[Link] 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 Change
Detection.
Angular utilise sa propre zone appelée NgZone.
C’est une seule zone et le Change Detection est uniquement déclenché
si une opération Asynchrone est déclenchée dans cette zone.
594
Change Detection
Zones et contexte d’exécution
// [Link]
// ngzone simplified
this._onMicrotaskEmptySubscription = this._zone.[Link]({
function onEnter() { next: () => {
_nesting++; this._zone.run(() => {
} [Link]();
function onLeave() { });
_nesting--; })
checkStable();
}
function checkStable() {
if (zone._nesting == 0 && ![Link]) {
[Link](null);
}
}
//[Link]
[Link] 595
VBOaoVk1E9fTBZdq1CfnRa2TtL_Kb&index=12&ab_channel=StepanSuvorov
Change Detection
Il existe deux stratégies de Change Detection :
La stratégie par défaut
La stratégie OnPush
596
Change Detection
La stratégie par défaut
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 dans
l'arborescence des composants de haut en bas pour voir si le modèle
correspondant a changé. Ceci est appelé dirty checking.
4. S'il y a une nouvelle valeur, il mettra à jour la partie correspondante
(DOM)
597
[Link] 598
Change Detection
La stratégie par défaut
Ici, dans tous les composants de
l’arbre de composant, le change
detector alloué à chaque
composant, compare la valeur
courante et la valeur précédente des
propriétés.
Si la valeur change, il va marquer
une propriété isChanged à true.
599
Change Detection
La stratégie par défaut
Cette première stratégie est assez
performante pour les application
petite et moyenne.
Ceci est du au fait qu’Angular est très
rapide pour le Change détection pour
chaque composant en utilisant la
technique du inline-caching.
Cependant, ceci peut ne plus suffire
pour les application assez volumineuse.
[Link] 600
Change Detection
La stratégie OnPush
Le but de cette stratégie est d’éviter les
test non nécessaires pour un composant
et sa descendance.
En appliquant cette stratégie, on définit
une nouvelle façon pour le déclenchement
du Change Detection pour ce composant
601
Change Detection
La stratégie OnPush
Afin d’activer la stratégie OnPush, il suffit d’ajouter la propriété
changeDetection du @Component object et de lui affecter la valeur
OnPush de l’objet ChangeDetectionstrategy.
@Component({
selector: 'app-list',
templateUrl: './[Link]',
styleUrls: ['./[Link]'],
changeDetection: [Link]
})
602
[Link] 603
Change Detection
La stratégie OnPush
En utilisant cette stratégie, Angular sait
que le composant n'a besoin d'être mis
à jour que si :
La référence d'entrée d’un Input du
composant est modifié
Le composant ou l'un de ses enfants
déclenche un événement.
La Change Detection est déclenchée
manuellement
Un observable lié au Template via le pipe
async émet une nouvelle valeur.
604
Change Detection
La stratégie OnPush
Les actions suivantes ne déclenchent pas le Change Detection dans ce
contexte :
setTimeout
setInterval
[Link]().then(), [Link]().then()
[Link]('...').subscribe() (En générale, n’importe quelle inscription à
un Observable RxJs)
605
Change Detection
La stratégie OnPush
Le changement de la référence Input
Dans la stratégie de détection de changement par défaut, Angular
exécutera le détecteur de changement chaque fois que les données
@Input() sont changées ou modifiées.
En utilisant la stratégie OnPush, le détecteur de changement n'est
déclenché que si une nouvelle référence est transmise en tant que
valeur @Input().
Les types primitifs tels que les nombres, les chaînes, les booléens, null et
indéfini sont passés par valeur.
606
Change Detection
La stratégie OnPush
Le changement de la référence Input
L'objet et les tableaux sont également passés par valeur, mais la
modification des propriétés d'objet ou des entrées de tableau ne crée
pas de nouvelle référence et ne déclenche donc pas la détection de
changement sur un composant OnPush.
Pour déclencher le détecteur de changement, vous devez passer une
nouvelle référence d'objet ou de tableau à la place.
[Link] facilite l’utilisation de l’Immutabilité.
Il fournit des structures de données immuables persistantes pour les objets
(Map) et les listes (List).
[Link] 607
[Link] 608
[Link] 609
PageComponent est OnPush
AppComponent passe une input à Page Component
[Link] 610
PageComponent et BredCrumbComponent sont OnPush
AppComponent passe une input à Page Component
[Link] 611
Tous les composants sont OnPush
[Link] 612
Tous les composants sont OnPush
GalleryComponent A un pipe Async et elle passe le résultat en
Input à SliderComponent
[Link] 613
Optimiser une application
Change Detection - La stratégie OnPush
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 detection dans
des parties qui n’ont aucun rapport avec cette action.
Solution : Utiliser OnPush et considérer du refactoring de votre
composant.
614
Change Detection
La stratégie OnPush
Le changement de la référence Input
Optimisation
Pensez à externaliser les parties ou vous avez des évènement et le
component recevant le @Input.
Ceci va faire en sorte que le composant avec le @Input ne sera réaffiché
que lorsqu’il aura une nouvelle référence.
615
Optimiser une application
Recalculation of referentially transparent
expressions
Cette problématique est soulevée lorsque vous avez une expression dans
votre Template qui doit être recalculé même lorsque ses paramètres ne
changent pas.
616
Change Detection
Recalculation of referentially transparent
expressions
Optimisation
Pensez à utiliser des pipes pour vos calcules.
Il faut avoir deux conditions :
Pas d’effets de bords (side effect), vous n’appelez pas d’api, pas de console,
…
Se sont des opérations pures, si l’entrée ne change pas, le résultat ne change
pas il reste le même
Pourquoi recalculer alors => utiliser les pipes, si l’entrée ne change pas, il ne
recalculera pas l’opération.
617
Change Detection
Recalculation of referentially transparent
expressions
Optimisation
Vous pouvez alors aller encore plus dans l’optimisation, puisque ce sont
des fonctions pures. Si une fonction a été déjà calculée, pourquoi le
refaire même si ce n’est pas la même entrée.
Pensez à utiliser le concept de cache avec le memo-decorator.
importer le décorateur memo de la bibliothèque memo-decorator et
appliquer le à votre fonction transform.
npm i memo-decorator
618
Change Detection
Le changement déclenché manuellement
Par défaut c’est [Link] à travers NgZone qui gère la partie déclenchement
du change detection.
[Link] 619
Change Detection
Le changement déclenché manuellement
620
Change Detection
Le changement déclenché manuellement
Dans certains cas d’utilisation, ce mode de fonctionnement peut
causer des problèmes et ralentir l’application.
Prenons le cas des évènements fréquents qu’on combinera avec des
Change Detection ayant beaucoup de calcul :
mousemove,
des scroll,
un setInterval avec des interval courts.
621
Change Detection
Le changement déclenché manuellement
Zone Pollution Pattern
Ce problème est identifié comme « Zone Pollution Pattern »
Il se produit lorsque Angular Zone encapsule un callback qui déclenche
des 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 voulez
isoler.
622
Change Detection
Le changement déclenché manuellement
Zone Pollution Pattern
[Link] 623
Change Detection
Le changement déclenché manuellement
Zone Pollution Pattern
Vous pouvez aussi désactiver complétement la prise en main de [Link] et
tout faire vous-même.
platformBrowserDynamic().bootstrapModule(AppModule , [
{ngZone: 'noop'}
])
[Link] 624
Change Detection
Le changement déclenché manuellement
Il existe trois méthodes pour déclencher manuellement les détections de
changement :
La méthode tick() de ApplicationRef qui déclenche la détection de
changement pour l'ensemble de l'application en respectant la stratégie
de détection de changement d'un composant
625
Change Detection
Le changement déclenché manuellement
Il existe trois méthodes pour déclencher manuellement les
détections de changement :
detectChanges() du ChangeDetectorRef qui exécute
la détection de changement sur cette vue et ses enfants
en gardant à l'esprit la stratégie de détection de changement.
Il peut être utilisé en combinaison avec detach() pour
implémenter des contrôles de détection de changement
locaux.
626
Change Detection
Le changement déclenché manuellement
Il existe trois méthodes pour déclencher manuellement
les détections de changement :
markForCheck() de ChangeDetectorRef qui ne
déclenche pas la détection de changement mais
marque tous les ancêtres OnPush comme devant être
vérifiés une fois, soit dans le cadre du cycle actuel ou du
cycle de détection de changement suivant. Il exécutera la
Change Detection sur les composants marqués même
s'ils utilisent la stratégie OnPush.
627
Change Detection
Détacher un composant du change detection
Vous pouvez détacher un composant complétement
du Change Detection.
Ceci peut être fait pour les composants qui n’ont pas
d’état et donc pas besoin que le ChangeDetector agisse.
Vous avez beaucoup de calcule lourd et vous voulez
gérer seul le déclanchement du Change Detection
localement.
Vous pouvez rattacher le composant quand c’est
nécessaire.
[Link] 628
Change Detection
Détacher un composant du change detection
629
Exercice
Clone ce projet :
[Link]
Lancez votre projet, c’est le RhComponent qui est exécuté.
Analysez ses problèmes et essayez de les résoudre avec les techniques
étudiées.
630
Notes
1. Zone Pollution Pattern
2. Out of Bound Change Detection
3. OnPush Stratégie
4. Recalculation of referentially transparent expressions
631
Angular
State Management
NgRx Store
AYMEN SELLAOUTI
632
NgRx
Introduction
Dans la plupart de nos
applications, nous gérons des
Model
composants qui (Services) Component
communiquent avec des
models afin de gérer leur
état. Model
Component
(Services)
Plus l’application grandi et
plus ca devient compliqué de
Model
gérer l’état de l’application (Services) Component
qui est éparpillé dans les
composants et les services.
633
NgRx
Introduction
Solution : Gestion centralisée de l’état de votre application
REDUX
634
NgRx
Introduction
[Link] 635
NgRx
Introduction
Redux
REDUX est un pattern né de FLUX, une architecture créée par
Facebook.
FLUX Se base sur un workflow de données unidirectionnel grâce
à un dispatcher, qui recueille des actions distribuées par le serveur ou
par l’utilisateur.
REDUX est une version moins complexe de Flux. Il se distingue
par :
Une source de vérité unique : le store ;
Des états immuables
Pas de dispatcher vue qu’on a un seul store
636
NgRx
Introduction
Redux Vs Flux
[Link] 637
NgRx
Introduction
Redux Vs Flux
[Link] 638
NgRx
Introduction
Redux
Store : C’est un objet contenant le state de votre application. Le
store n’est pas le state.
State : C’est la source de vérité unique. C’est un objet
immutable qui centralise l’état de votre application.
L’état est la représentation de votre application à un moment
donnée.
Les changement de l’état sont faites par des fonctions pures. Ces
fonctions n’ont aucun effet de bord et ne modifient aucune
donnée. Elle ne font que retourner une nouvelle valeur de l’état.
Les fonctions pures ne se basent que sur les paramètres d’entrées
et ne gèrent pas les éléments externes. Ceci implique que pour
une même entrée, on aura toujours le même résultat de sortie.
[Link] 639
NgRx
NgRx est une implémentation du pattern Redux.
Elle permet de gérer l’état de votre application.
NgRx est un framework pour créer des applications réactives dans
Angular. NgRx fournit des bibliothèques pour:
Gérer l'état global et local de votre application.
Isoler des effets de bord permettant d’avoir une architecture de
composants plus propre.
Gérer la collection de vos entités.
S’intégrer avec le routeur d’Angular.
640
NgRx
Quand L’utiliser
NgRx présente plusieurs avantages, cependant il ne faut pas
toujours l’utiliser.
Dans la documentation officielle de NgRx, on vous demande
de réfléchir avant d’entamer l’utilisation de NgRx dans votre
application.
L’idée est de ne pas ajouter une couche de complexité à
votre code si vous n’en avez pas besoin.
[Link] 641
NgRx
Quand L’utiliser
Le principe à suivre s’appelle SHARI. Il permet de voir quand utiliser NgRx :
Shared: Est ce que votre State est partagé par plusieurs composants et
services ?
Hydrated: Est ce que votre State doit être conservé et hydraté lors des
recharges de pages
Available: Est ce que le State doit être récupéré quand vous changer de
routes.
Retrieved: Est ce que votre State doit être récupéré à travers des effets de
bord, e.g. une requête HTTP.
Impacted: Est ce que le State est impacté par plusieurs components
[Link] 642
NgRx
Quand L’utiliser
Voici les cas d’usages les plus présents :
Quand plusieurs composants et services dépendent de la même portion du state
Chaque fois que vous refactorisez votre composant en plusieurs morceaux, vous
aurez une énorme probabilité d'introduire un système de gestion d'état.
Imaginez que vous ayez une grosse application avec beaucoup de pagination de
filtres, etc. Si vous quittez votre page pour modifier quelque chose à la page 5 et que
vous revenez ensuite, si vous êtes dans l'état initial de la page (page1 et pas de filtre)
c'est vraiment une mauvaise expérience. Dans ce cas, un système de gestion d'état
qui gère tous vos filtres, pagination, etc. vous aidera à conserver l'état.
[Link] 643
NgRx
Quand L’utiliser
D’autre part si on revient à la création de Flux et la première
apparition de Redux, les problèmes majeurs qu’avait à gérer la team
Facebook sont :
Plusieurs acteurs modifient les données, généralement c’est le
serveur et le client.
Le problème de ‘Extraneous props’ ou d’@Input étrangers.
C’est-à-dire qu’on trouve dans les composants des propriétés qui
n’ont rien à voir avec l’état du composant mais qui servent juste à
récupérer des paramètres à passer aux composant fils ou père.
644
NgRx
Quand L’utiliser
[Link]
ngconf-2020-part-1-introduction-h8l
[Link]
ngularConnect
[Link]
behaviorsubject-4818
[Link]
645
NgRx
Architecture
[Link] 646
NgRx
Architecture
[Link] 647
NgRx
Architecture
648
NgRx
Architecture
649
NgRx
Installation
Vous pouvez installer cette bibliothèque vous pouvez utiliser
les commandes suivantes :
npm install @ngrx/store providers: [
provideRouter(routes),
yarn add @ngrx/store provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
provideStore(),
ng add @ngrx/store provideEffects(),
L’utilisation de la commande
] ng add déclenchera les shcematics
installant la bibliothèques et ajoutant les fichiers et la configuration
nécessaire.
Le store sera configuré directement via la méthode provideStore
650
NgRx
Installation / debugger
Vous pouvez débugger votre application avec le redux store devtools.
Il faut d’abord installer l’extension Redux DevTools
[Link]
Pour l’installer lancer la commande suivante :
ng add @ngrx/store-devtools
providers: [
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
provideStore(),
]
[Link] 651
NgRx
Architecture
652
NgRx
Store
Afin de récupérer votre store, vous devez l’injecter.
Une fois fait, le store est représenté par un
observable qui n’offre aucune méthode permettant de
manipuler le state.
Etant un objet générique, vous pouvez lui spécifier
l’objet représentant votre état (le state).
export interface AppState {}
[Link]
constructor(
private store: Store<AppState>
)
653
NgRx
Store API
dispatch(action: Action) : cette méthode permet de déclencher une
action pour mettre à jour l'état de l'application. Elle prend en paramètre
un objet de type Action décrivant son type et les données associées.
pipe(select(selector: any)) : cette méthode permet de sélectionner
une partie de l'état de l'application à partir d'un sélecteur. Elle
retourne un Observable qui émet les valeurs de l'état sélectionné.
subscribe() : cette méthode permet de s'abonner aux changements
de l'état de l'application. Elle prend en paramètre des fonctions
callback pour gérer les changements de l'état, les erreurs et la fin de
l'abonnement.
654
NgRx
Architecture
655
NgRx
Actions
Les actions sont l'un des principaux composants de NgRx.
Les actions expriment des événements uniques qui se produisent
dans votre application.
Que ce soit des événements internes de votre utilisateur, ou des
événements externes via le réseau ou tout autre événement, les actions
sont la pour les décrire. interface Action {
type: string;
Les actions NgRx doivent implémenter l’interface Action. }
[Link] 657
NgRx
Actions
NgRx nous fournit une import { createAction, props } from "@ngrx/store";
méthode createAction qui vous import { User } from './model/[Link]';
permet de créer une action et const enum ActionsEnum {
une fonction props qui permet 'LOGIN' = '[Login Page] User Login'
}
de spécifier le type du payload
donnant plus de robustesse à export const loginAction = createAction(
votre code. [Link],
props<{user: User}>()
)
[Link] 658
NgRx
Actions
import { createAction, props } from "@ngrx/store";
import { User } from './model/[Link]';
const action = createAction('[Source] action simple');
action();
const action = createAction('[Source] action simple', props<User>());
action({
id: "1",
name: "aymen",
email: "aymen@[Link]",
});
const action = createAction('[Source] action simple',(user: User, prefix: string) => ({
...user,
name: `${prefix}${[Link]}`,
}) );
const user: User = { /* ... */ };
action(user, '@');
[Link] 659
NgRx
Actions
Afin de déclencher une action, [Link](loginAction({user}));
vous devez utiliser la méthode
dispatch de votre store.
Cette méthode prend en
paramètre une action.
En dispatchant cette action, on
informe le store qu’un événement
s’est déclenché et qu’il faut notifier
les reducers et les ‘effects’.
[Link] 660
NgRx
Actions
@Component({
selector: 'app-add-customer',
templateUrl: './[Link]',
styleUrls: ['./[Link]'],
})
export class AddCustomerComponent {
constructor(public store: Store<AppState>) {}
name = '';
email = '';
addCustomer() {
const newCustomer = new Customer(uuidv4(), [Link], [Link]);
[Link](
[Link]({ customer: newCustomer })
); addCustomerAction
} export interface AppState {
} customers: Customer[];
}
661
NgRx
Actions
Bonne pratiques
Il existe quelques règles pour écrire de bonnes actions dans votre
application.
Commencer par les actions : écrivez des actions avant de
développer des fonctionnalités pour avoir une vue globale de ce que
vous aller implémenter.
Catégoriser : catégorisez les actions en fonction de la source de
l'événement.
Soyer généreux en actions : les actions sont peu coûteuses à écrire,
donc plus vous écrivez d'actions, mieux vous exprimez les flux dans
votre application.
[Link] 662
NgRx
Actions
Bonne pratiques
C’est une bonne pratique de définir vos actions à coté des
fonctionnalités qui les utilisent
[Link] 663
NgRx
Actions
Les groupes d’actions (Actions Groups)
Vu que le groupement des actions de même sources est une
bonne pratique, NgRx à partir de la version 13 offre la possibilité de le
faire via les ActionGroup.
Pour ce faire, utiliser la fonction createActionGroup.
Elle prend en paramètre un objet avec deux propriétés à renseigner:
source : qui représente la source et qui sera ajouté automatiquement à
toutes les actions du groupe.
events : qui est un objet contenant les actions du groupe avec pour clé
le nom de l’action (il sera transformé en camelCase pour récupérer l’action)
et pour valeur la props si elle existe sinon la fonction emptyProps.
[Link] 664
NgRx
Actions
Les groupes d’actions (Actions Groups)
export const exampleActions = createActionGroup({
source: “Group Actions",
events: {
"Load Users": emptyProps(),
"Add User": props<{ user: User }>(),
},
});
[Link] 665
NgRx
Action
Nouveau State
Reducer
State
Reducers
Un Reducer est une fonction JS pure.
Elle est appelée suite à un évènement (une Action) et reçoit en
paramètre l’état actuel de l’application (le state) ainsi que l’action
dispatché par le store (qui contient le payload s’il existe).
En fonction du type de l’action et de son payload, le reducer
retourne le nouvel état (state) de l’application (du store).
Les reducers doivent garder l’immutabilité du state, ceci nous
permet d’avoir un historique des différentes versions de l’état de
l’application.
[Link] 666
NgRx
Reducers
Afin de créer un reducer et à partir de la version 8 de NgRx, vous
pouvez utiliser le helper createReducer.
createReducer prend en paramètre l’état initial du state, et la
fonction à déclencher pour une action donnée.
Dans les anciennes versions de RxJs, le travail se faisant avec un
switch qui selon le type de l’action exécutait le traitement nécessaire.
A partir de la version 8 vous pouvez utiliser le helper on qui prend en
premier paramètre l’action à gérer et en second paramètre la fonction à
exécuter. Cette fonction reçoit le state actuel et l’action et retourne
le nouveau state.
[Link] 667
NgRx
Reducers export function reducer(
state = initialState,
action: [Link]
): State {
const scoreboardReducer = createReducer( switch ([Link]) {
initialState, case [Link]: {
on([Link], return { ...state, home: [Link] + 1,};
state => ({ ...state, home: [Link] + 1 }) }
), case [Link]: {
on([Link], return {...state, away: [Link] + 1,};
state => ({ ...state, away: [Link] + 1 }) }
), case [Link]: {
on([Link], return [Link];
state => ({ home: 0, away: 0 })), }
on([Link], default: {
(state, { game }) => ({ home: [Link], away: [Link] })) return state;
); }
}
}
[Link] 668
NgRx
Reducers
export interface AuthState {
Pensez pour chaque reducer à user: User;
définir une interface décrivant la }
portion du state qu’il gère. export const initialAuthState: AuthState = {
user: null,
Définissez le state initiale de };
[Link] 669
NgRx
Reducers (Root State)
Enregistrement de l’état principale
L'état de votre application est défini comme un seul grand objet.
L'enregistrement des reducers pour gérer des parties de votre état vous
permet de définir uniquement les clés avec des valeurs associées dans l'objet.
Pour enregistrer le Store global dans votre application, utilisez la méthode
provideStore.
Elle prend un objet avec comme clé l’identifiant du reducer et comme
valeur le reducer. En enregistrant le state avec, vous rendez le state
disponible dès le lancement de l’application.
providers: [
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
provideStore({ app: reducer}),
]
[Link] 670
NgRx
Reducers (Feature State)
Enregistrement de l’état des fonctionnalités
Les états fonctionnels (Feature State) se comportent de la même
manière que l’état principal.
Chaque partie fonctionnelle aura son état qu’elle va gérer mais qui sera
aussi une partie de l’état principal.
Pour les états fonctionnels, utilisez la méthode provideState qui
prend aussi un objet avec l’identifiant de la portion et le reducer.
providers: [
provideStore({ app: reducer}),
provideState({ name: "todo", reducer: todoReducer }),
provideStoreDevtools({ maxAge: 25, logOnly: !isDevMode() }),
],
[Link] 671
NgRx
Reducers (Feature State)
Enregistrement de l’état des fonctionnalités
[Link]("auth", authReducer),
[Link] 672
NgRx
Reducers
Bonnes pratiques
Créer un reducer par fonctionnalité
Seul les reducers peuvent modifier l’état QUI DOIT RESTER
IMMUTABLE.
Evitez de développer dans le reducer directement. Créer des helpers
et appeler les.
[Link] 673
NgRx
Sélectionner des éléments du store
Afin de récupérer les données de votre state vous pouvez utiliser
plusieurs méthodes.
Le Store que vous utilisez pour dispatcher des actions vous permet
aussi d’accéder au state global de votre application.
Le store étant un Observable, vous pouvez vous y inscrire et
récupérer le state de l’application.
[Link]((state) => {
[Link]("state : ", state);
[Link]("autState : ", state['auth']);
});
[Link] 674
NgRx
Sélectionner des éléments du store
Afin de sélectionner la partie que vous voulez, utilisez
l’opérateur map de rxjs. [Link]$ = [Link](
map((state) => !! state["auth"].user)
);
Le problème ici est que cette opération va
se répéter à chaque fois alors que le résultat [Link]$ = [Link](
risque d’être le même. Une fonction pure qui map(
a le même input retournera toujours le même (state) => state["auth"]
),
Output. distinctUntilChanged()
);
La Solution est donc d’utiliser l’opérateur de
RxJs distinctUntilChanged.
[Link] 675
NgRx
Sélectionner des éléments du store
select operator
Afin de nous aider dans cette démarche, NgRx nous offre l’opérateur
select qui réalise un map selon une fonction pure et ne déclenche le flux
de l’observable qui si le résultat change en utilisant
distinctUntilChanged.
Elle prend en paramètre le state et elle applique une fonction de
mapping.
[Link]$ = [Link](
(state) => !!state["auth"].user
);
[Link] 676
NgRx
Sélecteurs
Le problème avec le select c’est qu’il effectue à chaque fois l’opération
de map. Dans certains cas, elle peut être très couteuse, sa répétition pose
donc un problème de performance.
Le Store fournit la fonction createSelector afin d’optimiser cette sélection.
En effet ce helper permet d’avoir de la :
Portabilité
Mémorisation
Composition
Testabilité
Sécurité de type ( Type Safety)
[Link] 677
NgRx
Sélecteurs
Lors de l'utilisation de createSelector, NgRx garde la trace des derniers
arguments avec lesquels votre fonction de sélection a été appelée.
Étant donné que les sélecteurs sont des fonctions pures, le dernier
résultat peut être renvoyé lorsque les arguments correspondent sans ré
invoquer votre fonction de sélection.
Cette pratique est connue sous le nom de mémorisation.
Un sélecteur est donc tout simplement une fonction de mapping avec de la
mémoire.
createSelector retourne un objet de type MemoizedSelector.
[Link] 678
NgRx
Sélecteurs
import { createSelector } from '@ngrx/store';
La fonction createSelector prend
en paramètre un ensemble de export interface FeatureState {
sélecteurs (8 maximum) permettant counter: number;
}
de sélectionner ce dont vous avez
besoin pour mapper votre state suivi export interface AppState {
feature: FeatureState;
de la fonction de map qui est }
toujours le dernier paramètre.
export const selectFeature = (state: AppState) => [Link];
La fonction de map récupère
comme paramètres le résultat de export const selectFeatureCount = createSelector(
selectFeature,
l’ensemble des sélecteurs passé en (state: FeatureState) => [Link]
paramètre avec elle. );
[Link] 679
NgRx
Sélecteurs
import { createSelector } from '@ngrx/store'; export const selectUser = (state: AppState) => [Link]
er;
export interface User { export const selectAllBooks = (state: AppState) => [Link]
id: number; s;
name: string;
} export const selectVisibleBooks = createSelector(
selectUser,
export interface Book { selectAllBooks,
id: number; (selectedUser: User, allBooks: Book[]) => {
userId: number; if (selectedUser && allBooks) {
name: string; return [Link]((book: Book) => [Link] === select
} [Link]);
} else {
export interface AppState { return allBooks;
selectedUser: User; }
allBooks: Book[]; }
} );
[Link] 680
NgRx
Sélecteurs de fonctionnalités
featureSelector
Afin de centraliser et de typer la partie de votre state qui correspond à une
fonctionnalité particulière (généralement le state du module), vous pouvez
utiliser les featureSelector.
Pour ce faire, utilisez la méthode createFeatureSelector caster la au
fetaureStateType que vous souhaitez et passer lui comme paramètre la clé
représentant la partie du state que vous voulez utiliser.
export const selectUser = (state: AppState) => [Link];
[Link] 681
NgRx
Sélecteurs de fonctionnalités
createFeature
Il existe trois éléments principaux de la gestion globale de l'état avec
@ngrx/store : les actions, les réducteurs et les sélecteurs.
Pour un état de fonctionnalité particulier, nous créons un réducteur pour
gérer les transitions d'état en fonction des actions et des sélecteurs distribués
pour obtenir des tranches de l'état de fonctionnalité.
Nous devons également définir un nom de fonctionnalité nécessaire pour
enregistrer le réducteur de fonctionnalités dans le magasin NgRx.
Par conséquent, nous pouvons considérer la fonctionnalité NgRx comme
un regroupement du nom de la fonctionnalité, du reducer de
fonctionnalité et des sélecteurs pour l'état de la fonctionnalité particulier.
[Link] 682
NgRx
Sélecteurs de fonctionnalités
createFeature
Afin d’éviter le code répétitif généré avec la création des différents
sélecteurs, NgRx propose à partir de la version 16 la fonction createFeature
La fonction createFeature réduit le code répétitif dans les fichiers de
sélection en générant des sélecteurs enfants pour chaque propriété
d'état de fonctionnalité.
Elle accepte un objet contenant un nom de fonctionnalité et un reducer de
fonctionnalité comme argument d'entrée :
export const appFeature = createFeature({
name: "app",
reducer: reducer,
});
[Link] 683
NgRx
Sélecteurs de fonctionnalités
createFeature
Afin d’ajouter vos propres selecteurs, vous pouvez utiliser l’option extraSelectors.
L'option extraSelectors accepte une fonction qui prend les sélecteurs générés
comme arguments d'entrée et renvoie un objet de tous les sélecteurs supplémentaires.
[Link] 685
NgRx
Les effects
Dans vos applications, vous avez tout le temps besoin d’interagir avec
les effets de bord (side effects) comme par exemple communiquer avec
une API.
Dans les applications basées sur les services, les composants sont
responsables de l'interaction avec les ressources externes directement via
les services.
Ceci pose un problème architectural. Nous voulons éviter cette
communication direct entre composant et service et isoler le
traitement des effets de bord.
C’est la ou interviennent les effects.
[Link] 686
NgRx
Les effects
Les effects isolent les effets de bord des composants, permettant
d’avoir des composants plus purs qui sélectionnent les états et
déclenchent des évènements.
Les effects sont des services qui écoutent un observable de chaque
action dispatchée depuis le Store.
Les effects filtrent ces actions en fonction du type d'action qui les
intéresse. Cela se fait à l'aide d'un opérateur.
Les effets exécutent des tâches synchrones ou asynchrones et
renvoient une nouvelle action.
[Link] 687
NgRx
Architecture
688
NgRx
Les effects
Installation
Vous pouvez installer la package en utilisant npm via la commande :
npm install @ngrx/effects
A partir de la version 6 il est recommandé d’utiliser la commande add
du cli qui va aussi configurer votre application avec le module installé.
ng add @ngrx/effects@latest
Cette commande fera les fonctionnalités suivantes :
Mettre à jour [Link] dependencies avec avec @ngrx/effects.
Exécuter npm install pour installer ces dépendances.
Mettre à jour votre src/app/[Link] et le tableau de la clé imports avec
[Link]([]).
[Link] 689
NgRx
Les effects
Configuration
Pour configurer vos effects passez en paramètre de votre méthode provideEffects un
tableau des effects que vous voulez ajouter.
[Link] 690
NgRx
Les effects
Les effects se basent sur l’écoute sur les actions qui sont
dispatchées.
Afin d’écouter sur les actions, NgRx vous offre le service Actions qui
étends de la class Observable (n’oublier pas NgRx est basée sur RxJs).
Injecter le et faite le traitement nécessaire selon le type de l’action.
[Link] 691
NgRx
Les effects
Maintenant que vous disposez d’un moyen d’écouter sur vos actions,
créer votre effects, NgRx vous offre le helper createEffect.
[Link]
Cette api prend en paramètre une fonction de mapping qui selon le
type de votre action, va mapper l’observable.
Généralement vous avez deux possibilité :
mapper l’observable à une action qui sera traitée par le reducer
ou faire un traitement et arrêter le process (exemple : sauvegarder le
user connecté dans le local storage).
[Link] 692
NgRx
Les effects
createEffect va retourner un nouvel Observable qui retourne une
Action. Si vous ne voulez pas le faire, ajouter l’option {dispatch: false}
Afin de tester le type de l’action sans passer par un switch ou un if,
NgRx vous offre le helper ofType
[Link] 693
NgRx
Les effects
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 './[Link]';
@Injectable()
export class MovieEffects {
@Injectable()
export class AuthEffects {
login$ = createEffect(
() =>[Link]$.pipe(
ofType([Link]),
tap(action => [Link]('user',[Link]([Link])))
),
{dispatch: false}
)
constructor(private actions$: Actions) {}
}
[Link] 695
NgRx
NgRx router-store
NgRx nous offre un outil de débogage très intéressant qui est le router
store.
Il va vous permettre de garder trace de votre router au niveau du
store et de visualiser à travers le timeline du devTools debugger les
changements au niveau de votre UI selon les actions dispatchées au
niveau de votre application.
npm install @ngrx/router-store –save
ng add @ngrx/router-store@latest
[Link] 696
NgRx
NgRx router-store
Configuration
Afin de configurer votre module, importer le module StoreRouterConnectingModule
au niveau de votre application.
Appeler la méthode forRoot et passez lui un objet de configuration de type
StorRoutingConfig.
stateKey: la clé de l’état du router dans le store (‘router’ par défaut)
serializer: Comment le snapshot du router est sérialisé, par défaut c’est le
DefaultRouterStateSerializer
navigationActionTiming: Quand l’action ROUTER_NAVIGATION est dispatchée
routerState: Pour décider quel sérialiser utiliser interface StoreRouterConfig {
stateKey?: string | Selector<any, RouterReducerState<T>>;
[Link]({ serializer?: new (...args: any[]) => RouterStateSerializer;
stateKey: "router", navigationActionTiming?: NavigationActionTiming;
routerState: [Link], routerState?: RouterState;
}), }
[Link] 697
NgRx
NgRx router-store
Configuration
Maintenant vous devez spécifier quel reducer va être utilisé pour gérer les
actions dispatchées concernant votre router.
[Link]({
root: rootReducer,
router: routerReducer
}, {}),
[Link] 698
NgRx
NgRx router-store
NgRx Store DevTools
[Link] 699
NgRx
NgRx router-store
selectors
Afin de sélectionner les éléments de votre router-store, vous pouvez
utiliser les selectors offerts par l’application.
import { getRouterSelectors, RouterReducerState } from "@ngrx/router-store";
export const {
selectCurrentRoute, // select the current route
selectFragment, // select the current route fragment
selectQueryParams, // select the current route query params
selectQueryParam, // factory function to select a query param
selectRouteParams, // select the current route params
selectRouteParam, // factory function to select a route param
selectRouteData, // select the current route data
selectRouteDataParam, // factory function to select a route data param
selectUrl, // select the current url
selectTitle, // select the title if available
} = getRouterSelectors();
[Link] 700
NgRx
NgRx runtime check
Les runtime checks sont là pour guider les développeurs à suivre les
concepts de base et les bonnes pratiques de NgRx et Redux.
Pendant le développement, lorsqu'une règle est violée, une erreur est
générée pour vous informer de la raison et de l'endroit où une erreur s'est
produite.
Vous avez 6 types de runtime checks fournies avec NgRx.
[Link] 701
NgRx
NgRx runtime check
Par défaut activé:
strictStateImmutability: vérifie que l'état n'est pas muté.
strictActionImmutability: vérifie que les actions ne sont pas mutées
Désactivé par défaut:
strictStateSerializability: vérifie si l'état est sérialisable
strictActionSerializability: vérifie si les actions sont sérialisables
strictActionWithinNgZone: vérifie si les actions sont distribuées dans
NgZone
strictActionTypeUniqueness: vérifie si les types d'actions enregistrés
sont uniques
[Link] 702
NgRx
NgRx runtime check
Afin de configurer les runtime checks, aller dans la configuration de votre store principale et passez
en deuxième paramètre un objet d’options.
Modifier la clé runtimeChecks avec un objet contenant les checks que vous voulez activer ou non.
[Link](reducers, {
runtimeChecks: {
strictActionImmutability: true,
strictActionSerializability: true,
strictStateImmutability: true,
strictStateSerializability: true
}
}),
[Link] 703
NgRx
Les méta reducers
Les méta reducers interceptent les actions avant qu’elles arrivent au
reducers.
Ceci va permettre un prétraitement des actions avant qu’elles n’arrivent
aux reducers. Le cas d’un loguer qui logue l’état d’un state avant l’action et
l’action en cours est illustratif du besoin des meta-reducers.
Dans le monde Redux, les meta-reducers sont les middlwares.
Un meta-reducer prend donc en paramètre une ActionReducer et
retourne une ActionReducer.
[Link] 704
NgRx
Les méta reducers
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
return function(state, action) {
[Link]('state', state);
[Link]('action', action);
return reducer(state, action);
};
}
export const metaReducers: MetaReducer<any> [] = [debug];
[Link] 705
NgRx
NgRx Entity
Généralement dans votre state vous allez sauvegarder un ensemble de
classe représentant vos entités dans la base de données.
Le premier réflexe est de les formaliser sous forme d’un tableau.
Ceci rend plus difficile la recherche des éléments pour des opérations
récurrentes telles que la sélection d’un élément par id.
Afin de remédier à ca, une bonne pratique est d’utiliser la structure
map.
Ceci nous aide dans la partie recherche mais pas dans le tri, la solution
c’est d’avoir un tableau nous permettant de gérer uniquement l’aspect tri.
Tout ceci est déjà prêt avec la bibliothèque NgRx Entity.
[Link] 706
NgRx
NgRx Entity
NgRx Entity fournit une API pour manipuler et interroger les
collections d'entités :
Réduit la répétition de code pour créer des reducrs qui gèrent une
collection de modèles.
Fournit des opérations CRUD performantes pour la gestion des
collections d'entités.
Fournit des Adaptateurs de type sécurisé extensibles pour
sélectionner les informations d'entité.
[Link] 707
NgRx
NgRx Entity
Afin d’installer la bibliothèque utiliser l’une des deux commandes
suivantes :
ng add @ngrx/entity@latest
[Link] 708
NgRx
NgRx Entity
NgRx Entity fournit interface générique pour la manipulation générique
ids: un tableau des clés primaries de la collection
entities: un dictionnaire d’entités indexé par la clé primaire
interface EntityState<V> {
ids: string[] | number[];
entities: { [id: string | id: number]: V };
}
[Link] 709
NgRx
NgRx Entity
Afin d’utiliser cette interface, faite en sorte que votre featureState
étende l’EntityState et passez lui le model que vous gérer.
export interface CvState extends EntityState<Cv>
{
error: string | null;
}
export const initialCvState: CvState = {
ids: [],
entities: {},
error: null,
};
[Link] 710
NgRx
NgRx Entity
Entity Adapter
NgRx fournit aussi une entité Adapter
Le rôle de l’Entity Adapter est de faciliter la gestion des Entity:
CRUD + Sélection
Elle fournit un ensemble de méthodes permettant de gérer votre
state.
Vous pouvez définir un adapter en utilisant la méthode
createEntityAdapter
[Link] 712
NgRx
NgRx Entity
Entity Adapter
L’adapters fournit plusieurs méthodes :
addOne: ajoute une entité dans la collection.
addMany: ajoute plusieurs entités dans la collection.
setAll: Remplace la collection courante par la collection fournie.
setOne: Ajoute ou remplace une entité dans la collection.
setMany: Ajoute ou remplace plusieurs entités dans la collection.
removeOne: Supprime une entité de la collection.
removeMany: Supprime plusieurs entités de la collection.
removeAll: Vide la collection.
[Link] 713
NgRx
NgRx Entity
Entity Adapter
L’adapters fournit plusieurs méthodes :
updateOne: Met à jour une entité de la collection, il supporte la
mise à jour partielle.
updateMany: Met à jour plusieurs entités de la collection, il
supporte la mise à jour partielle.
upsertOne: ajoute ou met à jour une entité de la collection.
upsertMany: ajoute ou met à jour plusieurs entités de la collection.
mapOne: Met à jour une entité dans la collection en définissant une
fonction de mapping.
map: Met à jour plusieurs entités dans la collection en définissant
une fonction de mapping..
[Link] 714
NgRx
NgRx Entity
Entity Adapter
Afin de sélectionner vos entities et de les transformer pour utilisation,
l’adapter offre une panoplie de selector prêt à l’emploi.
Pour récupérer la liste des sélecteurs, utiliser la méthode getSelectors
La méthode getSelectors renvoie des sélecteurs NgRx qui fournissent des
fonctions pour sélectionner des informations dans la collection d'entités.
selectIds : sélectionne un tableau d'identifiants
selectEntities : sélectionne le dictionnaire d'entités. Nous pouvons l'utiliser
pour récupérer l'entité par identifiant.
selectAll : sélectionne un tableau de toutes les entités sous format.
selectTotal : sélectionne le nombre total d'entités.
[Link] 715
NgRx
NgRx Entity
Entity Adapter
export const {
selectAll,
selectEntities,
selectIds,
selectTotal } =
[Link]();
716
NgRx
NgRx Data
Le problème majeur avec la gestion des entités et NgRx Entity est la
répétition de code.
Pour chaque opération, nous devons créer les actions, reducers, effects
et selectors.
Afin de faciliter cette tache, NgRx propose l’extension NgRx Data.
NgRx Data est une abstraction sur le store, les effects et les entity qui
réduit radicalement la quantité de code que vous écrivez.
Il offre des implémentations par défaut ( se base sur les
conventions) tout en fournissant de nombreux points d'extension pour
modifier ou augmenter ces comportements par défaut.
[Link] 717
NgRx
NgRx Data
Comme pour toute abstraction, alors que vous gagnez en simplicité,
vous perdez le caractère explicite de l'interaction directe avec NgRx.
NgRx Data n’est pas adapté aux données « non-entités ».
Les types scalaires, les énumérations, les données de session et autres
sont mieux gérées avec le NgRx standard.
Vous pouvez donc combiner selon le type des données à traiter les
différentes techniques NgRx.
[Link] 718
NgRx
NgRx Data
Installation
Afin d’installer NgRx Data utilisez la commande suivante :
ng add @ngrx/data@latest
Il est nécessaire d’avoir déjà installé NgRx Store, NgRx Effects et
NgRx Entity.
Vous devez aussi appelez les modules Store et Effects avant le
DataModule.
[Link] 719
NgRx
NgRx Data
Entity
NgRx Data gère des
collections d’Entités. Il
maintient un cache ou il
stocke ces informations.
Une collection d’entité
implémente l’interface
EntityCollection
[Link] 720
NgRx
NgRx Data
Entity Metadata
NgRx Data conserve un cache des collections d'entités dans le
Store.
Donc et afin de spécifier à NgRx Data les entités qu’elle doit gérer,
vous devez passer par l’objet EntityMetadataMap.
Chaque élément de cet objet est un objet de type EntityMetadata.
La clé représente le nom de l’identifiant de l’objet et le nom par
défaut de l’entité.
Afin d’associer cet objet à votre NgRxDataModule, ajoutez le dans
la config sous la clé entityMetadata
[Link] 721
NgRx
NgRx Data
Entity Metadata
const entityMetadata: EntityMetadataMap = {
Cv: {}
};
export const entityConfig: EntityDataModuleConfig = {
entityMetadata,
};
[Link](entityConfig),
[Link]
[Link] 722
NgRx
NgRx Data
Entity Metadata
Afin de configurer NgRx Data pour export function ageAndNameFilter(
cvs: Cv[], pattern: { filter: string; ageMin: number }) {
savoir comment gérer votre entité, return [Link](
vous avez plusieurs propriétés. (cv) => [Link]([Link]) &&
[Link] > [Link]
entityName : Si le nom de l’entité );
est différent de la clé, vous pouvez le }
[Link] 724
NgRx
NgRx Data
Entity Metadata
entityDispatcherOptions : permet de définir le comportement de
NgRxData vis-à-vis des opérations de CRUD (optimistic or
pesimitic)
Les valeurs par défaut sont les plus sûres : optimiste pour la
suppression et pessimiste pour l'ajout et la mise à jour.
Prend en paramètre un objet:
optimisticAdd: boolean;
optimisticDelete: boolean;
optimisticUpdate: boolean;
optimisticUpsert: boolean;
optimisticSaveEntities: boolean;
[Link] 725
NgRx
NgRx Data
EntityCollectionService
Le service EntityCollectionService est une façade sur le dispatcher
et le sélecteur qui gèrent les collections d’entités.
Le dispatcher offre les commandes qui dispatches les entity Actions.
Ces commandes mettent à jour directement la collection d'entités
ou déclenchent des requêtes HTTP vers un serveur.
Lorsque le serveur répond, NgRx distribue de nouvelles actions
avec les données de réponse et ces actions mettent à jour la
collection d'entités.
Les Selectors sont des propriétés renvoyant des observables de
sélecteur. Chaque observable surveille un changement spécifique
dans la collection d'entités en cache et émet la valeur modifiée.
[Link] 726
NgRx
NgRx Data
Entity Commands
[Link] 727
NgRx
NgRx Data
EntityCollectionService
Property Description
entityName: string [Link] of the entity collection for these selectors$
collection$: Observable<EntityCollection>
[Link] of the collection as a whole
| Store<EntityCollection>
count$: Observable<number> [Link] of count of entities in the cached
| Store<number> collection.
entities$: Observable<T[]> | Store<T[]> [Link] of all entities in the cached collection.
entityActions$: Observable<EntityAction> [Link] of actions related to this entity type.
entityMap$: Observable<Dictionary<T>>
[Link] of the map of entity keys to entities
| Store<Dictionary<T>>
[Link] of error actions related to this entity
errors$: Observable<EntityAction>
type.
[Link] 728
NgRx
NgRx Data
EntityCollectionService
Property Description
[Link] of the filter pattern applied by the
filter$: Observable<string> | Store<string>
entity collection's filter function
[Link] of entities in the cached collection
filteredEntities$: Observable<T[]> | Store<T[]>
that pass the filter function
keys$: Observable<string[] | number[]> | Store<string[] [Link] of the keys of the cached collection,
| number[]> in the collection's native sort order
[Link] true when the collection has been
loaded$: Observable<boolean> | Store<boolean>
loaded
[Link] true when a multi-entity query
loading$: Observable<boolean> | Store<boolean>
command is in progress.
changeState$: Observable<ChangeStateMap<T>> [Link] (including original values) of
| Store<ChangeStateMap<T>> entities with unsaved changes
[Link] 729
NgRx
NgRx Data
EntityCollectionService
Afin d’utiliser le EntityCollectionService, vous avez deux options :
1. Injecter le EntityCollectionServiceFactory et utiliser sa méthode
create qui vous créera un service de la classe EntityCollectionService.
2. Créer un service qui étend EntityCollectionServiceBase
[Link] 733
NgRx
NgRx Data
EntityCollectionService
const defaultDataServiceConfig: DefaultDataServiceConfig = {
root: "[Link]
entityHttpResourceUrls: {
Cv: {
entityResourceUrl: "[Link]
collectionResourceUrl: "[Link]
},
},
};
[Link] 734
export declare abstract class DefaultDataServiceConfig {
/**
* root path of the web api. may also include protocol, domain, and port
* for remote api, e.g.: `'[Link] (default: 'api')
*/
root?: string;
/**
* Known entity HttpResourceUrls.
* HttpUrlGenerator will create these URLs for entity types not listed here.
*/
entityHttpResourceUrls?: EntityHttpResourceUrls;
/** Is a DELETE 404 really OK? (default: true) */
delete404OK?: boolean;
/** Simulate GET latency in a demo (default: 0) */
getDelay?: number;
/** Simulate save method (PUT/POST/DELETE) latency in a demo (default: 0) */
saveDelay?: number;
/** request timeout in MS (default: 0)*/
timeout?: number;
/** to keep leading & trailing slashes or not; false by default */
trailingSlashEndpoints?: boolean;
} [Link] 735
Angular
Débogage et Tests
Unitaires
AYMEN SELLAOUTI
736
Références
737
Plan du Cours
1. Introduction
2. Les composants
3. Les directives
4. Service et injection de dépendances
5. Le routage
6. Forms
7. HTTP Module
8. Tests unitaires
738
Débogage angular 9 et Ivy
A partir de angular 9 et l’apparition du compilateur ivy, la methode [Link] ne fonctionne plus et
nouvel api a été proposé.
getComponent au quel vous passez l’objet composant ($0,$1,…) vous permet de récupérer l’instance
du composant.
739
Angular DevTools (Compatible avec
Angular 9 et +)
Angular Devtools est l’alternative offerte par Angular à Augury pour
la version 9 et plus d’Angular)
C’est une extension que vous pouvez trouver dans Chrome ou
Firefox.
[Link]
devtools/ienfalfjdbdpebioblfackkekamfmbnh
Une fois installée, vous aurez une onglet dans votre outils de
développeur qui s’appelle angular.
[Link] 740
Angular DevTools (Compatible avec
Angular 9 et +)
Lorsque vous y accéder vous
avez deux options :
Components : qui vous permet
d’explorer les composants et les
directives et de voir ou éditer leur
état.
Profiler - vous permet de profiler
votre application et voir ce qui se
passe dedans.
[Link] 741
Angular DevTools (Compatible avec
Angular 9 et +)
Lorsque vous y accéder vous
avez deux options :
Components : qui vous permet
d’explorer les composants et les
directives et de voir ou éditer leur
état.
Profiler - vous permet de profiler
votre application et voir ce qui se
passe dedans.
[Link] 742
Angular
Tests Unitaires
et E2E
AYMEN SELLAOUTI
743
Références
744
Tests unitaires : Introduction
La plus petite unité de test possible
[Link] 745
Tests unitaires : Pourquoi
Rend votre code plus robuste, le bug sera identifié plus tôt
Vous permet de formaliser et de documenter vos besoins
Un bon test décrit clairement comment le code d'implémentation
doit se comporter
Un bon test doit couvrir les scénarios les plus importants
Les tests rendent le changement sûr en empêchant les régressions
746
Tests unitaires : Partout ?
Alors devriez-vous écrire des tests automatisés pour tous les cas
possibles afin de garantir l'exactitude ?
Non, disent les principes de l'ISTQB : "Les tests exhaustifs sont
impossibles".
Il n'est ni techniquement faisable ni utile d'écrire des tests pour
toutes les entrées et conditions possibles.
Au lieu de cela, vous devez évaluer les risques d'un certain cas et
rédiger d'abord des tests pour les cas à haut risque.
Même s'il était viable de couvrir tous les cas, cela vous donnerait un
faux sentiment de sécurité.
747
Angular et les tests unitaires
Angular avec sa structuration et ses différentes couches se marie
parfaitement avec les tests unitaires.
Chaque couche ayant un rôle unique et une tache bien spécifique,
les tests unitaires seront donc propres à chaque partie,
composant, pipe, service, directive, …
L’utilisation de l’injection de dépendance implique le couplage
faible et donc des tests isolés.
Les providers qui permettent de fournir des classes fictives
facilitent aussi cette notion d’isolation.
748
Jasmin et Karma
En installant Angular via le Cli, vous trouverez prêt à l’emploi deux
outils de tests : Jasmin et Karma.
"@types/jasmine": "~4.3.0",
"jasmine-core": "~4.5.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
749
Jasmin et Karma
751
Concepts de base de jasmin
describe (suite)
Pour ce qui est de Jasmine, un test se compose d'une ou plusieurs
suites. Une suite est déclarée avec un bloc describe :
[Link] 752
Concepts de base de jasmin
describe (suite)
describe est une fonction qui prend deux paramètres.
Une chaîne. généralement le nom de la fonction ou de la classe testée. Par
exemple, describe(‘TestExampleComponent‘)
Une fonction contenant la définition de la suite
On peut avoir des describe imbriqué afin de diviser des gros describe
en des sections logiques describe('Suite description', () => {
describe('One aspect', () => {
Les blocs de description imbriqués ajoutent /* … */
une description lisible à un groupe de });
describe('Another aspect', () => {
spécifications. Ils peuvent également héberger /* … */
leur propre logique de configuration. });
});
753
Concepts de base de jasmin
Specification (it)
Chaque describe se compose d'une ou plusieurs spécifications.
Une spécification est déclarée avec un bloc it :
describe('description de la suite', () => {
it('description de la spécification', () => {
/* … */
});
});
754
Concepts de base de jasmin
Specification (it)
Le pronom it fait référence au code testé.
La phrase doit donc être lisible et affirme le comportement du code
testé.
Le code de votre spécification prouve alors cette affirmation.
Ce style d'écriture des spécifications provient du concept de Behavior-
Driven Development (BDD).
L'un des objectifs du BDD est de décrire le comportement du logiciel
dans un langage naturel.
Chaque partie prenante doit être capable de lire les phrases et de
comprendre comment le code est censé se comporter.
755
Concepts de base de jasmin
Specification (it)
Pour écrire le titre de votre test, le it…, demandez vous ce que doit
faire le code que vous testez.
Pour une LampeComponent par exemple, il doit allumer et éteindre la
lampe, on aura donc :
it('switch on the lamp', () => {
/* … */
});
it('switch off the lamp', () => {
/* … */
})
756
Concepts de base de jasmin
Specification (it)
À l’intérieur du bloc it se trouve le code de test réel.
Indépendamment du framework de test, le code de test se compose généralement
de trois phases :
Arrange
Act
Assert.
Arrange est la phase de préparation et de mise en place. Par exemple, la classe
testée est instanciée. Les dépendances sont mises en place. Des espions (spy) et des
faux sont créés.
Act est la phase où l'interaction avec le code testé. Par exemple, une méthode est
appelée ou un élément HTML du DOM est cliqué.
Assert est la phase où le comportement du code est contrôlé et vérifié. Par
exemple, la sortie réelle est comparée à la sortie attendue.
757
Concepts de base de jasmin
Specification (it)
Imaginons que nous voulons tester un service qui permet
d’additionner et de soustraire des entiers.
Nous commençons par tester l’addition.
Arrange
Nous devons créer une instance du service et de ses dépendances
s’ils existent
Act
Appeler la fonction add avec deux paramètres
Assert.
Vérifier que la fonction retourne le bon résultat
758
Concepts de base de jasmin
Attente (Expectation)
Dans la phase d'affirmation (Assert), le test compare la sortie ou la
valeur de retour réelle à la sortie ou à la valeur de retour attendue.
S'ils sont identiques, le test réussit. S'ils diffèrent, le test échoue.
Afin de gérer ca, jasmine nous offre la fonction expect.
Cette fonction est associée à un ensemble de matchers permettant
de faciliter la validation de vos attentes ou expectations.
const expectedValue = 5;
const actualValue = [Link](2, 3);
expect(actualValue).toBe(expectedValue);
759
Jasmin matchers
Les matchers de Jasmine sont des fonctions qui permettent de tester si une
valeur donnée correspond à une condition spécifique. Ils permettent donc de
vérifier que les fonctionnalités de l'application se comportent comme prévu.
toBe() : vérifie si deux valeurs sont strictement égales (utilisant l'opérateur "===")
toEqual() : vérifie si deux objets ont les mêmes propriétés et les mêmes valeurs
toMatch() : vérifie si une chaîne de caractères correspond à une expression
régulière
toBeDefined() : vérifie si une variable est définie
toBeUndefined() : vérifie si une variable n'est pas définie
toBeNull() : vérifie si une variable est null
[Link] 760
Jasmin matchers
toBeTruthy() : vérifie si une expression est vraie
toBeFalsy() : vérifie si une expression est fausse
toContain() : vérifie si un tableau ou une chaîne de caractères contient
un élément spécifié
toBeLessThan() : vérifie si une valeur est inférieure à une autre
toBeGreaterThan() : vérifie si une valeur est supérieure à une autre
…
[Link] 761
Concepts de base de jasmin
describe (string, function) : fonction qui prend en paramètre un
titre et une ensemble de test individuel.
it (string, function) : fonction représentant un test individuel qui
prend en paramètre un titre et une fonction définissant un test
individuel.
expect : fonction qui retourne un booléen et évalue une expectation
un besoin à valider par le test unitaire.
Exemple expect(etatActuel).toBe(etatExpecté)
les matchers : sont des helpers prédéfinis permettant différentes
validations.
762
Concepts de base de jasmin
xit permet d’exclure un test individuel
763
Exercice
Créer un service MathService
Ajouter y deux fonction add et substract
Créer les tests nécessaires pour ce service
764
Concepts de base de jasmin
Lorsque vous écrivez plusieurs spécifications dans une suite, vous
réalisez rapidement que la phase d'arrangement (Arrange) est
similaire, voire identique, dans toutes ces spécifications.
Par exemple, lors du test du MathService, la phase Arrange consiste
toujours à créer une instance de MathService.
Afin de centraliser ces traitements réplétifs, Jasmine propose quatre
fonctions : beforeEach, afterEach, beforeAll et afterAll. Ils sont
appelés à l'intérieur d'un bloc describe.
Ils attendent un paramètre, une fonction qui est appelée aux étapes
données.
765
Concepts de base de jasmin
Jasmin offre des handlers permettant de répéter certaines fonctionnalités.
beforeEach : prend en paramètre une callback et la répète avant
chaque spec it.
afterEach : prend en paramètre une callback et la répète après
chaque spec it.
beforeAll : prend en paramètre une callback et la répète avant
chaque suite describe.
afterEach : prend en paramètre une callback et la répète après
chaque suite describe.
766
Exercice
Modifier vos tests en utilisant ces nouvelles fonctionnalités
Ajouter un service loggerService permettant de loger ce que vous
voulez.
Injecter le dans le MathService
Faite en sorte que vous logez chaque opération opérée
(Exemple l’appel de add(2,3) devra loger 3 + 2 = 5)
Mettez à jour vos tests
767
Simuler les dépendances
Dans plusieurs cas, et pour garder l’isolation de vos tests, vous devez
simuler les dépendances de l’élément à tester.
Ce remplacement s’appelle ‘Mocking’ et le remplaçant un ‘mock’
Un mock doit avoir la même forme que l'original. Si la dépendance
est une fonction, le mock doit avoir la même signature, c'est-à-dire les
mêmes paramètres et la même valeur de retour.
Le mock n'a pas besoin d'être complet, mais suffisant pour servir
de remplacement. Il doit être équivalent à l'original en ce qui
concerne le code testé, pas entièrement égal à l'original.
768
Jasmin spies
Les jasmine spies sont des outils utilisés pour tester les fonctions dans le
cadre des tests unitaires dans le framework Jasmine pour JavaScript.
Ils permettent de surveiller l'interaction d'une fonction avec ses
dépendances, en remplaçant ces dépendances par des objets de surveillance
(spies) qui enregistrent les appels et les arguments passés à ces dépendances.
Afin de créer un spy vous pouvez utiliser la méthode createSpyObj de
Jasmine qui permet de créer un objet "espion" (spy) pour les tests unitaires
dans Angular.
Cet objet est utilisé pour simuler les dépendances d'un composant ou
d'un service, et permet de vérifier que ces dépendances sont utilisées
correctement. Il peut également être utilisé pour définir des
comportements spécifiques pour ces dépendances lors des tests.
769
Jasmin spies
createSpyObj prend en premier paramètre une chaine identifiant le
spy et en second paramètre un tableau avec l’ensemble des
méthodes à simuler dans votre objet ou un objet avec comme clé
l’élément et comme valeur sa valeur.
oneSpy = [Link]('ExampleService', ['fn1', 'fn2']);
const service = new Service(oneSpy);
oneSpy = [Link]<ExempleService>(
'ExampleService', {
'f1': undefined,
'f2': () => {},
});
Ici, oneSpy simule un objet ayant une fonction fn1 et une fonction fn2.
770
Jasmin spies
spyOn(object, methodName) : Cette fonction permet de créer un
espion pour une méthode donnée d'un objet. Par exemple, pour
espionner la méthode getData d'un service myService, vous pouvez
utiliser :
let spy: [Link] = spyOn(myService, 'getData');
[Link]() : Cette fonction permet de configurer l'espion
pour qu'il appelle la méthode originale sous-jacente lorsqu'il est appelé.
Par exemple, pour continuer à appeler la méthode getData lorsque
l'espion est appelé, vous pouvez utiliser :
[Link]();
771
772
Jasmin spies
[Link](value) : Cette fonction permet de configurer
l'espion pour qu'il renvoie une valeur spécifique lorsqu'il est appelé. Par
exemple, pour faire en sorte que l'espion de la méthode getData renvoie
une valeur de test prédéfinie, vous pouvez utiliser :
[Link](testData);
[Link](fn) : Cette fonction permet de configurer l'espion pour
qu'il appelle une fonction de rappel spécifiée lorsqu'il est appelé.
Par exemple, pour faire en sorte que l'espion de la méthode getData appelle
une fonction de rappel qui génère des données de test, vous pouvez utiliser :
[Link](() => generateTestData());
773
Jasmin spies
[Link]() : Cette propriété permet de récupérer le nombre de
fois que l'espion a été appelé. Par exemple, pour vérifier que l'espion de
la méthode getData a été appelé trois fois, vous pouvez utiliser :
expect([Link]()).toEqual(3);
778
Exercice
Proposer un test à ce service.
Vous devez vous assurez que le service existe, et qu’il fasse le
nécessaire import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class AuthService {
authenticated = false;
constructor() {}
isAuthentified(): boolean {
[Link] = !;
return [Link];
}
}
779
Tester un pipe
Un pipe contient uniquement la méthode transform. Tester un pipe
revient donc à tester la méthode transform.
780
Angular TestBed
TestBed est l’outil le plus important des utilitaires de test Angular.
Il vous facilite l’étape de préparation de l’environnement de Test
(Arrange).
Il permet de construire dynamiquement un module de test afin de
simuler un Module Angular.
En utilisant la méthode configureTestingModule de TestBed vous avez
accès à une grande partie des propriétés de @NgModule.
La méthode configureTestingModule prend en paramètre l’objet
permettant de définir un module.
Ceci vous permet aussi d’accéder au système d’injection de dépendance
d’Angular.
781
Angular TestBed
beforeEach(async () => {
await [Link]({
imports: [
LesModulesNecessaires
],
declarations: [
AppComponent
],
});
});
782
Angular TestBed
Une fois le module configuré, nous devons utiliser la méthode
compileComponents de TestBed, qui va compiler toutes les
déclarations (composants, pipes et directives).
Cette fonction retourne une promesse.
Etant donné que la méthode configureTestingModule retourne un
TestBed, nous pouvons chainer l’appel avec la déclaration du
module.
783
Angular TestBed
beforeEach(async () => {
await [Link]({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
],
}).compileComponents();
});
784
Récupérer un service injecté avec TestBed
Afin de récupérer un service injecté, TestBed offre la méthode inject
qui prend en paramètre la classe du service injecté et retourne une
instance.
Exemple [Link](MonService);
785
Tester un service HTTP
Dans un test unitaire, on ne veut pas vraiment appeler le serveur
HTTP : ce n’est pas ce que nous testons. Nous voulons faire un
"faux" appel HTTP pour retourner de fausses données.
Pour cela, nous pouvons remplacer la dépendance au
service HttpClient du HttpClientModule par une implémentation
mocké en important le HttpClientTestingModule du
'@angular/common/http/testing'
Le service HttpTestingController du HttpClientTestingModule
sera alors utilisée pour tester les services qui utilisent l'objet HttpClient.
Il permet de simuler des requêtes HTTP et de vérifier les
réponses, en interceptant la requête http envoyé.
786
Tester un service HTTP
Les méthodes du HttpTestingController
expectOne(url: string | RegExp) : Cette méthode s'attend à ce qu'il y
ait une seule requête correspondant à l'URL ou à l'expression régulière
spécifiée. Elle renvoie un objet HttpRequest qui permet de vérifier les
propriétés de la requête.
verify() : Cette méthode vérifie qu'il n'y a plus de requêtes en attente.
Si il y en a, une erreur est levée. Généralement utilisé dans le
BeforeEach.
787
Tester un service HTTP
Les méthodes du TestRequest
La méthode expectOne vous retourne un
objet de type TestRequest.
Elle vous permet à travers sa propriété request
de récupérer la requête en cours.
Elle offre aussi la méthode flush(data: any,
options?: {headers?: HttpHeaders, status?: number,
statusText?: string}) : Cette méthode envoie une
réponse à toutes les requêtes en attente. Les
options peuvent être utilisées pour spécifier les en-
têtes, le statut et le statut texte de la réponse.
Pour retourner une erreur elle offre la méthode
error, qui prend en paramètre un objet
ProgressEvent et un objet d’option permettant
d’ajouter le status et le statusText par exemple.
788
Tester un service HTTP
Généralement, les tests vont suivre le processus suivant :
1. Appelez la méthode testée qui envoie des requêtes HTTP.
2. Récupérer les requêtes en attente
3. Répondre à ces requêtes avec de fausses données.
4. Vérifier le résultat de l'appel de la méthode
5. Vérifier que toutes les requêtes ont été gérées
789
it('return all cvs', () => {
// ACT
[Link]().subscribe((cvs) => {
fdescribe('CvService', () => { [Link]('cvs', cvs);
let service: CvService;
let http: HttpTestingController; // Assert
beforeEach(() => { /* J'attend que la liste des cvs existe */
// Arrange expect(cvs).toBeTruthy();
[Link]({ });
imports: [HttpClientTestingModule],
}); //Act
service = [Link](CvService); const req = [Link]([Link]);
http = [Link](HttpTestingController);
}); expect([Link]).toBe('GET');
790
Tester un Http Service
Finaliser la méthode getCvs
Tester GetById
Ajouter un test de mise à jour qui a échoué du à un id d’un cv qui
n’existe pas
791
Tester un composant
Les composants sont le corps d’Angular
Lors de la conception d'un test de composant, les questions directrices
sont :
que fait le composant
que doit-on tester ?
comment tester ce comportement ?
qu’est ce qui est nécessaire pour tester le composant ?
792
Angular TestBed
TestBed vous facilite l’étape de préparation de l’environnement de
Test (Arrange).
Il permet de construire dynamiquement un module de test et nous
permet donc de répondre à la question qu’est ce qui est nécessaire pour
tester le composant ?
Avec configureTestingModule récupérer les dépendances de votre
composant, à savoir les diretives, pipes et autres composant qu’il
utilise via la propriété declarations.
Les modules nécessaires à son fonctionnement via imports.
Les services dont il a besoin via providers.
793
Component fixtures
Le composant étant formé d’une partie métier (L’instance du
composant) et d’une partie vue (son template), nous devons avoir le moyen
de les récupérer.
La méthode createComponent de TestBed prend en paramètre un
composant et en crée un objet de type ComponentFixture contenant ses
deux partie.
Exemple :
fixture = [Link](AppComponent);
Cette classe permet d’encapsuler un composant et son template et offre
une multitude d’attributs et de méthodes très utiles pour tester un
composant.
[Link] 794
Component fixtures
changeDetection
createComponent affiche le composant dans un élément conteneur div dans le
DOM HTML.
Cependant, il manque quelque chose. Le composant n'est pas entièrement
affiché.
Tout le HTML statique est présent, mais le HTML dynamique est absent.
Toute la partie binding nécessitant un changeDetection pour s’afficher ne sera
pas la.
En effet, dans l’environnement de test, il n'y a pas de détection automatique
des changements.
En testant le code, nous devons déclencher la détection de changement
manuellement.
Pour ce faire, utiliser la méthode detectChanges du fixture
[Link]();
795
Component fixtures
debugElement
debugElement est un objet fourni par @angular/core/testing qui
permet d'accéder aux éléments du DOM lors des tests unitaires dans
Angular. Il est utilisé en conjonction avec ComponentFixture, qui permet
de créer une instance d'un composant pour les tests.
Voici quelques méthodes courantes que vous pouvez utiliser pour
sélectionner des éléments du DOM à l'aide de debugElement :
query(predicate: Predicate<DebugElement>) : DebugElement : permet
de sélectionner le premier élément qui correspond au prédicat spécifié.
queryAll(predicate: Predicate<DebugElement>) : DebugElement[] :
permet de sélectionner tous les éléments qui correspondent au prédicat
spécifié.
796
debugElement
nativeElement : permet d'accéder à l'élément natif du DOM
correspondant à l'élément sélectionné. Vous pouvez utiliser cette
propriété pour accéder aux propriétés standard de l'élément, telles que
innerHTML, className, etc.
properties : permet d'accéder aux propriétés de liaison de l'élément
sélectionné. Vous pouvez utiliser cette propriété pour accéder aux
propriétés liées à l'élément, telles que [ngClass], [value], etc.
attributes : permet d'accéder aux attributs de l'élément sélectionné.
Vous pouvez utiliser cette propriété pour accéder aux attributs de
l'élément, tels que id, name, etc.
797
debugElement
classes : permet d'accéder aux classes CSS de l'élément sélectionné.
Vous pouvez utiliser cette propriété pour vérifier si l'élément a une
classe spécifique ou pour obtenir la liste de toutes les classes de
l'élément.
styles : permet d'accéder aux styles de l'élément sélectionné. Vous
pouvez utiliser cette propriété pour vérifier si l'élément a un style
spécifique ou pour obtenir la liste de tous les styles de l'élément.
childNodes : permet d'accéder aux enfants de l'élément sélectionné.
Vous pouvez utiliser cette propriété pour accéder aux enfants de
l'élément, soit pour les sélectionner soit pour accéder à leur propriétés.
798
debugElement
Les propriétés parent,
children et childNodes
facilitent la navigation dans
l'arborescence DOM. Ils
renvoient également un
DebugElements.
799
debugElement
nativeElement
Il est souvent nécessaire de récupérer le DebugElement pour accéder à l'élément
DOM natif.
Chaque DebugElement a une propriété nativeElement.
nativeElement est typé comme any car Angular ne connaît pas le type exact de
l'élément DOM.
La plupart du temps, il s'agit d'une sous-classe de HTMLElement. Lorsque
vous utilisez nativeElement, vous devez en savoir plus sur l'interface DOM de
l'élément spécifique.
Par exemple, un élément de bouton est représenté sous la forme
HTMLButtonElement dans le DOM.
800
debugElement
Requêter le DOM
Chaque DebugElement comporte les méthodes query et queryAll
pour rechercher des éléments descendants. Ils prennent en paramètre un
prédicat.
Il existe plusieurs prédicats prédéfinis que vous pouvez utiliser
pour sélectionner des éléments du DOM :
[Link](selector: string) : permet de sélectionner des éléments en
fonction d'un sélecteur CSS.
[Link](type: Type<any>) : permet de sélectionner des éléments
qui ont une directive spécifique.
[Link] : permet de sélectionner tous les éléments.
801
import { ComponentFixture, TestBed } from it('should select element by css', () => {
'@angular/core/testing'; let el = [Link]([Link]('.my-class'));
import { By } from '@angular/platform-browser'; expect(el).toBeTruthy();
expect([Link]).toEqual('my text');
describe('My component test', () => { });
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>; it('should select all elements by css', () => {
let els = [Link]([Link]('.my-class'));
beforeEach(() => { expect([Link]).toEqual(2);
[Link]({ expect(els[0].[Link]).toEqual('my text 1');
declarations: [ MyComponent ] expect(els[1].[Link]).toEqual('my text 2');
}); });
});
fixture = [Link](MyComponent);
component = [Link];
[Link]();
});
802
import { ComponentFixture, TestBed } from it('should select element by directive', () => {
'@angular/core/testing'; let el =
import { By } from '@angular/platform-browser'; [Link]([Link](MyDirective));
import { MyDirective } from './[Link]'; expect(el).toBeTruthy();
});
describe('My component test', () => {
let component: MyComponent; it('should select all elements by directive', () => {
let fixture: ComponentFixture<MyComponent>; let els =
[Link]([Link](MyDirective));
beforeEach(() => { expect([Link]).toEqual(2);
[Link]({ });
declarations: [ MyComponent, MyDirective ] });
});
fixture = [Link](MyComponent);
component = [Link];
[Link]();
});
803
componentInstance
A partir des fixtures on peut utiliser l’attribut componentInstance
afin de récupérer une instance de notre composant.
Exemple :
fixture = [Link](AppComponent);
const myCompo = [Link];
804
Premier Test d’un composant
En installant votre projet Angular vous avez un test avec votre premier
composant import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './[Link]';
describe('AppComponent', () => {
beforeEach(async(() => {
[Link]({
declarations: [
AppComponent
],
}).compileComponents();
}));
806
Tester un composant
Soit le composant counterComponent
Pour ce composant on peut tester les éléments suivants :
Lorsque l'utilisateur click sur le bouton ‘+’, le compteur s'incrémente
de 1.
Lorsque l'utilisateur click sur le bouton ‘-’, le compteur se
décrémente de 1
[Link] 807
Tester un composant
Commencez par tester la première Assertion
Lorsque l'utilisateur click sur le bouton ‘+’, le compteur s'incrémente
de 1.
Ici le Arrange est déjà prêt
Le Act c’est le click sur le bouton increment
Le Assert est la vérification que la valeur a augmenté de 1.
808
Tester un composant
Les sélecteurs de type et de classe introduisent un couplage fort entre le test et le
template.
Les éléments HTML sont choisis pour des raisons sémantiques. Les classes
sont choisies principalement pour le style.
Les deux changent fréquemment lorsque le modèle du composant est refactorisé.
Le test doit-il échouer si le type ou la classe de l'élément change ?
Le test devrait mieux trouver l'élément par une caractéristique qui ne change
jamais et qui n'a pas de signification supplémentaire
Un propriété identifiant l’élément à sélectionner est une bonne alternative
810
Tester un composant
Factoriser notre code de tests
Vous pouvez créer vos helpers avec votre propre logique
Vous pouvez vous inspirer des helpers de [Link]
[Link]
[Link] 811
Exercice
Créer un composant ColorUtComponent.
812
Exercice
Créer un composant Authentification
Dans ce composant on va avoir deux
div. @Component({
selector: 'app-authentification',
La première div apparaitra si le user templateUrl: './[Link]',
est authentifié. styleUrls: ['./[Link]']
})
La seconde sinon. export class AuthentificationComponent {
isLogged = true;
Utiliser le service AuthService crée constructor(private authentificationService:
pour la phase de test. AuthentificationService) { }
ngOnInit() {
<div *ngIf="isLogged"> }
<h1>User Logged</h1> login() {
</div> [Link] =
[Link]();
<div *ngIf="!isLogged">
}
<h1>User not Logged</h1> }
</div> 813
Exercice
Que pensez vous de cette solution. Si elle ne vous plait pas proposez
une alternatives et implémentez la.
814
Test Asynchrone
Il existe plusieurs façons de gérer les tests asynchrones avec Jasmine
dans Angular.
L'une des méthodes courantes consiste à utiliser des promesses pour
gérer les appels asynchrones, et à utiliser la fonction done() de Jasmine
pour signaler la fin des tests.
Il y a aussi la fonction fakeAsync fournie par
@angular/core/testing qui permet de simuler l'exécution
asynchrone dans les tests.
815
Test Asynchrone
done
La fonction done() de Jasmine permet de signaler la fin des tests.
En la passant comme paramètre à votre fonction de test, vous
spécifier à jasmine que c’est un traitement asynchrone et qu’il doit
attendre votre signal pour lancer la vérification.
describe(‘Test Asynchrone', () => {
it('should do something async', (done: DoneFn) => {
asyncFunction().then(() => {
// Mettez votre assertion ici
done();
});
});
}); 816
Test Asynchrone
done
it('exemple timeout with done', (done: DoneFn) => {
let x = 1;
setTimeout(() => {
x++;
expect(x).toBe(2);
done();
}, 1000);
});
817
Exercice
Créer un composant helloWorld.
Ce composant va juste attendre une second puis afficher helloWorld
dans une div
Tester ce composant
818
Test Asynchrone
fakeAsync
fakeAsync est une fonction fournie par @angular/core/testing qui
permet de simuler l'exécution asynchrone dans les tests.
Elle est utilisé avec la fonction tick() pour faire avancer le temps
virtuel et exécuter les tâches asynchrones planifiées.
Cela permet de faciliter l'écriture des tests, car il n'est pas nécessaire
d'utiliser la fonction done() de Jasmine.
Il est important de noter que lorsque vous utilisez fakeAsync, toutes
les fonctions asynchrones doivent être exécutées dans le contexte
de fakeAsync, sinon elles ne seront pas prises en compte par le
mécanisme de simulation de temps.
819
Test Asynchrone
fakeAsync
Lorsque vous utilisez fakeAsync pour simuler l'exécution asynchrone dans
les tests, vous pouvez utiliser plusieurs fonctions pour gérer le temps virtuel :
tick(milliseconds: number) : Cette fonction permet de faire avancer le temps
virtuel et d'exécuter les tâches asynchrones planifiées dans un intervalle de temps
donné (en millisecondes). Cela permet de vous assurer que toutes les tâches
asynchrones ont été exécutées dans un intervalle de temps donné avant de
continuer le test.
flush() : Cette fonction permet de faire avancer le temps virtuel et d'exécuter
toutes les tâches asynchrones planifiées, comme les timeouts et les intervals. Cela
permet de vous assurer que toutes les tâches asynchrones ont été exécutées avant
de continuer le test.
flushMicrotasks() : Cette fonction permet de faire avancer le temps virtuel et
d'exécuter toutes les tâches asynchrones de microtâches planifiées, comme les
promesses et les observables. Cela permet de vous assurer que toutes les
microtâches asynchrones ont été exécutées avant de continuer le test.
820
Test Asynchrone
fakeAsync
it('exemple timeout fakeAsync + tick()', fakeAsync(() => {
let x = 1;
setTimeout(() => {
x++;
}, 1000);
tick(1000);
expect(x).toBe(2);
}));
821
Test Asynchrone
fakeAsync
it('exemple timeout fakeAsync + flush', fakeAsync(() => {
let x = 1;
setTimeout(() => {
x++;
}, 1000);
flush();
expect(x).toBe(2);
}));
822
Exercice
Reprenez ce même exercice en ajoutant un test avec async
Tester le fait qu’avant 1s le h1 n’existe pas.
823
Test Asynchrone
async
Permet de gérer les tests des opérations asynchrones
Ne possède pas les fonctions de gestion de temps comme tick ou
flush,…
Pour gérer la fin des opération asynchrones utiliser la fonction
whenStable de votre fixture.
Cette fonction retourne une promesse vous permettant d’exécuter un
traitement que votre code est stable.
On l’utilise par exemple dans les très rares cas ou on veut tester du
http, on ne sait pas quand ca termine donc on ne peut pas faire du
fakeAsync.
824
Code Coverage
Afin d’obtenir un rapport de code coverage lancer la commande
suivant : ng test --no-watch --code-coverage
Lorsque les tests sont terminés, la commande crée un nouveau
répertoire /coverage dans le projet. Ouvrez le fichier [Link] pour
afficher un rapport avec votre code source et les valeurs de
couverture de code.
825
Tests E2E
Ces test permettent de SIMULER L'UTILISATION RÉELLE de
votre application.
Certains tests ont une vue d'ensemble de haut niveau sur
l'application.
Ils simulent un utilisateur interagissant avec l'application :
navigation vers une adresse,
lecture de texte,
clic sur un lien ou un bouton,
remplissage d'un formulaire,
déplacement de la souris ou saisie au clavier.
826
Tests E2E
Ces tests font des attentes sur ce que l'utilisateur voit.
Du point de vue de l'utilisateur, peu importe que votre application
soit implémentée dans Angular.
L'expérience complète est testée => TESTS DE BOUT EN
BOUT
Les tests de bout en bout constituent également la partie automatisée
des tests d'acceptation puisqu'ils indiquent si l'application fonctionne
pour l'utilisateur.
827
Tests E2E
Comment ca marche ?
Les tests E2E vont donc simuler les interactions de l’utilisateur
avec votre application.
Vous allez donc lancer le navigateur, et le contrôler afin de
simuler un scénario d’interactions.
Une fois le scénario exécuté, vous allez avoir des attentes
(expectations), exactement comme avec les tests unitaires :
Est-ce que les éléments de la pages sont correct
Est-ce que suite au click j’ai le bon affichage
…
828
Tests E2E
Cypress
Cypress est un Framework pour les Test E2E dont les avantages sont :
1. Interface utilisateur facile à utiliser
2. Temps de développement rapide
3. Intégration parfaite avec le développement front-end
4. Exécution rapide des tests
5. Capacité à tester directement dans le navigateur
6. Possibilité de deboguer facilement les tests
7. Prise en charge native de la manipulation du DOM et de l'Ajax
8. Documentations et communauté actives
9. Tests fiables et reproductibles.
829
Tests E2E
Cypress
Afin d’installer Cypress utiliser la commande npm i cypress –save-dev.
Avec angular, utilisez la commande ng add @cypress/schematic
L’utilisation de cette commande permet d’automatiser la configuration
en ajoutant
Cypress et les packages npm auxiliaires à [Link].
Le fichier de configuration Cypress [Link].
Modifiez le fichier de configuration [Link] afin d'ajouter des
commandes d'exécution ng.
Créez un sous-répertoire nommé cypress avec des templates pour
vos tests.
830
Exercice
Installez Cypress et regarder les différents fichiers ajoutés
831
Tests E2E
Le dossier cypress
Le dossier cypress généré contient :
Une configuration [Link] pour tous les
fichiers TypeScript spécifiquement dans ce
répertoire,
Un répertoire e2e pour les tests E2E,
Un répertoire de support pour les commandes
personnalisées et autres assistants de test,
un répertoire fixtures pour les données de
test.
832
Tests E2E
Configuration
Il y a aussi le fichier [Link] au niveau de
la racine de votre projet.
Ce fichier vous permet de configurer cypress
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
'baseUrl': '[Link]
},
component: {
devServer: {
framework: 'angular',
bundler: 'webpack',
},
specPattern: '**/*.[Link]'
}
}) 833
Tests E2E
Lancer les tests E2E
Dans [Link] on peut identifier deux commandes:
cypress:open qui exécute la commande cypress open
cypress:run qui exécute la commande cypress run
834
Tests E2E
Lancer les tests E2E
cypress open est le mode interactif. Elle ouvre une fenêtre
dans laquelle vous pouvez sélectionner le navigateur à utiliser et
les tests à exécuter. A chaque changement, tout est mis à jour.
cypress run, c’est le mode non interactif. Exécute les tests
dans un navigateur "headless". Cela signifie que la fenêtre du
navigateur n'est pas visible. Les tests sont exécutés une fois,
puis le navigateur est fermé et la commande shell se termine.
Cette commande est généralement utilisée dans un
environnement d'intégration continue.
835
Exercice
Lancez cypress en mode interactif et suivez les étapes
836
Tests E2E
Ecrire des tests E2E
Vous devez lancer votre serveur dans un terminal et cypress dans l’autre
Vos tests doivent être dans le dossier e2e
Chaque groupement de test, généralement par page sera représenté par un
fichier dont l’extension est .[Link].
En règle générale, un fichier contient un bloc de description describe.
On peut avoir des blocs de description imbriqués.
À l'intérieur, les blocs beforeEach, afterEach, beforeAll, afterAll peuvent
être utilisés de la même manière que les tests Jasmine.
À l'intérieur des blocs on peut avoir un ou plusieurs attentes.
837
Tests E2E
Visiter une page
Afin d’accéder à une page vous pouvez utiliser visit
Si vous avez définit votre baseUrl dans la config comme nous l’avons spécifié
(Qui est une bonne pratique : [Link]
practices#Setting-a-global-baseUrl), ajoutez l’URI vers lequel vous voulez
naviguer.
[Link]('/') // visits the baseUrl
[Link]('[Link]') // visits the local file "[Link]" if baseUrl is null
[Link]('[Link] // specify full URL if baseUrl is null or the domain is different
//the baseUrl
[Link]({
url: '/pages/[Link]',
method: 'GET',
})
[Link] 838
Tests E2E
Sélectionner des éléments
Certaines méthodes joue le double rôle de sélecteur et d’assertions comme
get qui vérifie que l’élément existe et qui le sélectionne
[Link](): Cette méthode permet de sélectionner un élément spécifique en
utilisant un sélecteur CSS. Exemple: [Link]('#bouton-submit').click()
[Link](): Cette méthode permet de sélectionner un élément en
fonction du texte qu'ils contient.
Exemple: [Link]('Submit').click()
[Link](): Cette méthode permet de sélectionner l'élément qui a le focus
actuellement.
Exemple: [Link]().should('[Link]', 'form-input-focused')
[Link] 839
Tests E2E
Sélectionner des éléments
[Link](): Cette méthode permet de sélectionner le premier élément d'une
liste d'éléments.
Exemple: [Link]('.liste-éléments').first()
[Link](): Cette méthode permet de sélectionner le dernier élément d'une liste
d'éléments.
Exemple: [Link]('.liste-éléments').last()
[Link](): Cette méthode permet de sélectionner le parent d'un élément
donné.
Exemple: [Link]('.élément-enfant').parent()
[Link] 840
Tests E2E
Sélectionner des éléments
[Link](): Cette méthode permet de sélectionner la racine du document
HTML. Exemple: [Link]().should('[Link]', 'racine')
[Link](): Cette méthode permet de sélectionner les enfants d'un élément
donné. Exemple: [Link]('.élément-parent').children().should('[Link]', '3')
[Link](): Cette méthode permet de sélectionner l'élément suivant d'un
élément donné.
Exemple: [Link]('.élément-précédent').next()
[Link](): Cette méthode permet de sélectionner l'élément précédent d'un
élément donné. Exemple: `[Link]('.élément-suivant').prev().
[Link] 841
Tests E2E
Sélectionner des éléments
Les bonnes pratiques
Cypress déconseille d’utiliser les sélecteurs susceptibles d’être modifiés
fréquemment comme les classes ou les ids c’est un Anti-Pattern.
La bonne pratique est d’utiliser des attributs avec ce pattern data-*
permettant de donner un context à vos sélecteurs et de les isoler des
changements css et js.
De plus en utilisant cette technique le selector Playground de cypress va
préférer ces sélecteurs et les mettre en avant :
data-cy
data-test
data-testid
[Link] 842
Exercice
Dans la page Cv, vérifiez l’existence de la liste des cvs.
Vérifiez qu’il n’existe pas de cvCard au départ (utiliser l’assertion
.should(‘[Link]’)).
843
Tests E2E
Test des requêtes HTTP
Lorsque vous testez des apis, vous avez deux stratégies:
Utilisez la réponse du serveur : la stratégie de requêtes réelles consiste à utiliser
les API externes pour effectuer des tests. Cela signifie que les tests sont plus
proches de la réalité, mais peuvent être plus lents et plus instables en raison
de la dépendance aux API. Cependant, cette approche garantit une meilleure
couverture des cas d'utilisation et une meilleure qualité de test en général.
Utilisez des fixtures : : La stratégie de requêtes mockées consiste à
remplacer les réponses API réelles par des réponses prédéfinies et contrôlées
par le développeur. Cela signifie que les tests ne dépendent pas de la
disponibilité ou de la rapidité des API, ce qui peut accélérer les tests et les
rendre plus fiables. Cependant, cette approche n'est pas toujours réaliste et peut
ne pas couvrir tous les cas d'utilisation possibles.
[Link] 844
Tests E2E
Test des requêtes HTTP
API Réel
Avantages
Plus susceptible de travailler en production
Tester la couverture de vos endpoints
Idéal pour le rendu HTML traditionnel côté serveur (en cas de réponse HTML et non JSON)
Inconvénients
Nécessite de seeder des données (Base de données de test à préparer pour les différents cas)
Beaucoup plus lent
Plus difficile à tester les cas extrêmes
Utilisation suggérée
Utiliser avec parcimonie
Idéal pour les chemins critiques de votre application
[Link] 845
Tests E2E
Test des requêtes HTTP
API Mockés
Avantages
Contrôle des corps de réponse, de l'état et des en-têtes
Peut forcer les réponses à prendre plus de temps pour simuler le retard du réseau
Temps de réponse rapides, < 20 ms
Inconvénients
Aucune garantie que vos réponses tronquées correspondent aux données réelles envoyées par le
serveur
Aucun test couverture sur certains points de terminaison de serveur
Pas aussi utile si vous utilisez le rendu HTML traditionnel côté serveur
Utilisation suggérée
Utilisez pour la grande majorité des tests
Mélangez et faites correspondre, ayez généralement un vrai test de bout en bout, puis remplacez
le reste
Parfait pour JSON Apis
[Link] 846
Tests E2E
Test des requêtes HTTP
API Mockés
Cypress vous permet de remplacer une réponse et de contrôler le
corps, l'état, les en-têtes ou même le délai.
[Link]() est utilisé pour contrôler le comportement des requêtes
HTTP. Vous pouvez définir de manière statique le corps, le status
HTTP, les en-têtes et d'autres caractéristiques de réponse.
Elle peut prend en paramètre un grand nombre de combinaison
selon votre cas d’utilisation.
[Link] 847
Tests E2E
Test des requêtes HTTP
API Mockés
spying
[Link]('/users/**')
[Link]('GET', '/users ')
[Link]({
method: 'GET’,
url: '/users ’,
hostname: 'localhost',
})
spying and response stubbing
[Link]('POST', '/users ', {
statusCode: 201,
body: {
name: 'Peter Pan’,
},
})
spying, dynamic stubbing, request modification, etc.
[Link]('/users ', { hostname: 'localhost' }, (req) {
do something with request and/or response
})
[Link] 848
Tests E2E
Test des requêtes HTTP
API Mockés / intercept, mockez une réponse
Lorsque vous utilisez intercept suivez les étapes suivantes:
1. Préparer l’interception
2. Lancer l’opération souhaité
3. Lancez vos Assertions
[Link] 849
Tests E2E
Test des requêtes HTTP
API Mockés / intercept, mockez une réponse
Vous pouvez moquer la réponse de votre api avec des fixtures.
Les fixtures peuvent êtres de plusieurs types et vous avez le dossier fixtures pour
les stocker.
// requests to '/update' will be fulfilled [Link]('/not-found', { [Link](
// with a body of "success" statusCode: 404, {
[Link]('/update', 'success') body: '404 Not Found!', method: 'GET',
// requests to '/[Link]' will be fulfilled headers: { url: [Link],
// with the contents of the "[Link]" fixture 'x-not-found': 'true', },
[Link]('/[Link]', { fixture: '[Link]' }) }, {
[Link]('/projects', { }) fixture: 'cvs',
body: [{ projectId: '1' }, { projectId: '2' }], }
}) )
[Link] 850
Tests E2E
Test des requêtes HTTP
API Mockés / intercept, affecter un alias
Afin de pouvoir manipuler l’intercept, comme par exemple l’attendre
avec un wait, vous pouvez lui affecter un alias.
[Link]('[Link]
[Link]('@getSettings')
[Link]({
url: '[Link]
query: { q: 'expected terms' },
}).as('search')
[Link]('@search')
[Link] 851
Exercice
Faite en sorte d’avoir des fixtures pour la liste des cvs permettant de
tester cette liste.
Vérifier que l’affichage utilise vos fixtures
Ajouter des fixture pour la sélection d’un cv par son id.
852
Tests E2E
Les assertions
Cypress intègre plusieurs assertions de diverses bibliothèques
d'assertions JS telles que Chai, jQuery, etc.
Nous pouvons globalement classer toutes ces assertions en deux
segments en fonction du sujet sur lequel nous pouvons les invoquer :
Les assertions implicites
Les assertions explicites
853
Tests E2E
Les assertions implicites
Lorsque l'assertion s'applique à l'objet fourni par la commande chaînée
parente, elle s'appelle une assertion implicite.
Cette catégorie d'assertions inclut généralement des commandes telles que
".should()" et ".and()".
Comme ces commandes ne sont pas indépendantes et dépendent toujours
de la commande parente précédemment chaînée, elles héritent et agissent
automatiquement sur l'objet généré par la commande précédente.
Généralement, nous utilisons des assertions implicites lorsque nous voulons :
Affirmer plusieurs validations sur le même sujet.
Changez de sujet avant de faire des affirmations sur le sujet.
[Link] 854
<table class="table table-bordered assertion-table">
<thead>
[Link]('.assertion-table')
.find('tbody tr:last')
.should('[Link]', 'success')
.find('td') // Pour vérifier qu'un texte valide une expression régulière,
.first() // préférer l'utilisation de contains
// valider le contenu d'un élément [Link]('.assertion-table')
.should('[Link]', 'Column content') .find('tbody tr:last')
.should('contain', 'Column content') // finds first element with text content matching regular
.should('[Link]', 'Column content') expression
.should('match', 'td') .contains('td', /column content/i)
.should('[Link]')
[Link] 855
Tests E2E
Les assertions
have
exist : pour vérifier l'existence d'un élément
[Link] : pour vérifier la visibilité d'un élément
[Link] : pour vérifier l'état activé/désactivé d'un élément
[Link] : pour vérifier l'état coché/décoché d'un élément
[Link] : pour vérifier la valeur d'un élément
[Link] : pour vérifier le texte d'un élément
[Link] : pour vérifier la présence d'un attribut de style sur un
élément
[Link] : pour vérifier la présence d'une classe sur un élément
[Link] 856
Tests E2E
Les assertions
[Link] : pour vérifier la longueur d'un objet.
[Link] / [Link] : pour vérifier si une expression est vraie ou fausse
eq / equal / eql : pour vérifier l'égalité de deux valeurs
contain : pour vérifier la présence d'une valeur dans un tableau ou une
chaîne de caractères
match : pour vérifier si une chaîne de caractères correspond à une
expression régulière
[Link] / [Link] : pour vérifier si une valeur est
supérieure ou inférieure à une autre valeur.
[Link] 857
Exercice
Dans la page des cvs vous avez deux onglets, un pour les seniors et un
pour les juniors.
Vérifiez que vous avez les deux onglets
Vérifiez que le premiers élément correspond pour les deux listes
Vérifiez que La taille des deux listes est correcte.
Vérifiez que le premier onglet et visible et que le second ne l’est pas
858
Tests E2E
Les assertions explicites
Lorsqu'il est nécessaire de transmettre un sujet explicite à l'assertion, celle-ci
relève de la catégorie d'assertion explicite.
Cette catégorie d'assertions contient les commandes telles que "expect()" et
"assert()", qui permettent de passer un sujet explicitement à l’assertion.
En règle générale, vous utiliserez des "assertions explicites" lorsque vous
souhaitez :
Effectuez une logique personnalisée avant de faire les affirmations sur le sujet
donné.
Effectuez plusieurs assertions sur le même sujet.
Cependant les deux types d’assertions peuvent être utilisées
[Link] 859
Tests E2E
Les assertions
[Link]('div').should(($div) {
expect($div).[Link](1)
860
Tests E2E
Les assertions
Pour ces assertions Cypress regroupe la célèbre bibliothèque d'assertions
Chai, ainsi que ses extensions comme jQuery
Chainers Assertion
attr(name, [value]) expect($el).[Link]('foo', 'bar')
prop(name, [value]) expect($el).[Link]('disabled', false)
css(name, [value]) expect($el).[Link]('background-color', 'rgb(0, 0, 0)')
data(name, [value]) expect($el).[Link]('foo', 'bar')
class(className) expect($el).[Link]('foo')
id(id) expect($el).[Link]('foo')
[Link] 861
Tests E2E
Les assertions
Chainers Assertion
html(html) expect($el).[Link]('I love testing')
text(text) expect($el).[Link]('I love testing')
value(value) expect($el).[Link]('test@[Link]')
visible expect($el).[Link]
hidden expect($el).[Link]
selected expect($option).[Link]
checked expect($input).[Link]
expect($input).[Link]
focus[ed]
expect($input).[Link]
[Link] 862
Tests E2E
Les assertions
Chainers Assertion
enabled expect($input).[Link]
disabled expect($input).[Link]
empty expect($el).[Link]
exist expect($nonexistent).[Link]
match(selector) expect($emptyEl).[Link](':empty')
contain(text) expect($el).[Link]('text')
descendants(selector) expect($el).[Link]('div')
[Link] 863
Tests E2E
Location
Afin d’avoir des information sur la localisation
actuelle, donc l’url actif, vous pouvez utilisez la
commande location.
Avec l’assertion should, vous pouvez lui passez une
callback qui prend en paramètre la location et appelle
les expections que vous voulez valider.
[Link]().should((location) => {
expect([Link]).[Link]('/cv/1');
});
864
Tests E2E
Déclencher des actions
Cypress vous permet de simuler des fonctions.
Pour écrire dans un élément DOM, utilisez la commande .type().
Vous pouvez effacer le champ avant de taper avec clear()
it('Visits the initial project page', () => {
[Link]('/');
[Link]('Faurecia');
[Link]('[data-cy=email-input]')
.type('aymen@[Link]')
.should('[Link]', 'aymen@[Link]')
});
[Link] 865
Tests E2E
Déclencher des actions
Pour avoir le focus sur un élément du DOM, utilisez la commande focus()
Pour perdre le focus sur un élément du DOM, utilisez la commande blur()
Pour soumettre un formulaire, utilisez la commande [Link]()
Pour cliquer sur un élément du DOM, utilisez la commande click(). Si
l’élément n’est pas visible ou n’est pas enabled ajouter en paramètre
{force:true} .click({ force: true });
[Link] 866
Tests E2E
Déclencher des actions
Afin de simuler le click sur un caractère spécial, entre, insert, delete, utilisez
la syntaxe suivante:
// Special characters: // Arrows:
[Link]('input').type('{enter}') [Link]('input').type('{upArrow}')
[Link]('input').type('{backspace}') [Link]('input').type('{downArrow}')
[Link]('input').type('{del}') [Link]('input').type('{leftArrow}')
[Link]('input').type('{esc}') [Link]('input').type('{rightArrow}')
[Link]('input').type('{end}')
[Link]('input').type('{home}')
[Link]('input').type('{insert}') // Modifier keys:
[Link]('input').type('{moveToEnd}') // Move cursor to the end of [Link]('input').type('{shift}')
typeable element [Link]('input').type('{ctrl}')
[Link]('input').type('{moveToStart}') // Move cursor to the start of [Link]('input').type('{alt}')
typeable element
[Link]('input').type('{pageDown}') // Scroll down
[Link]('input').type('{pageUp}') // Scroll up
[Link]('input').type('{selectAll}') // Select the entire input value 867
Exercice
Ecrivez un test qui permet de tester le color component
Au début la couleur de la div doit être celle de l’uri
Lorsqu’on écrit dans l’input la couleur de la div change
Lorsqu’on appuie sur reset la couleur reprend la couleur par défaut
868
Exercice
Ecrivez un test qui permet de tester le fonctionnement de la sélection
d’un cv via le click sur le bouton détails.
Utilisez des fixtures
Simulez un click sur le bouton détails du premier élément.
Vérifiez qu’en cliquant sur le bouton, vous accéder à la bonne url
Vérifier que les infos du cv sont bien affichées
869
PWA
Les Progressive Web Apps (PWA) sont des applications Web qui utilisent
les dernières technologies pour se rapprocher de la fiabilité et des
performances des applications mobiles natives.
Les PWA peuvent s’installer sur la tablette ou le mobile de l’utilisateur et
possèdent leur propre icône, sans avoir besoin de passer par un app store.
Elles fonctionnent sur tous les supports, elles sont progressives et responsives.
Elles se lancent en plein écran et peuvent émettre des notifications push.
Elles sont indexables par les moteurs de recherche, contrairement aux
applications natives, elles sont donc plus simple à trouver.
Elles sont partageables avec un lien sans installation (contrairement à une
application mobile).
870
PWA
Les services workers
L’une des technologies utilisée par les PWA sont les service workers. Ce
sont de scripts qui tournent dans les navigateurs et sont responsables de
la mise en cache des ressources nécessaires à une application.
Ils fonctionnent comme un proxy du côté du navigateur : ils
interceptent toutes les requêtes HTTP sortantes effectuées par
l’application et décident de comment y répondre (Passer par le cache ou
utiliser le réseau).
Les service workers sont toujours actifs même après la fermeture d’un
onglet.
871
PWA
Les services Workers
Quand elles sont lancées depuis l’écran de l’utilisateur, les services workers
permettent aux PWA de se charger instantanément, et ce même hors
ligne.
Elles sont à jour grâce aux services workers.
Disponibles en production uniquement en HTTPS et en développement
sur localhost.
Les avantages en termes de performances de l'installation de tous nos
bundles Javascript et CSS sur le navigateur de l'utilisateur, c’est qu’elles
rendent le démarrage de l'application beaucoup plus rapide.
872
Les services workers dans Angular
Depuis sa version 5, Angular nous propose sa propre API pour gérer les
service workers.
Le Service Worker d’Angular peut mettre en cache toutes sortes de
contenus dans le Cache Storage du navigateur.
Le service worker Angular est comme un cache installé du côté du
navigateur de l’utilisateur. Son objectif est de répondre aux requêtes
effectuées par l’application Angular pour obtenir des ressources ou des
données grâce au cache local.
Comme tous les caches, il y a des règles paramétrables pour décider
comment le système de cache fonctionne.
873
Les services workers dans Angular
Le service worker Angular est implémenté de la manière suivante :
Lors du premier chargement de l’application, il met en cache les
parties de l’application configurées pour être cachées.
Lorsque l’utilisateur rafraîchit l’application, il obtient la dernière
version de l’application mise en cache.
La mise à jour de l’application est faite en arrière plan, pendant
que l’utilisateur utilise la version précédente qui est en cache. Lorsque
la nouvelle version est totalement chargée, la mise à jour est prête.
Lorsque l’utilisateur rechargera une nouvelle fois l’application il
aura la dernière version en cache et il obtiendra la version mise à
jour.
874
Les services workers dans Angular
Pour obtenir ce comportement, le service worker Angular charge un
fichier [Link] (angular pour ng, et service worker pour sw) depuis
le serveur. Ce fichier décrit les ressources à mettre en cache et permet
de savoir la version de tous les fichiers mis en cache.
Lorsque l’application est mise à jour, le contenu de ce fichier
change, et le service worker est informé qu’une nouvelle version de
l’application est disponible et doit être téléchargée et mise en
cache.
Ce fichier est généré à partir d’un fichier de configuration ngsw-
[Link].
875
Les services workers dans Angular
1er chargement de l’application
1- http Request 2- http Request
Cache Storage
876
Les services workers dans Angular
n éme chargement
1- http Request Donne moi le fichier
[Link]
Angular App Service Worker Server
3- Version en cache
de l’application
Cache Storage
Si le fichier [Link] récupéré au
niveau du serveur est modifié, je met
à jour les données du cache
877
Les services workers dans Angular
Afin de mettre en place votre Service worker utiliser la commande
ng add @angular/pwa --project NOM_DU_PROJET
Cette commande va permettre d’effectuer les actions suivantes :
Ajouter la dépendance @angular/service-worker
Activer le support du service worker par le CLI
Importer et activer le service worker dans le module racine [Link]
Mets à jour le fichier [Link] pour importer le [Link] et des
métas tags pour les couleurs
Créer les fichiers d’icônes pour pouvoir ajouter la PWA à l’écran d’accueil sur mobile
Crée le fichier [Link] qui permet notamment de configurer la mise en
cache
[Link] 878
Exercice
Installer le module pwa d’Angular
Builder votre projet via ng build
Si vous avez le serveur http-client installé, utiliser le pour tester votre
application. Sinon installer le via la commande npm i -g http-server
Lancer la commande http-server –c-1 dist/nomDeVotreProjet
Accéder à l’url localhost:8080, vérifier que ca marche puis ouvrez le
devtools de votre navigateur.
Allez dans l’onglet application et vérifier les éléments Manifest et Service
Workers.
Mettez vous en mode offline et vérifier que votre application marche
encore.
879
Exercice
880
Exercice
Visiter votre dossier de build et vérifier les changements qui ont été
réalisés
881
Les services workers
[Link]
Le fichier [Link] permet de spécifier quels fichiers et
quelles données provenant des URLs spécifiées doivent être mise en
cache par le service worker et également comment ils doivent être mis à
jour.
Ce fichier est ensuite utilisé par le CLI pendant le build en production (ng
build).
[Link] utilise le format JSON. Tous les chemins de fichiers
doivent commencer par /, qui correspond au répertoire de déploiement —
généralement dist/<nom-projet> dans les projets CLI.
Les modèles utilisent un format limité qui sera converti en interne en
regex
[Link] 882
Les services workers
[Link]
** permet de matcher 0 ou plusieurs niveaux de chemin (
/niv1/niv2/../nivn).
? Correspond exactement à un caractère à l'exception de /
! Marque le modèle comme étant négatif, ce qui signifie que seuls les
fichiers qui ne correspondent pas au modèle sont inclus.
* permet de matcher 0 ou plusieurs caractères en excluant /.
/**/*.html spécifie tous les fichiers HTML
/*.html spécifie uniquement les fichiers HTML à la racine
[Link] 883
{
"$schema": "./node_modules/@angular/service-worker/config/[Link]",
"index": "/[Link]",
"assetGroups": [
{
"name": "app",
"installMode": "prefetch",
"resources": {
"files": [
"/[Link]", "/[Link]", "/[Link]", "/*.css", "/*.js"
]
}
},
{
"name": "assets",
"installMode": "lazy",
"updateMode": "prefetch",
"resources": {
"files": [
"/assets/**",
"/*.(svg|cur|jpg|jpeg|png|apng|webp|avif|gif|otf|ttf|woff|woff2)"
]
}
}
]
}
884
Les services workers
[Link]
Les AssetGroups
Les AssetGroups sont les ressources qui sont mises à interface AssetGroup {
name: string;
jour en même temps que l’application. installMode?: 'prefetch'| 'lazy';
updateMode?: 'prefetch' | 'lazy';
La propriété assetGroups est un tableau qui contient resources: {
files?: string[];
des asset groups, qui décrivent un type de ressources urls?: string[];
et la stratégie de mise en cache. };
cacheQueryOptions?: {
ignoreSearch?: boolean;
Elles peuvent inclure des ressources chargées depuis };
}
le serveur, depuis des CDNs ou autres sources externes.
Vous pouvez utiliser les regex pour matcher des URLs
dont seule une partie peut être connue par avance.
885
Les services workers
[Link]
Les assetGroups - installMode
Le mode d’installation, installMode, permet de paramétrer la
stratégie de mise en cache initiale d’une ressource.
Il existe deux types de stratégies de mise en cache :
prefetch : permet de télécharger toutes les ressources listées pendant
que la version actuelle de l’application est mise en cache. Cette
stratégie permet de s’assurer que toutes les ressources listées seront
disponibles, même hors connexion.
lazy : les ressources ne sont mise en cache que lorsque des requêtes
pour les charger sont émises.
886
Les services workers
[Link]
Les assetGroups - updateMode
Le mode de mise à jour, updateMode, permet de paramétrer la stratégie
de mise à jour des ressources mises en cache lorsqu’une nouvelle
version de l’application est disponible.
Il existe deux types de stratégies de mise à jour des ressources qui ont
changé depuis la dernière version de l’application :
prefetch : permet de dire au service worker de télécharger et de mettre
en cache les ressources qui ont été modifiées immédiatement.
lazy : permet de dire au service worker d’attendre que ces ressources
soient requêtées pour les télécharger de nouveau afin de les mettre à
jour dans le cache. Pour activer ce mode de mise à jour, il est nécessaire
que le mode d’installation soit aussi lazy.
887
Les services workers
[Link]
Les assetGroups - ressources
Les ressources peuvent être de deux types.
Les fichiers, files, sont une liste de regex qui match un ou plusieurs
fichiers dans le dossier du build (public ou dist généralement).
Les urls sont une liste de regex qui match des urls lors de l’exécution
de l’application. Elles permettent par exemple de mettre en cache des
polices de caractères. "resources": {
"files": [
"/[Link]",
"/[Link]",
"/[Link]",
"/*.css",
"/*.js"
]
}
888
Les services workers
[Link]
Le fichier [Link] d'une application Web fournit des
informations la concernant dans un fichier JSON afin de pouvoir
l'installer sur l'écran d'accueil d'un appareil mobile.
Les propriétés sont :
Le name ou le short_name sera le nom utilisé sur l’écran d’accueil du
mobile sous l’icône de votre application une fois que celle-ci aura été installée.
Le theme_color définit la couleur du thème de l’application. Cette couleur
sera utilisée à certains endroits par le système d’exploitation (Android par
exemple, va utiliser cette couleur dans le gestionnaire des applications lancées).
889
Les services workers
[Link]
background_color est la couleur de fond de base de l’application web.
Cette valeur peut être utilisée par le navigateur comme couleur de
fond lorsque le fichier [Link] est disponible avant
que la feuille de style de l’application soit chargée.
display est le mode d’affichage de l’application web sur mobile
lorsqu’elle est installée. La valeur recommandée est standalone pour
la plupart des utilisations. L’application ressemblera à une
application native et se comportera comme telle
([Link]
icons sont les images qui vont servir d’icônes pour l’application
dans différents tailles sur l’appareil mobile ou la tablette.
890
Les services workers
Le dossier Build
[Link]
Ce fichier est généré à chaque build du projet.
Il s'agit du fichier de configuration d'exécution que le Service Worker
d’Angular utilise.
Ce fichier est construit sur la base du fichier [Link] et
contient toutes les informations nécessaires au Service Worker
d’Angular pour savoir au moment de l'exécution quels fichiers il
doit mettre en cache et quand.
C’est une version étendue du fichier [Link], où toutes
les URL génériques ont été appliquées et remplacées par les
chemins de tous les fichiers qui leur correspondaient.
891
{
"configVersion": 1,
"index": "/[Link]",
"assetGroups": [
{
"name": "app", …
"urls": [
"/[Link]",
"/[Link]", …]
},
{
"name": "assets",…
"urls": [
"/assets/icons/[Link]",
"/assets/icons/[Link]", …
]
}
],
"hashTable": {
"/assets/icons/[Link]": "b1f06c7d714abb4a0cc4d1f7c954feb26826e4c4",
"/assets/icons/[Link]": "96a0d629765c1bc85d9263c6bc83094ca776d267",
…
},
} 892
Les services workers
Le service swUpdate
Le service SwUpdate permet d’accéder à plusieurs événements
qui indiquent le moment où le service worker a trouvé une mise à
jour disponible de l’application ou lorsqu’il a activé une mise à
jour, c’est-à-dire lorsqu’il sert le contenu de l’application mise à jour.
Ce service permet trois opérations :
D’être notifié lorsqu’une mise à jour est disponible : c’est-à-dire une nouvelle
version de l’application qui sera chargée lors du rafraichissement de la page.
Demander au service worker de vérifier si une mise à jour est disponible.
Demander au service worker d’activer la dernière version
893
Les services workers
Le service swUpdate
versionUpdate
La propriété versionUpdates est un observable de SwUpdate et qui
émet quatre événements :
Émis lorsque le service worker a détecté une nouvelle version de l'application sur le
VersionDetectedEvent
serveur et est sur le point de commencer à la télécharger.
Émis lorsqu'une nouvelle version de l'application est disponible pour être activée par les
VersionReadyEvent clients. Il peut être utilisé pour informer l'utilisateur d'une mise à jour disponible ou l'inviter
à actualiser la page.
Émis lorsque l'installation d'une nouvelle version a échoué. Il peut être utilisé à des fins de
VersionInstallationFailedEvent
journalisation/surveillance.
894
Exercice
Faites en sorte d’avoir une alerte lors de la détection d’une nouvelle
version de votre application.
Cette alerte devra notifier l’utilisateur qu’une nouvelle version de
l’application a été détecté et s’il veut recharger la page.
895
Les services workers
[Link]
Les dataGroups
Les dataGroups export interface DataGroup {
permettent de mettre en name: string;
urls: string[];
cache la réponse de version?: number;
requêtes à des APIs. cacheConfig: {
maxSize: number;
Les requêtes pour des maxAge: string;
données ne sont pas timeout?: string;
strategy?: 'freshness' | 'performance';
versionnées comme les };
assets. Elles sont mises en cacheQueryOptions?: {
ignoreSearch?: boolean;
cache suivant des };
paramètres configurés }
manuellement.
896
Les services workers
[Link]
Les dataGroups
name, permet d’identifier chaque dataGroup de manière unique.
urls contient un tableau avec des regex qui vont permettre la mise en
cache des réponses des requêtes aux URLs qui matcheront ces regex.
version permet d’indiquer que le format des données de l’API a
changé et que l’ancien n’est plus compatible avec l’application. Il permet
donc d’indiquer au service worker qu’il est nécessaire de ne pas utiliser
les données mises en cache depuis l’API. Cette propriété est un nombre
qui s’incrémente manuellement en partant de 0.
897
Les services workers
[Link]
Les dataGroups
maxSize correspond au nombre maximal de réponses d’API en cache.
maxAge permet d’indiquer combien de temps les données mises en cache
peuvent être conservées. Elle se paramètre par exemple : 2d12h (avec d pour days,
h, m, s et u pour heures, minutes, secondes et millisecondes respectivement).
strategy peut être définie entre, performance et freshness.
Performance: le service worker va répondre en priorité avec les données en
cache.
freshness le service worker va d’abord effectuer une requête réseau et ne
répondra avec le cache que si cette requête est en timeout.
timeout permet de spécifier la durée avant de considérer que le réseau est en
timeout. Le service worker Angular va attendre ce temps avant d’utiliser la réponse
en cache (si il est configuré en stratégie freshness comme nous allons le voir).
898
Exercice
Ajouter dans votre composant l’affichage de la liste des users
récupérées depuis [Link]
Faite en sorte que vos appels d’apis soient cachées.
Rebuilder votre application et tester son fonctionnement en offline.
899
Les services workers
Résumé
Pour résumer, voici comment les nouvelles versions d'application sont
gérées par Angular Service Worker :
Pour chaque nouvelle version, un nouveau [Link] 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
[Link] 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
900
Les Push Notifications
Les push notifications web sont des informations que vous pouvez
envoyer à vos utilisateurs s’ils ont accepté d’en recevoir, et ce
même si votre site est fermé.
Pour recevoir une notification il suffit que leur navigateur soit
ouvert, même en tâche de fond.
Vous pouvez penser aux notifications push sur mobile.
Afin de faire ca, vous avez besoin d’un script qui fonctionne en
tache de fond. C’est le Service Worker
901
Les Push Notifications
Les push notifications web sont une combinaison de Web Push et
de notification.
L’API de notification du browser vous permet d’afficher des
notifications à votre utilisateur. Ceci est indépendant de l’API Web
Push.
if ('Notification' in window) {
[Link](`Vous posséder l'API de Notification`);
if ([Link] === 'granted') {
[Link]('Permission accordée');
new [Link]('Ceci st ma première notification!');
}
} 902
Les Push Notifications
Le push API permet aux applications Web de recevoir des
messages d'un serveur, même lorsque le site Web n'est pas ouvert.
Nous pouvons utiliser cette API pour recevoir des messages puis les
afficher, en utilisant l'API de notification.
Cette API est uniquement disponible via les service workers, qui
permettent d'exécuter du code en arrière-plan lorsque l'application n'est
pas ouverte.
903
Les Push Notifications
Cependant, les navigateurs ne nous permettent pas d’envoyer les
notifications de notre serveur directement au navigateur de
l'utilisateur.
Ceci est du à la peur de spammer l’utilisateur avec des notifications.
Au lieu de cela, seuls certains serveurs spécifique au navigateur
pourront envoyer des notifications à un navigateur donné.
Ces serveurs sont connus sous le nom de Browser Push Service.
Notez que par exemple, le service Push utilisé par Chrome (Firebase
Cloud Messaging) est différent de celui utilisé par Firefox (autopush).
904
Les Push Notifications
905
Les Push Notifications
Afin de pouvoir délivrer un message à un utilisateur donné et
uniquement à cet utilisateur, le Service Push identifie l'utilisateur
de manière anonyme, garantissant la confidentialité de l'utilisateur.
Afin que le Service Push puisse analyser le comportement du serveur,
il doit pouvoir l’identifier.
Afin de faire ca on va utiliser le protocole VAPID (Voluntary
Application Server Identification)
906
Les Push Notifications
VAPID
Une paire de clés VAPID est une paire de clés publique/privée
cryptographique utilisée de la manière suivante :
La clé publique est utilisée comme identifiant unique du serveur pour
abonner l'utilisateur aux notifications envoyées par ce serveur
La clé privée doit être gardée secrète et utilisée par le serveur d'application
pour signer les messages, avant de les envoyer au service Push pour livraison.
Afin de générer les clés, installer la librairie web-push côté serveur
npm install -g web-push
Pour générer la paire de clés nécessaire au protocole VAPID :
web-push generate-vapid-keys --json
907
Les Push Notifications
VAPID
Pour notre serveur de notification, voici les deux clés utilisés :
vapidKeys = {
publicKey:
'BCucKI9WG2aEbOhigJ6y_3Z28GIe0jp9QjaZtXXUABjGoUMup6IoYUrTSM
h72MxP3t6eVoV6cZ1doEGMm9cRJ2Q',
privateKey: 'OQazMChcws-F21vMTt81rlpRr4eZsWkR9yQlZAf0lW0',
};
908
[Link] 909
Les Push Notifications
910
Les Push Notifications
1- Inscription au service push
Côté client : autorisation et PushSubscription
La première étape est d’inscrire un utilisateur pour qu’il puisse
recevoir des notifications de votre application.
Il faut d’abord obtenir son autorisation avec une pop-up spécifique,
native au navigateur.
Ensuite, il est nécessaire d’obtenir du navigateur une
PushSubscription.
Cette PushSubscription contient toutes les informations nécessaires
pour pouvoir envoyer une notification à un utilisateur donné.
Cette inscription utilise l’API push.
911
Les Push Notifications
Quelles sont les étapes pour permettre
l’activation des notifications web push ?
Afin de recevoir cette PushSubscription, injecter le service SwPush offert
par votre ServiceWorkerModule.
Utilisez ensuite sa méthode requestSubscription qui prend en paramètre
un objet d’option dont le paramètre obligatoire est la propriété
serverPublicKey qui correspondra à la clé publique de votre VAPID.
Lorsque vous l’appelez, cette méthode va déclencher un popup demandant
à l’utilisateur s’il accepte ou pas d’être notifié (ceci se fait une seule fois).
S’il accepte la fonction retourne une promesse qui va émettre la
PushSubscription.
S’il n’accepte pas vous n’aurez aucun moyen de lui redemander.
912
Les Push Notifications
Quelles sont les étapes pour permettre
l’activation des notifications web push ?
[Link]
.requestSubscription({
serverPublicKey: [Link],
})
.then((sub:PushSubscription) => {
// Faites ce que vous voulez avec cet objet
})
.catch((err) => {//en cas de refus}
);
913
Les Push Notifications
Quelles sont les étapes pour permettre
l’activation des notifications web push ?
L’objet Push Subscription contient les informations suivantes :
{
subscription: {
endpoint: '[Link]
expirationTime: null,
keys: {
p256dh: 'BJIRfMw6Va3GG2u…',
auth: 'xBnPrD-I0_xNzikHRuxCxg'
}
}
}
914
Les Push Notifications
915
Exercice
Mettez en place le système de push notification afin de récupérer
l’objet de type pushSubscription
Vérifier ce qui se passe lorsque vous dites non lors de la demande
d’autorisation.
Afin d’annuler les demandes d’autorisation que ce soit l’accpetation et
les rejets vous pouvez suivre ce lien
chrome://settings/content/notifications
916
Les Push Notifications
Quelles sont les étapes pour permettre
l’activation des notifications web push ?
2 - Le client envoi une demande d’inscription au serveur en y incluant
l’objet subscription.
3 - Côté serveur : Le serveur doit sauvegarder le subscription
Votre serveur reçoit la PushSubscription envoyée par votre
application cliente et l’enregistre habituellement dans une base de
données pour pouvoir envoyer des notifications à l’utilisateur.
Pour envoyer une notification, vous allez devoir passer par un service
push qui va ensuite l’envoyer à l’utilisateur, et ce à des fins de contrôles.
Si le navigateur n’est pas en ligne, il va la mettre en attente
jusqu’à ce que l’utilisateur l’ouvre.
917
Les Push Notifications
Quelles sont les étapes pour permettre
l’activation des notifications web push ?
subscribeToNotifications() {
[Link]
.requestSubscription({
serverPublicKey: [Link],
})
.then((sub:PushSubscription) => {
[Link]({sub});
[Link]
.post('[Link] sub)
.subscribe((data) => [Link]({ data: data }));
})
.catch((err) =>
[Link]('Could not subscribe to notifications', err)
);
}
918
private sendPushNotification(subscription) {
webpush
.sendNotification(
subscription,
[Link]({
notification: {
title: 'Our first push notification',
body: 'Here you can add some text',
},
}),
options,
)
.then((log) => {
[Link]('Push notification sent.');
[Link](log);
})
.catch((error) => {
[Link](error);
});
}
}
[Link] 919
Exercice
Connecter vous au service de notification suivant et tester la réception
d’une notification.
[Link]
920
Server Side Rendering
Angular Universal
Angular Universal est une librairie développée par l'équipe d'Angular
qui permet le Server Side Rendering (SSR) et le prerendring de
votre application.
Dans son fonctionnement standard, une application Angular va
s'exécuter dans le navigateur et ne va entamer l’affichage qu’après s'être
chargée.
Afin d’éviter cet aspect ‘page blanche’ et éviter à l’utilisateur
d’attendre de peur qu’il quitte la page, Angular Universal va permettre
de générer des pages statiques sur votre serveur afin de rendre votre
application accessible avant que votre application SPA ne soit
chargée.
921
Server Side Rendering
Angular Universal
Comment ca marche ?
Lorsque nous utilisons Angular Universal, nous restituons à
l'avance le HTML et le CSS initiaux présentés à l'utilisateur.
Nous pouvons le faire par exemple au moment de la construction,
ou à la volée sur le serveur lorsque l'utilisateur demande la page.
Ce code HTML et CSS sera d'abord servi à l'utilisateur, afin que
l'utilisateur puisse voir rapidement quelque chose à l'écran.
En même temps, votre application Angular sera chargé côté
client et prendra alors le contrôle de la page, et à partir de là, tout
fonctionnera comme une SPA normale
922
Server Side Rendering
Angular Universal
Pourquoi ?
Il existe trois raisons principales pour utiliser Angular Universal :
1. Affichez la première page rapidement avec un premier rendu
(FCP)
2. Améliorer les performances sur les appareils mobiles et à faible
puissance.
3. Faciliter le travail des robots d'exploration (crawlers) des réseaux
sociaux et d'exploration Web pour l'optimisation des moteurs de
recherche (SEO).
[Link] 923
Server Side Rendering
Angular Universal
Mise en place
Afin de mettre en place Angular
Universal, Angular nous offre un
Schematic prêt à l’emploi. Lancez la
commande suivante :
ng add @nguniversal/express-
engine
La commande met à jour le code de
l'application pour activer le SSR et
ajoute des fichiers supplémentaires à
la structure du projet.
[Link] 924
Exercice
Télécharger le projet de ce répo Github :
[Link]
Mettez en place Angular Universal
925
Server Side Rendering
Angular Universal
[Link]
C’est le fichier d’entrée de la partie serveur.
Il exporte le AppServerModule qui lui représente le AppModule du
serveur.
926
Server Side Rendering
Angular Universal
[Link]
Ce fichier représente le serveur ts qui permettra de gérer les requêtes.
ngExpressEngine permet de rendre l'application avec Express et de
gérer la mise en cache des requêtes.
// Example Express Rest API endpoints
// [Link]('/api/**', (req, res) => { }); [Link]('html', ngExpressEngine({
// Serve static files from /browser bootstrap: AppServerModule
[Link]('*.*', [Link](distFolder, { }));
maxAge: '1y'
}));
929
Exercice
Dans votre AppComponent, ajouter une phrase affichant le nombre d’accès
à votre application et qui devra s’incrémenter à chaque chargement de votre
application.
Commencez par tester votre application en mode standard.
Lancez ensuite votre application en mode SSR.
Que remarquez vous et quelle est votre interprétation ?
Certaines versions d'Angular ne permettent pas la vérification du
résultat SSR dans votre navigateur. Malgré que le ssr fonctionne côté
serveur.
Afin de vérifier ca, utilisez curl en tapant pour notre cas :
curl [Link] > [Link]
930
Server Side Rendering
Angular Universal
Les API du Browser
Etant donnée que vous utilisez un serveur afin de lancer votre
application, il ne peut pas accéder aux API du Navigateur, telles que
Window, Location ou localstorage.
Ceci implique qu’il ne faut pas utiliser ces objets pour le rendu
serveur.
Afin de gérer ce problème, il faudra injecter le token PLATFORM_ID
pour savoir si vous êtes dans un rendu serveur ou navigateur;
Il existe deux helpers qui prenne en paramètre le platform_id pour
identifier votre contexte :
isPlatformBrowser
isPlatformServer
931
Server Side Rendering
Angular Universal
Les API du Browser
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Inject, PLATFORM_ID } from '@angular/core';
constructor(
@Inject(PLATFORM_ID) private platformId: Object
) {}
ngOnInit() {
if (isPlatformBrowser([Link])) {
// code navigateur navigateur
}
if (isPlatformServer([Link])) {
// code serveur
}
}
[Link] 932
Exercice
Faite le nécessaire pour résoudre le problème avec le localstorage
933
Server Side Rendering
Angular Universal
Les appels doubles des API (Serveur / Client)
Lorsque vous utilisez Angular Universal pour le chargement de votre
page, si cette dernière nécessite des données d’une API, un premier
appel sera fait au niveau serveur.
Ensuite, lorsque l’application Angular sera chargé, un second appel
sera effectué. Nous identifions ici deux problème :
Si vous avez une connexion un peu lente (3G pour mobile par
exemple), l’utilisateur remarquera un aspect de flickering. L’interface
se rechargera avec la liste récupérée du second appel.
Le double appel de l’API.
[Link] 934
Server Side Rendering
Angular Universal
Le TransferHttpCacheModule
La première solution que vous pouvez utiliser est le
TransferHttpCacheModule.
TransferHttpCacheModule est un module utilisé dans les applications
Angular pour éviter les requêtes HTTP GET dupliquées côté client.
Il installe un intercepteur HTTP qui intercepte les requêtes HTTP
GET effectuées par le HttpClient côté client.
L'intercepteur. vérifie si la même demande a déjà été faite
lorsque l'application a été rendue côté serveur
Si la demande a déjà été faite, l'intercepteur extrait la réponse du store
clé-valeur TransferState.
[Link] 935
Server Side Rendering
Angular Universal
Le TransferHttpCacheModule
La réponse est ensuite transférée côté client, où elle est utilisée pour répondre
aux mêmes requêtes HttpClient côté client.
Cela évite les requêtes HTTP GET dupliquées côté client, ce qui peut
améliorer les performances de l'application.
Il est important de noter que cela ne fonctionne que pour les requêtes HTTP
GET et non pour les autres types de requêtes.
Pour utiliser le module TransferHttpCacheModule, vous devez l’importer dans
votre AppModule. import { TransferHttpCacheModule } from "@nguniversal/common";
@NgModule({ imports: [
BrowserModule,
TransferHttpCacheModule,
],})
export class AppModule {}
[Link] 936
Server Side Rendering
Angular Universal
L’API TransferState
La deuxième méthode pour gérer ce problème, est de manuellement
utiliser l'API TransferState.
Ceci peut être utile pour gérer l’incapacité du
TransferHttpCacheModule de gérer les méthodes non POST ou de
personnaliser le comportement selon votre besoin
L’API nous fournit un conteneur de stockage pour transférer
facilement des données entre le serveur et l'application cliente,
évitant ainsi à l'application cliente d'avoir à contacter le serveur pour
obtenir les données.
[Link] 937
Server Side Rendering
Angular Universal
L’API TransferState
Dans l’application cliente et afin de l’utiliser, injecter le service
TransferState de @angular/core.
Sur le serveur, il est déjà inclus si la fonction renderApplication est
utilisée. Sinon, importez le module ServerTransferStateModule
pour rendre le TransferState disponible.
Les valeurs dans le store sont sérialisées/désérialisées à l'aide de
[Link]/[Link].
Seuls les booléens, numériques, chaîne, nuls et les objets non classe
seront sérialisés et désérialisés sans perte. const key = makeStateKey<any>("cvs");
La clé utilisée afin de gérer les données doit être de type StateKey
[Link] 938
Exercice
Utilisez l’objet TransferState afin de gérer le probléme de double
chargement de l’api de la liste des Cvs.
939
Server Side Rendering
Angular Universal
Prerender
Nous avons vu comment Angular Universal utiliser le serveur afin de
pré charger une route à la volée.
Ce n’est pas l’unique solution, Angular Universal nous permet de pré
charger une page, non à la volée, mais lors du Build. C’est le
Prerender.
Cela permet d'envoyer directement les pages rendus sans avoir à
effectuer aucun traitement (Comme le ferai un serveur standard pour
une page HTML).
Ceci permet d’être le plus performant possible. Cependant, les pages
générées restent statiques et non dynamiques.
940
Server Side Rendering
Angular Universal
Prerender
Afin de lancer ce mécanisme de prerender, Angular Universal nous a
préparé la commande pour.
Fermer vos serveur et lancer la commande npm run prerender
"scripts": {
"dev:ssr": "ng run ngUniversalStarting:serve-ssr",
"serve:ssr": "node dist/ngUniversalStarting/server/[Link]",
"build:ssr": "ng build && ng run ngUniversalStarting:server",
"prerender": "ng run ngUniversalStarting:prerender",
},
941
Server Side Rendering
Angular Universal
Prerender - option
La fonction prerender permet de charger les routes statiques.
Afin de générer les routes dynamiques ajouter l’option --routes suivi
de la route souhaitée.
"prerender": "ng run ngUniversalStarting:prerender --routes 'cv/1' --routes 'cv/2' ",
Vous pouvez aussi lui spécifier quelles routes vous voulez prérendrer
"prerender": "ng run ngUniversalStarting:prerender --no-guess-routes --routes /home /cv",
[Link] 942
Angular Material
Angular Material est une librairie permettant d'intégrer et de
personnaliser des composants Angular en suivant le Material Design.
Material Design est un système de conception conçu et pris en charge
par les concepteurs et les développeurs de Google.
[Link] inclut des conseils UX approfondis et des implémentations
de composants d'interface utilisateur pour Android, Flutter et le Web.
Afin d’installer Angular Material lancer la commande :
ng add @angular/material
Vous aurez à choisir le thème que vous voulez utiliser dans votre
projet puis à spécifiez si vous souhaitez inclure les animations.
[Link] 943
Angular Material
Les éléments du Formulaire
Le composant <mat-form-field>
Pour fonctionner, plusieurs composants d’Angular material ont
besoin du composant <mat-form-field> qui est utilisé comme
wrapper.
Les composants Material concernés sont :
les input
les select
Pour l’utiliser il suffit de l’importer depuis la librairie Material :
import {MatFormFieldModule} from '@angular/material/form-field';
[Link] 944
Angular Material
Les éléments du Formulaire
Voici quelques-uns des attributs
importants de `mat-form-field` :
appearance : Cet attribut contrôle
l'apparence du champ de formulaire.
Les valeurs possibles sont "fill" par
défaut et "outline".
floatLabel : Cet attribut contrôle le
comportement de l'étiquette du
champ lorsqu'il est en focus ou
contient une valeur. Les valeurs
possibles sont "auto", "always" et
"never".
[Link] 945
Angular Material
Les éléments du Formulaire
<mat-hint> : Vous pouvez ajouter un texte d'indication (hint) pour
guider les utilisateurs lorsqu'ils remplissent le champ.
directive matTextPrefix et matTextSuffix : Vous pouvez ajouter un
préfixe/suffixe (par exemple, une icône) au champ de formulaire.
<mat-form-field floatLabel="auto">
<mat-label>You favorite food</mat-label>
imports:
<mat-hint>Une petite astuce</mat-hint>
[
<span matTextPrefix>
MatFormFieldModule,
<mat-icon> restaurant </mat-icon>
MatInputModule,
</span>
MatIconModule,
<input matInput />
];
</mat-form-field>
[Link] 946
Angular Material
Les éléments du Formulaire
Les Inputs
matInput est une directive qui permet aux éléments natifs <input>
et <textarea> de fonctionner avec <mat-form-field>.
Vous pouvez spécifiez les erreurs avec la balise mat-error
Le champ posséde plusieurs attributs:
placehoder
value
…
[Link] 947
Angular Material
Les éléments du Formulaire
Les checkbox
Pour utiliser le module Material pour les checkbox, il suffit de
l’importer puis d’appeler la balise <mat-checkbox> </mat-checkbox>.
import { MatCheckboxModule } from '@angular/material/checkbox';
La checkbox de Material supporte toutes les fonctionnalités d’HTML
5 et a la même API, e.g. checked, labelPosition, name
[Link] 948
Angular Material
Les éléments du Formulaire
Les select Listes <mat-select>
Afin de gérer les lists,
commencer par importer le
MatSelectModule
import { MatSelectModule }
from '@angular/material/select'; <mat-form-field>
<mat-label>Frameworks</mat-label>
mat-select doit vivre au sein <mat-select>
<mat-option value="angular">Angular</mat-option>
d’un mat-form-field <mat-option value="nest">Nest</mat-option>
<mat-option value="symfony">Symfony</mat-option>
Pour identifier les options de </mat-select>
votre liste utiliser <mat-option> </mat-form-field>
[Link] 949
Angular Material
Les éléments du Formulaire
Vous pouvez avoir un select multiple en ajoutant la propriété
multiple dans le mat-select.
<mat-form-field>
<mat-label>Frameworks</mat-label>
<mat-select multiple>
<mat-option value="angular">Angular</mat-option>
<mat-option value="nest">Nest</mat-option>
<mat-option value="symfony">Symfony</mat-option>
</mat-select>
</mat-form-field>
[Link] 950
Angular Material
Les boutons
Afin d’utiliser les boutons de Material, importer le MatButtonModule
import { MatButtonModule } from "@angular/material/button";
Material Propose plusieurs variantes de boutons
La propriété color permet de spécifier
La couleur
le bouton par défaut est fourni avec la
Directive mat-button
[Link] 951
Angular Material
Les boutons
<button
mat-raised-button
color="primary“
>
raised primary
</button>
[Link] 952
Angular Material
Les menus Material
Ce sont des panneaux flottants contenant une liste d’options fournies
par MatMenuModule
import {MatMenuModule} from '@angular/material/menu';
1. Créer d’abord un mat-menu comprenant plusieurs options qui sont
des boutons sur lesquels est ajouté l’attribut mat-menu-item
2. Récupérer une référence locale (via #) sur le menu auquel vous
assignez la directive matMenu.
<mat-menu #monMatMenu="matMenu">
<button mat-menu-item>Option 1</button>
<button mat-menu-item>Option 2</button>
</mat-menu>
[Link] 953
Angular Material
Les menus Material
Ce menu restera caché. Il faudra donc un élément pour l’ouvrir
comme un un bouton.
Sur cet élément, nous bindons la directive matMenuTriggerFor
auquelle nous passons la référence du menu.
<mat-menu #monMatMenu="matMenu">
<button mat-menu-item>Option 1</button>
<button mat-menu-item>Option 2</button>
</mat-menu>
<p [matMenuTriggerFor]="monMatMenu">Menu ouvre toi</p>
[Link] 954
Angular
Internationalisation
ngx translate
AYMEN SELLAOUTI
955
Ngx-Translate
Ngx-Translate est une bibliothèque de traduction pour angular.
[Link]
956
Ngx-Translate
Installation
Afin d’installer ngx-translate vous devez d’abord installer le core de cette
bibliothèque.
@ngx-translate/http-loader
[Link] 957
Ngx-Translate
Installation
Initialiser le module de traduction, TranslationModule
Le HttpLoaderFactory est requis pour la compilation AOT de votre projet.
[Link]({ L’instanciation du
defaultLanguage: 'fr',
loader: {
TranslateHttpLoader prend en
provide: TranslateLoader, paramètre le service
useFactory: HttpLoaderFactory, permettant de récupérer les
deps: [HttpClient],
},
fichiers de traduction, le
}), chemin vers les fichiers de
traduction et leur extension.
// AoT requires an exported function for factories Par défaut les valeurs sont :
export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http); './assets/i18n/', '.json'
} 958
Ngx-Translate
Utilisation
Dans le composant qui va se charger de la traduction et du changement de la
langue, injecter votre service.
constructor(public translateService: TranslateService) {
}
[Link] 959
Ngx-Translate
Utilisation
Ce service offre plusieurs fonctions vous permettant de gérer la langue.
setDefaultLang qui prend en paramètre la langue par défaut.
addLang qui prend en paramètre un tableau de langue de votre application.
getBrowserLang qui retourne la liste des langues définies.
use qui prend en paramètre la langue que vous voulez instaurer dans le site.
[Link] 960
Ngx-Translate
Les fichiers de traductions
Les fichiers de traduction sont par défaut sous le dossier ‘assets/i18n/.
Ils contiennent simplement un objet JSON de paires clé-valeur, où la clé décrit le
texte qui est traduit et la valeur est le texte réel dans la langue spécifiée par le fichier.
La valeur peut également être un autre objet, ce qui vous permet de regrouper vos
traductions comme vous le souhaitez.
Dans le texte de votre valeur de traduction, vous pouvez également inclure des
doubles accolades autour d'un nom de variable, ce qui vous permettra plus tard
d'interpoler des chaînes de manière dynamique dans vos traductions.
[Link] 961
Ngx-Translate
Les fichiers de traductions
{ {
"[Link]": "Translation Example", "[Link]": "Exemple de traduction",
"HOME" : { "HOME": {
"welcomeMessage": "Thanks for joining, {{ fir "welcomeMessage": "Nous vous remercions pour votre prés
stName }}! It's great to have you!", ence, {{ firstName }}! C'est un plaisir de vous avoir avec nous!",
"login": { "login": {
"username": "Enter your user name", "username": "Veuillez saisir votre nom d'utilisateur",
"password": "Password here" "password": "Votre mot de passe"
} }
} }
} }
[Link]
[Link]
Les parties variables de vos messages doivent être interpolé et passé en paramètres
lorsque vous aller utiliser la traduction.
[Link] 962
Utiliser la traduction
Maintenant que tout est en place, il faudra voir comment utiliser cette traduction.
Afin de spécifier les éléments traductibles, vous avez deux méthodes. Les pipes, et
les directives.
<h1 [translate]="'[Link]'"></h1>
<h2 translate="">[Link]</h2>
<input type="text" placeholder="{{ '[Link]' | translate }}" />
Voici comment passer des paramètres avec les deux méthodes.
[Link] 963
Exercice
Traduisez les pages des CVs
964
Angular internationalization i18n
La team Angular a choisi d’implémenter un système
d’internationalisation au moment du Build.
Au lieu de faire une traduction au runtime, votre projet
sera buildé en plusieurs langues. Vous aurez donc un build
par langue.
Pour implémenter ce mécanisme, commencer par ajouter le
package @angular/localize.
ng add @angular/localize
[Link] 965
Angular internationalization i18n
Spécifier les langues de traductions
Une fois le package installé, nous devons mettre en place notre
système pour l’internationalisation
1. La première étape consiste à modifier le fichier [Link] afin de
spécifier quelles locales seront supportées. Aller sur projects ->
nomVotreApp et ajouter une clé i18n.
Cette clé prend 2 paramètres:
sourceLocale: la locale par défaut
locales : objet des autres locales avec
comme clé la locale et comme valeur un objet d’options ou le chemin du
fichier de traduction
966
Angular internationalization i18n
Spécifier les langues de traductions
Pour la partie serve et étant donner que vous ne pouvez servir qu’une seule
langue vous devez définir les configuration des langues une par une.
Allez dans la partie [Link] et ajouter la configuration de la langue.
Ensuite, dans la partie [Link] définissez un script et passez lui la clé de
la langue que vous avez définie
Maintenant pour lancer votre serveur dans la langue souhaitée ajouter
ng serve –configuration=votreLocale
"build": { "serve": {
"configurations": { "builder": "@angular-devkit/build-angular:dev-server",
"en": { "configurations": {
"localize": ["en-US"] "en": {
}, "browserTarget": "materialExamples:build:development,en"
}
[Link] 967
Angular internationalization i18n
Spécifier les langues de traductions
Afin de builder votre application avec les différentes locales vous
allez dans build->options et mettez la propriété localize à true ce qui
vous oblige à générer la configuration serve pour toutes les langues.
Sinon enlevez le localize à true et lancez ng build --localize
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"localize": true,
968
Angular internationalization i18n
Spécifier les éléments à traduire
2. La deuxième étape est de définir quels éléments traduire
Utiliser l’attribute i18n afin de spécifier qu’un élément textuel du
template doit être traduit.
L'attribut i18n prend une chaîne de caractères optionnelles qui va
permettre de paramétrer la traduction du texte.
Cette chaîne est un contexte comportant une signification et un
descriptif pour aider le traducteur. signification | descriptif@@Id.
La signification permet de ne traduire la même chose qu’une seule fois.
<mat-hint i18n="hint | message pour spécifier une astuce">Une petite astuce</mat-hint>
969
Angular internationalization i18n
Génération des fichiers de traduction
3. La troisième étape est la génération des fichiers de traduction
Pour extraire les fichiers de langue source, ouvrez une fenêtre de
terminal, accédez au répertoire racine de votre projet d'application et
exécutez la commande CLI suivante : ng extract-i18n
La commande xi18n crée un fichier de langue source nommé
[Link] dans le répertoire racine de votre proje
--output-path : modifiez l'emplacement.
--format : modifier le format.
--outFile : modifier le nom du fichier.
970
Angular internationalization i18n
Génération des fichiers de traduction
Cette commande va générer un fichier [Link]
Les élément à traduire sont dans des balises trans-unit
La balise source représente l’élément à traduire
les balises context décrivent le contexte de l’élément à traduire
971
Angular internationalization i18n
Traduire les éléments
Une fois le fichier généré, pour chaque langue, créer un nouveau fichier
avec le même contenu du fichier [Link]
Par convention, il aura le nom [Link] => [Link]
Ensuite, dans chaque trans-unit créer une balise target qui contiendra la
traduction de la source
<trans-unit id="4972876516686899215" datatype="html">
<source>la chaine Techwall</source>
<target>Techwall</target>
<context-group purpose="location">
<context context-type="sourcefile">src/app/home/[Link]</context>
<context context-type="linenumber">7</context>
</context-group>
</trans-unit>
972
Exercice
Mettez en place le système de traduction pour avoir deux langues :
Français et Anglais
Faite en sorte d’avoir deux scripts, chacun lançant la langue associée.
Testez la trauction d’un premier composant
973
Angular internationalization i18n
Spécifier les éléments à traduire
2. La deuxième étape est de définir quels éléments traduire
Afin de traduire un attribut, il faut préfixer le nom de l’attribut par i18n-
Par exemple, pour traduire l’attribut title il faut taguer la balise avec
i18n-title à laquelle vous pouvez lui passez la même chaine que pour le
i18n.
<a
href="[Link]
title="Go To Google"
i18n-title
>google</a>
974
Exercice
Ajoutez une image dans votre composant et traduisez les attributs alt
et title
975
Angular internationalization i18n
Spécifier les éléments à traduire
La traduction des pluriels
Les langues ont des constructions grammaticales très différentes. Par exemple 2
minutes ago se transforme en Il y a 2 minutes.
Souvent vous voudrez afficher différentes phrases suivant qu'il y ait zéro, une ou
plusieurs unités d'un objet considéré.
Pour cela, il suffit d'utiliser une variable contenant le nombre d'unités.
Par exemple, ici nous avons une variable minutes contenant le nombre de minutes
écoulées depuis la mise à jour d'un produit :
<span i18n>
Mis à jour {minutes, plural, =0 {maintenant} =1 {il y a une minute} other { il y a {{minutes}} minutes }}
</span>
[Link] 976
Angular internationalization i18n
Spécifier les éléments à traduire
La traduction des select
Pour les selects, vous pouvez utilisez une syntaxe qui ressemble à celle des plural.
Ouvrez les {, spécifiez votre propriété à traduire, ensuite le mot clé select ensuite
pour chaque élément, le nom puis {valeur}.
<span i18n>
Vous préférez un contrat {duration, select, long {long} short {short} indeterminate {indeterminate}}
</span>
[Link] 977
Angular internationalization i18n
Traduire les éléments
Pour les plural et les select vous allez avoir une syntaxe ressemblante à
ce que vous avez défini dans la partie html
978
Angular internationalization i18n
Spécifier les éléments à traduire
La traduction des attributs côté TS
$localize() est une fonction globale d'Angular. Vous n'avez donc pas à
l'importer.
Elle permet de spécifier à Angular que vous voulez traduire un attribut
dans vos classes TS.
title = $localize `Je suis Aymen`;
i18n va en fait extraire le texte à traduire et le transformer en unité et
traduction et également va remplacer l'attribut par un gabarit étiqueté
avec la fonction $localize qui va permettre la traduction des chaînes de
caractères.
[Link] 979
Exercice
Ajoutez une variable dans votre TS et traduisez la via $localize
980
Angular internationalization i18n
Génération des fichiers de traduction
Lorsque un même élément est répété dans plusieurs parties le contexte
identifiera la totalité de ces éléments.
<trans-unit id="tecwallchanel" datatype="html">
<source>Le mur des technologies</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/home/[Link]</context>
<context context-type="linenumber">4</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/home/[Link]</context>
<context context-type="linenumber">8</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/home/[Link]</context>
<context context-type="linenumber">13</context>
</context-group>
</trans-unit> 981
Angular internationalization i18n
Configuration du serveur
Voici un exemple simple avec express:
const express = require("express");
const path = require("path");
const app = express();
[Link]( [Link]([Link](__dirname, "../front/dist/front"), { index: false }));
[Link]((req, res, next) => {
if ([Link]("/en") || [Link]("/fr")) { next(); } else { [Link](`/en${[Link]}`);}
});
[Link]("/*", (req, res) => {
if ([Link]("/en")) {
[Link]([Link](__dirname, "../front/dist/front/en/[Link]"));
} else {
[Link]([Link](__dirname, "../front/dist/front/fr/[Link]"));
}
});
[Link](3000);
[Link] 982
Angular internationalization i18n
Configuration du serveur
Le déploiement typique de plusieurs langues sert chaque langue à partir
d'un sous-répertoire différent (Les différents build par langage).
Pour changer la langue, changez votre emplacement actuel vers un autre
sous-répertoire.
Le changement de sous-répertoire s'effectue souvent à l'aide d'un menu
implémenté dans l'application.
La configuration dépendra du serveur dans lequel vous déployez votre
application.
[Link] 983
9
8
4
985
Control flow
@if
@if est associé à une expression booléenne. Si cette expression est
fausse (false) alors l'élément et son contenu sont retirés du DOM, ou
jamais ajoutés).
@if(condition)
Si le booléen est true alors l’élément host est visible.
Si le booléen est false alors l’élément host est caché.
<button @if ([Link]()) {
*ngIf="[Link]()" <button
(click)="deleteCv(cv)" (click)="deleteCv(cv)"
class="btn btn-danger"> class="btn btn-danger">
Delete Delete
</button> </button>
}
986
Control flow
@if
Si vous utilisez l’ancienne version *ngIf avec des standalone
component, vous êtes dans l’obligation d’importer la directive.
@If est nativement reconnu par le compilateur, vous n’avez pas
besoin de l’importer.
@Component({
standalone: true,
template: `<div
*ngIf="condition">Content</div>`,
imports: [NgIf],
})
export class MyComponent {}
987
Control flow
@if, @else if et @else
@if peut également être utilisé avec @else if et/ou @else selon le
besoin.
La syntaxe est très intuitive, demandé vous comment vous aurez fait
en Javasript et ajoutez un @ :D.
<div *ngIf="connectedUser; else dissconnectedMessage"> @if (connectedUser) {
Hello {{ [Link]}} Hello {{ [Link]}}
</div> } @else {
<div>Merci de vous connectez</div>
<ng-template #dissconnectedMessage> }
<div>Merci de vous connectez</div>
</ng-template>
988
Control flow
@if, @else if et @else
@if (!connectedUser) {
<div class="alert alert-danger">Merci de vous connectez</div>
} @else if (![Link]) {
<div class="alert alert-warning">
Hello {{ [Link] }}, merci d'activer votre compte
</div>
} @else {
<div class="alert alert-success">Hello {{ [Link] }}</div>
}
989
9
Control flow
9
0
@For
La directive structurelle @for permet de boucler sur un itérable et
d'injecter les éléments dans le DOM.
<ul>
<li *ngFor="let episode of episodes">{{ [Link] }}</li>
</ul>
<ul>
@for (episode of episodes; track [Link]) {
<li>{{ [Link] }}</li>
}
</ul>
Control flow <ul>
@For <li
*ngFor="
let episode of episodes;
@for fournit certaines informations sur la let i = index;
let isOdd = odd;
boucle en cours : let isFirst = first
"
$index: position de l'élément. [ngClass]="{ odd: isOdd, bgfonce: isFirst }"
>
Episode {{ i + 1 }}{{ [Link] }}
$odd: true si l'élément est à une position impaire. </li>
</ul>
$even: true si l'élément est à une position paire. <ul>
@for (episode of episodes; track [Link]) {
$first: true si l'élément est à la première position. <li
[ngClass]="{ odd: $odd, bgfonce: $first }"
>
$last: true si l'élément est à la dernière position. Episode {{ $index + 1 }} : {{ [Link] }}
</li>
}
</ul>
991
Control flow 9
9
@For
2
track
Avec @For la fonction de tracking est devenue obligatoire
La fonction de suivi créée via l'instruction track est utilisée pour
permettre au mécanisme de détection des changements
d’Angular de savoir exactement 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.
@for (episode of episodes; track [Link]) {
<li>{{ [Link] }}</li>
}
Control flow 9
9
@For
3
track
En principe, il devrait toujours y avoir quelque chose d'unique dans
les éléments sur lesquels vous itérer.
Dans le pire des cas, s'il n'y a rien d'unique dans les éléments du
tableau, vous pouvez utiliser $index de l'élément, c'est-à-dire la
position de l'élément dans le tableau.
@For
4
[Link]
9
Control flow
9
5
Control flow
9
6
@switch
<div [ngSwitch]="streamingService">
Avec @switch, vous pouvez créer <div *ngSwitchCase="'AppleTV'">Ted Lasso</div>
<div *ngSwitchCase="'Disney+'">Mandalorian</div>
des switch très simplement. <div *ngSwitchDefault>Peaky Blinders</div>
</div>
Vous avez les trois opérateurs : @switch(streamingService) {
@case ('Disney+') {
@switch, @case, @default <div>'Mandalorian'</div>
} @case ('AppleTV') {
@switch : définir l’élément sur <div>'Ted Lasso'</div>
} @default {
lequel switcher <div>'Peaky Blinders'</div>
}
@case : pour identifier le cas }
Control flow
9
7
Migration automatique
Si vous voulez migrer votre ancien code et utilisez le nouveau work
flow, angular vous fournie une commande qui le fait pour vous :
ng g @angular/core:control-flow
Cette fonctionnalité est encore en developer Preview (Anguar 17.2)
9
Optimisation
9
8
@defer
Dans cette partie nous allons voir les éléments suivants :
Dans quels cas d’utilisation nous aurons besoin de defer
Comment utiliser @defer
Quels sont les blocks offerts avec @defer : @placeholder, @loading,
@error
Qu’est ce qu’un trigger
Quels sont les triggers offerts par Angular
Comment créer son propre trigger
Optimisation 9
9
@defer
9
Optimisation
0
0
@defer
@defer est une syntaxe des templates Angular, qui vous permet de
charger des parties d'un modèle uniquement lorsqu'elles sont
nécessaires, compte tenu d'une condition logique.
La syntaxe @defer nous permet d'implémenter des cas d'utilisation
courants tels que :
ne charge un composant volumineux qu'une fois que l'utilisateur a fait
défiler la page après un certain point
charger un composant volumineux uniquement après que l'utilisateur ait
cliqué sur un bouton
précharger un composant volumineux en arrière-plan pendant que
l'utilisateur lit la page, afin qu'il soit prêt au moment où l'utilisateur clique
sur un bouton
1
0
Optimisation
0
1
@defer
@defer offre deux niveaux de control:
Quand pré charger le bundle de la partie à différer
Quand la section pré chargé sera affichée à l’utilisateur
Pour que les dépendances au sein d'un bloc @defer soient différées, elles doivent
remplir deux conditions :
Elles doivent être autonomes (standalones). Les dépendances non autonomes
ne peuvent pas être différées et seront toujours chargées au sein du bundle principal,
même à l'intérieur des blocs @defer.
Ils ne doivent pas être directement référencés depuis le même fichier, en
dehors des blocs @defer ; cela inclut les requêtes ViewChild.
1
0
Optimisation
0
2
@defer
Afin d’appliquer @defer sur un bloc, il suffit de
l’appeler dans votre template afin d’englober la <p>defer examples!</p>
partie concernée. @defer {
<app-huge/>
Ceci permet la création d’un bundle spécifique }
pour toute cette partie
Le nouveau bundle supplémentaire contenant le
HugeComposant ne sera chargé que lorsque le
bloc @defer sera déclenché.
Ici, nous n'avons spécifié aucun déclencheur
pour le bloc @defer, il sera donc déclenché par
défaut lorsque le navigateur est inactif (idle).
Le navigateur est considéré comme inactif
lorsque toutes les ressources de la page ont
fini de se charger.
Optimisation 1
0
0
@defer 3
@placeholder
Parfois, nous souhaitons simplement afficher un espace vide à l’endroit où est placé notre bloc
@defer.
Cependant, ceci peut avoir un comportement perturbant pour l’utilisateur, nous souhaitons donc
afficher du contenu initial à l'utilisateur, qui sera ensuite remplacé par le code chargé via le
bloc @defer.
Nous pouvons le faire en encapsulant le contenu initial que nous souhaitons afficher dans un bloc
@placeholder.
Chaque composant, directive ou pipe introduite dans le bloc @placeholder sera chargé dans le
bundle principal.
@defer {
<app-huge/>
}
@placeholder {
<h2>Nous chargeons le Huge Component merci de nous attendre :)</h2>
}
Optimisation 1
0
0
@defer 4
@placeholder
Comme vous l’avez remarqué, et selon l’état de la connexion, l’utilisation du @placeholder peut
causer un effet de vacillement (flickering) qui est très désagréable pour l’utilisateur et provoque
donc une mauvaise expérience utilisateur.
Afin de gérer ca, nous pouvons utiliser le paramètre minimum que nous pouvons passer à
@placeholder.
Il permet de spécifier sa durée minimale d’affichage indépendamment du chargement du bloc
différé.
@defer {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>Je suis la en attendant le vrai component:)</h2>
}
Optimisation 1
0
0
@defer 5
@loading
Le bloc @loading est utilisé pour afficher du contenu pendant que
<p>defer works!</p>
le bloc @defer charge toujours son bundle Javascript en arrière-
<p>defer examples!</p>
plan.
@defer {
Le contenu du block @loading sera rendu uniquement pendant le <app-huge/>
chargement du bundle app-huge. Une fois le chargement }
terminé, le message sera supprimé de la page et le app-huge sera @placeholder(minimum 1s) {
affiché à sa place. <h2>Placeholder de AppHuge</h2>
}
Le bloc @loading accepte deux paramètres facultatifs; minimum et @loading(minimum 1s; after 500ms){
after : <h3>AppHuge is loading.....</h3>
minimum est utilisé pour spécifier la durée minimale pendant }
laquelle le bloc @loading sera affiché à l'utilisateur.
after est utilisé pour spécifier le temps que nous devons
attendre avant d'afficher l'indicateur @loading après le démarrage
du processus de chargement.
Optimisation 1
0
0
@defer 6
@error
Le chargement différé peut échouer pour une raison ou une autre ( problème réseau, bug, …)
Afin de gérer ca vous pouvez utiliser le bloc @error
<p>defer works!</p>
<p>defer examples!</p>
@defer {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>Nous chargeons le Huge Component merci de nous attendre :)</h2>
}
@loading (minimum 1s; after 500ms) {
<h3>loading.....</h3>
}
@error {
<h2>Un problème est survenu, nous ne pouvons pas charger le Huge Component :'</h2>
}
Optimisation 1
0
0
@defer 7
Les déclencheurs
Comme mentionné au début, @defer a deux niveaux de contrôle, chacun avec son déclencheur :
le déclencheur facultatif de prefetch, qui contrôle le moment où le bundle est chargé depuis le
backend,
le déclencheur facultatif @defer, qui contrôle le moment où le bloc @defer est affiché à
l'utilisateur.
Rappelez-vous, ce sont deux événements très différents, et nous pouvons les contrôler séparément
et prendre en charge toutes sortes de cas d'utilisation avancés.
Lorsqu'il s'agit de choisir le bon déclencheur, nous avons deux options disponibles :
Utiliser des déclencheurs prédéfinis, qui couvrent tous les cas d'utilisation les plus courants
Définir nos déclencheurs personnalisés, si nécessaire.
Le mot-clé on est utilisé pour les déclencheurs prédéfinis, tandis que le mot clé when est utilisé pour
les déclencheurs personnalisés.
idle est le déclencheur par défaut pour le chargement et l’affichage.
@defer(on idle ; prefetch on idle) {
<app-huge/>
}
Optimisation 1
0
0
@defer 8
Les déclencheurs
Angular offre 6 déclencheurs:
idle : déclenchera le chargement différé une fois que le navigateur aura atteint un état
d'inactivité (détecté à l'aide de l'API requestIdleCallback). C'est le comportement par défaut.
viewport : déclenchera le bloc différé lorsque le contenu spécifié entre dans la fenêtre en
utilisant le IntersectionObservateur API. Il peut s'agir du contenu @placeholder ou d'une
référence d'élément.
interaction : déclenchera le blocage différé lorsque l'utilisateur interagit avec l'élément
spécifié via des événements de clic ou de keydown.
hover : déclenchera le blocage différé lorsque l'utilisateur va entrer dans l’élément spécifié,
les événements pris en considération ici sont mouseenter et focusin.
immediate : déclenche immédiatement le chargement différé, ce qui signifie qu'une fois que le
client a terminé le rendu, le morceau (chunk) différé commencerait alors à être récupéré
immédiatement.
timer : se déclencherait après une durée spécifiée. La durée est obligatoire et peut être spécifiée
en ms ou s.
Optimisation 1
0
0
@defer 9
viewport
viewport : déclenchera le bloc différé @defer(on viewport) {
lorsque le contenu spécifié entre dans <app-huge/>
la fenêtre en utilisant le }
IntersectionObservateur API. Il peut @placeholder(minimum 1s) {
s'agir du contenu @placeholder ou d'une <h2>
référence d'élément. Je suis la en attendant le vrai component:)
</h2>
Vous pouvez aussi spécifiez la référence }
d’un élément de votre template qui sera
le déclencheur lorsqu’on l’atteindra. <p #deferTriggerElement>defer examples!</p>
@defer(on viewport(deferTriggerElement)) {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>
Je suis la en attendant le vrai component:)
</h2>
}
Optimisation 1
0
1
@defer 0
interaction
interaction : déclenchera le blocage @defer(on interaction) {
différé lorsque l'utilisateur interagit <app-huge/>
avec l'élément spécifié via des }
événements de clic ou de keydown. @placeholder(minimum 1s) {
<h2>
Vous pouvez aussi spécifiez la référence Je suis la en attendant le vrai component:)
d’un élément de votre template qui sera </h2>
le déclencheur lorsqu’on interagie }
avec.
<p #deferTriggerElement>defer trigger</p>
@defer(on interaction(deferTriggerElement)) {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>
Je suis la en attendant le vrai component:)
</h2>
}
Optimisation 1
0
1
@defer 1
hover
hover : déclenchera le blocage différé @defer(on hover) {
lorsque l'utilisateur va entrer dans <app-huge/>
l’élément spécifié, les événements pris }
en considération ici sont mouseenter @placeholder(minimum 1s) {
et focusin. <h2>
Je suis la en attendant le vrai component:)
Vous pouvez aussi spécifiez la référence </h2>
d’un élément de votre template qui sera }
le déclencheur lorsqu’on y entre.
<p #deferTriggerElement>defer trigger</p>
@defer(on hover(deferTriggerElement)) {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>
Je suis la en attendant le vrai component:)
</h2>
}
Optimisation 1
0
1
@defer 2
@defer 3
@defer 4
Prefetching
@defer vous permet de spécifier les conditions dans lesquelles le préchargement des chunks des
dépendances doit être déclenchée.
Vous pouvez utiliser un mot-clé prefetch.
La syntaxe de prefetch fonctionne de manière similaire aux principales conditions de defer et
accepte when et/ou on pour déclarer le déclencheur.
Comme nous l’avons mentionné avant, ceci lorsqu’on associe when et on à defer, nous
contrôlons le rendu
Lorsqu’on associe prefetch avec on et when, nous contrôlons quand récupérer les ressources.
Cela permet des comportements plus avancés, tels que vous permettre de commencer à pré-
extraire des ressources avant qu'un utilisateur n'ait réellement vu ou interagi avec un bloc différé,
mais pourrait bientôt interagir avec lui, rendant les ressources disponibles plus rapidement.
Optimisation 1
0
1
@defer 5
Prefetching
<input type="text" #myInput (keyup)="[Link] > 4 ? loadDeffered
= true : loadDeffered = false" class="form-control">
@defer(on hover; prefetch when loadDeffered) {
<app-huge/>
}
@placeholder(minimum 1s) {
<h2>Je suis la en attendant le vrai component:)</h2>
}
@loading (minimum 1s; after 500ms) {
<h3>loading.....</h3>
}
[Link]@[Link]
1016
Best practices for writing actions in NgRx include initially writing actions before feature development to define functionality scope, categorizing actions by event source for clarity, and defining actions near the features they influence to maintain organization. Encouraging ample actions ensures clear expression of application flows. When grouped using ActionGroup, actions share a source context, aiding in maintainability and consistency across the application .
FormArray in Angular's Reactive Forms allows handling of dynamic form controls where the number of form controls is not predefined. Unlike FormGroup, which requires knowing controls in advance, FormArray lets developers dynamically add or remove FormControl instances in response to user interaction. Each FormControl is indexed numerically, and users can reference these controls using an appropriate getter method to perform operations like validations or updates .
NgRx Data abstracts over the NgRx store, effects, and entities, reducing the boilerplate required for state management. It provides default implementations based on conventions, freeing developers from manually creating actions, reducers, and effects for common CRUD operations. This simplicity comes at the cost of losing explicit control, requiring different handling for non-entity data, but significantly eases management of entity collections .
NavigationExtras in Angular provide a way to pass additional information during navigation. It is used as the second parameter in the router's navigate method, allowing dynamic data passage through the router. This method involves passing an object of type NavigationExtras, which contains properties like state whereby dynamic information, such as IDs, can be passed and later retrieved in the target component using the router's getCurrentNavigation() method .
NgRx Entity addresses the complexity and redundancy in managing state related to a collection of entities. It provides abstractions to standardize operations such as CRUD, significantly reducing boilerplate code. By utilizing a map structure for entities, it enhances efficiency in access and updates, contrasting with traditional array-based management which can be cumbersome for common operations like entity selection by ID .
Runtime checks in NgRx enforce rules on state and actions to prevent common errors, improving application stability. Enabled default checks include strict state and action immutability, ensuring that neither the state nor actions are directly mutated, thus maintaining data integrity and predictability. Optional configurations allow further checks like serialization, which monitors that state and actions are serializable, crucial for correct replay and debugging. These checks catch errors early, guiding adherence to best practices .
Angular handles changes in route parameters by using the Observable property params of the ActivatedRoute, which allows the component to dynamically react to parameter changes without recreating the component instance. When route parameters change, Angular does not call the constructor or ngOnInit again; instead, applications should subscribe to the params Observable to capture any parameter changes .
Using <a href> for routing in a Single Page Application (SPA) with Angular can lead to full page reloads, which are undesirable in SPAs because they disrupt the seamless user experience by reloading the entire page content. Instead, Angular provides the routerLink directive as a more appropriate method for routing within SPAs. The routerLink does not cause a full page reload and allows for dynamic content loading without leaving the currently rendered page .
Pre-loading selective modules in Angular can be achieved with a custom preloading strategy. Instead of using PreloadAllModules, which loads all modules at once, a custom strategy class implements the PreloadingStrategy interface where the preload method determines preloading based on route data. Developers can return a load Observable for wanted routes or null for others, allowing fine-grained control over which modules are preloaded based on application needs .
Smart Components, also known as container components, handle data fetching and complex operations, while Presentational Components, or pure components, focus on rendering data and UI elements without concern for their data source. Presentational components receive data through inputs and communicate user actions through outputs, promoting reusability and separation of concerns .