Angular
Concepts avancés v2
Angular rappel
@loading
Note: Databinding, Modules, Components, Directives, Pip...
NODEJS >= 14.15.5
Angular CLI >= 13.3.3
Commande git
Les pré-requis :
Utiliser la CLI
Pour:
Le développement et la maintenance.
La création de vos composants, directives, pipes `ng g`
La vérification de la qualité de code `ng lint`
*Toujours rester à la racine de son projet.
VS
=
JavaScript & JScript
Principales fonctionnalités ES6
Template strings :
function hello(firstName, lastName) {
return `Good morning ${firstName} ${lastName}!
How are you?`;
}
console.log(hello('Jan', 'Kowalski'));
// display: Good morning Jan Kowalski!\nHow are you?
` backquote
Liste d'arguments : (Ellipses)
function sort(array, ... options) {
console.log('I\'m going to sort the array',
array, options);
}
sort([1, 2, 3]);
sort([1, 2, 3], 'descending');
// éclater un tableau :
sort([1, 2, 3], ... ['descending', 'blabla']);
// = sort([1, 2, 3], 'descending', 'blabla');
[... ['1', '2'], ... ['3', '4']] // = ['1', '2', '3', '4']
Principales fonctionnalités ES6
Queries : (Séléction d'élements dans le DOM)
const list1 = document.querySelectorAll('.className');
const list2 = document.querySelectorAll('div');
const list3 = document.querySelectorAll('[id]');
const list4 = document.querySelectorAll('[id=123]');
const firstMatchElement = document.querySelector('[id=123]');
list1.forEach(console.log) // doesn't work
[... list1].forEach(console.log) // works
Principales fonctionnalités DOM
L'échauffement
TP:
- Coder un petit script qui collecte dans un tableau le nom des films de la page du site web (lien ci-dessous).
?
Architecture standard d'une application Angular
// Tout ce qui va concerner les tests end to end Protractor
|- e2e/
|----- src/app.e2e-spec.ts
|----- src/app.po.ts
|----- tsconfig.json
|----- protractor.conf.js // équivalent à la configuration karma pour les tests e2e
// les dépendances avec npm
|- node_modules/
// l'endroit où les fichiers de build seront mis
|- dist/
...
|- src/
|----- app/ // ici ca va coder sec !
|----- app.component.css|html|spec.ts|ts
|----- app.module.ts
|----- assets/ // Ressources
|----- environments/ // configurations d'environement
|----- environment.prod.ts|ts
|----- favicon.ico
|----- index.html
|----- main.ts
|----- polyfills.ts // Rétrocompatibilité
|----- styles.css
|----- test.ts
|----- tsconfig.app.json
|----- tsconfig.spec.json
|----- typings.d.ts
|- .gitignore // .gitignore près généré
|- karma.conf.js // configuration serveur de test
|- package.json // configuration principal
|- README.md
|- tsconfig.app.json // extends tsconfig.json
|- tsconfig.json // configuration compiler typescript
|- tslint.json // linter syntaxique
Architecture standard d'une application Angular
Accéder à une ressource depuis un fichier html.
<img src="assets/icons/socials/github.svg" />
Angular Module
Component,
Directive,
Pipe
Module
Declares
Service
Provides
Exports
Imports
Others
Modules
Components,
Directives,
Pipe,
Services
Note: Toujours penser à ses imports/exports
Échanger des données entre vue/composant
Le Data binding
Event binding : ( .. )
*Parenthèses
One-way :
Event binding : ( .. )
<button (click)="f()">Click me</button>
La fonction f est déclaré dans le composant.
Interpolation : {{ .. }}
*Rappel
*Accolades
One-way :
Interpolation : {{ .. }}
Property binding : [ .. ]
*Crochets
One-way (Property-binding) :
Property binding : [ .. ]
ngModel : [( .. )]
*Crochets Parenthèses
Two-way (NgModel) :
*Nécessite l'importation du module FormsModule
ngModel : [( .. )]
Décoration Input : @Input()
*@Input(Nom de la propriétée)
@Input (Récupération d'une variable via la propriété) :
Décoration Input : @Input()
Décoration Output : @Output()
*@Output(Nom de l'event)
Décoration Output : @Output()
@Output (Création d'events) :
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-card',
templateUrl: './card.component.html',
styleUrls: ['./card.component.css']
})
export class CardComponent implements OnInit {
@Input() id: string;
@Output() selected: EventEmitter<string> = new EventEmitter();
constructor() { }
ngOnInit(): void {
}
f() {
this.selected.emit(this.id);
}
}
Utilisation :
<app-card (selected)="x()">
</app-card>
Composants
Life cycle
Note: Ordre d'appel des implementations dans un composant.
Cycles de vie d'un composant
Lifecycle hooks :
Permettent d'appeler des callbacks à différents moments du cycle.
@Component(...)
export class MyComponent {
constructor() { }
ngOnChanges(records) { }
ngOnInit() { }
ngAfterContentInit() { }
ngAfterViewInit() { }
ngOnDestroy() { }
}
First Load
Composant life
View binding life
Caractéristiques composant
Lifecycle hooks :
import {Component, OnInit} from '@angular/core'
@Component()
export class myComponent implements OnInit{
constructor() {}
ngOnInit() { /* code à executer à l'initialisation */ }
}
En pratique, on utilise principalement ngOnInit() en addition au constructeur :
Nb : ne pas oublier d'importer et d'implémenter le hook.
https://angular.io/guide/lifecycle-hooks
Caractéristiques composant
Les fonctions de cycle de vie peuvent êtres asynchrones.
import {Component, OnInit} from '@angular/core'
@Component()
export class myComponent implements OnInit {
public logged: boolean = false;
constructor() {}
async ngOnInit() {
this.logged = await new Promise((success) => {
setTimeout(() => success(true), 1000);
});
}
}
Les Hooks d'Angular
TP:
- Implementer les hooks suivants:
1. onChanges
2. onInit
3. onDestroy
3. afterContentInit
4. afterViewInit
Dans chacun des hooks émettre un message en console puis visualiser le comportement final.
?
Tests Unitaires
2 + 2 = 4
ng test
Pourquoi tester ?
1. On écrit un test pour rendre votre application non régressive.
2. La spécification permet de vérifier que la relation d'entrée / sortie d'un cas donné c'est bien réalisée avec ou sans succès de manière à rendre la redondance visible.
3. Le test permet de statuer sur le succès ou sur l'échec d'une vérification.
Karma
Jasmine, framework de tests unitaires
Jasmine Cheatsheet
@angular/cli s'occupe de la configuration de Jasmine
Karma lance les fichiers de test jasmine
Tous les fichiers ayant l'extension .spec.ts
jasmine effectue les tests unitaires
Karma affiche les résultats
Mise en place
ng test
Tester un service
Structure d'une spec :
describe('ValueService', () => {
let service: ValueService;
beforeEach(() => { service = new ValueService(); });
it('#getValue should return real value', () => {
expect(service.getValue()).toBe('real value');
});
it('#getObservableValue should return value from observable',
(done: DoneFn) => {
service.getObservableValue().subscribe(value => {
expect(value).toEqual('observable value');
done();
});
});
it('#getPromiseValue should return value from a promise',
(done: DoneFn) => {
service.getPromiseValue().then(value => {
expect(value).toEqual('promise value');
done();
});
});
});
Valeur
Observable
Promesse
Les Tests Unitaires
TP:
- Supprimer tout les fichiers .spec.ts du projet.
- Créer un nouveau composant nommé IpComponant.
- Ajouter une variable ip = '127.0.0.1' dans le scope du composant.ts.
- Verifier que la variable ip correspond bien à un format attendu.
- Executer la commande `ng test`.
?
Jasmine Spying
// create `getValue` spy on an object representing the ValueService
const valueServiceSpy = jasmine.createSpyObj('ValueService', ['getValue']);
// set the value to return when the `getValue` spy is called.
valueServiceSpy.getValue.and.returnValue('stub value');
SpyObject :
SpyOn :
// create `getValue` spy on an originalObject
const valueServiceSpy = spyOn(originalObject, 'getValue');
// set the value to return when the `getValue` spy is called.
valueServiceSpy.getValue.and.returnValue('stub value');
Autres Spys :
spyOnAllFunctions(obj),
spyOnProperty(obj, propertyName) (*Props)
Le plus utilisé
it('#getValue should return stubbed value from a spy', () => {
const stubValue = 'stub value';
let valueServiceSpy = spyOn(monService, 'getValue');
valueServiceSpy.getValue.and.returnValue(stubValue);
expect(monService.getValue())
.toBe(stubValue, 'service returned stub value');
expect(valueServiceSpy.getValue.calls.count())
.toBe(1, 'spy method was called once');
expect(valueServiceSpy.getValue.calls.mostRecent().returnValue)
.toBe(stubValue);
});
Test du/des retour(s) d'une fonction éspionnée.
Les Tests Unitaires
TP:
- Dans le composant IpComponant ajouter une fonction nommée testIp qui retourne un Observable<string>, retourner une valeur factice pour le moment. (of(''))
- À l'aide d'un des Spy forcer la fonction testIp à retourner une string '127.0.0.1'.
- Tester l'appel de la fonction testIp et vérifier que la valeur de retour correspond bien à une adresse ip.
?
TestBed
TestBed est l'outil le plus important pour les tests avec Angular
Il crée pour nous dynamiquement un module de test qui simule un @NgModule
Permet aussi l'accès aux services fournies.
TestBed
TestBed.configureTestingModule( objet )
Fonction de création d'un module de test.
Reçoit un objet de métadonnées qui peut contenir presque toutes les propriétés d'un @NgModule
TestBed.inject( type )
Fonction qui permet la récuperation d'un service, directive ou composant déclaré.
Dans cet objet de métadonnées, on remplit le tableau providers des services qu'on veut tester ou mocker
let service: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({ providers: [ValueService] });
});
Puis on espionne ce service dans un test avec TestBed
it('should use ValueService', () => {
let service = TestBed.inject(ValueService);
let spyService = spyOn(service, 'getValue');
spyService.and.returnValue('...');
expect(monComposant.getValueFromService()).toEqual('...');
});
Quand on test un service avec une dépendance, on ajoute un mock à la liste des providers
Dans cet exemple, un objet ValueMockService
describe('ValueService', () => {
let service: ValueService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
// On force testBed a instancier ValueMockService en temp que ValueService
{ provide: ValueService, useClass: ValueMockService }
]
});
service = TestBed.inject(ValueService);
});
... // tests unitaires
});
HttpClientTestingModule
import { HttpClientTestingModule } from '@angular/common/http/testing';
Pour fournir à vos services un mock d'httpclientModule
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
HttpClientTestingModule
let service: MonService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule]
});
service = TestBed.inject(MonService);
});
it('should be get ok', (done: DoneFn) => {
let httpClient = TestBed.inject(HttpClient);
let spyHttpClient = spyOn(httpClient, 'get');
spyHttpClient.and.returnValue(of('testValue'));
service.testGet().subscribe((v) => {
expect(v).toBe('testValue');
done();
});
});
Simuler des retours API.
Les Tests Unitaires
TP:
- Dans le composant IpComponant ajouter une fonction nommée getIp qui retourne un Observable<string>, recupérer votre adresse ip via un service IpService qui va utiliser HttpClient.
- Avec TestBed créer un module de test et fournisais IpService qui importe HttpClientTestingModule.
- Simuler une valeur du HttpClient puis Tester l'appel de la fonction getIp et vérifier que la valeur de retour correspond bien à une adresse ip.
?
Tester le DOM d'un composant
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BannerComponent } from './banner.component';
describe('BannerComponent', () => {
let component: BannerComponent;
let fixture: ComponentFixture<BannerComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ BannerComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BannerComponent);
component = fixture.componentInstance;
fixture.detectChanges();// forcer l'affichage
});
it('should create', () => {
expect(component).toBeDefined();
});
});
Tester le DOM d'un composant
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ TestComponent, HighlightDirective]
})
.createComponent(TestComponent);
fixture.detectChanges(); // initial binding
});
it('should have skyblue <h2>', () => {
const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
const oneDirective: DebugElement = fixture.debugElement.query(By.directive(HighlightDirective));
const arrayOfElement = fixture.debugElement.queryAll(By.css('h2:not([highlight])'));
const bgColor = h2.style.backgroundColor;
expect(bgColor).toBe('skyblue');
});
3 Façons de séléctionner un ou plusieurs élements dans la vue.
Test debugging
- Ouvrir le navigateur de Karma
- Cliquer sur le bouton DEBUG
- Un onglet s'ouvre et Karma relance les tests
- Ouvrir les Outils Développeur du navigateur
- Ouvrir la section "Source"
- Ouvrir un fichier spec (Ctrl/Command-P)
- Appliquer un breakpoint dans les tests
- Rafraîchir le navigateur, il s'arrête sur un breakpoint
?
Les Tests Unitaires
TP:
- Configurer karma.conf pour que les tests ne sois lancés qu'une seul fois.
- Créer et inspecter de bout en bout un nouveau composant qui contient 3 div's avec du text de 3 couleurs css differentes.
- Configurer karma.conf pour tester l'application sur Firefox.
?
ng e2e
Tests E2E
E2E c'est quoi ?
Protractor un super-set de Selenium
E2E Fonctionnement
E2E Installation
Vous avez besoin d'un serveur selenium.
npm install webdriver-manager
E2E Éxécution
ng e2e
Vous avez un dossier e2e à la racine du projet.
./e2e
src/
app.e2e-spec.ts // fichier de description des tests
app.po.ts // fichier de selection d'élements
protractor.conf.js // configuration protractor
tsconfig.json
Protractor Helpers
$$() Permet de selectionner une liste d'élément
$() Permet de selectionner un seul élément
By.css, By.id, By.tagName (Génère une query)
ElementHelpher type associer à chaques éléments (permet de multiples actions sur les éléments)
element.click();
browser variable permettant la navigation et divers interaction sur le navigateur. (sleep, params)
Tests E2E
TP:
- Créer un test complet.
- Cliquer sur le bouton Card.
- Attendre le chargement de la page.
- Selectionner le premier élément de la liste et verifier les données affichés.
?
Les bonnes pratiques d'Angular
Les recommandations de longueur de code
Les fichiers doivent être limités à 400 lignes de code.
Créez des fonctions courtes avec pas plus de 75 lignes de code.
Utiliser ES6 à fond
let & const
Template strings `${maVariable}`.
Fonctions lambda () => { }
*ngFor
Appliquer le paramètre trackBy pour éviter les rechargements complet d'une liste.
// fichier mon.component.html
<div *ngFor='let book of books; trackBy:trackByBookCode'>
// fichier mon.component.ts
trackByBookCode(index: number, book: any): string {
return book.code;
}
Spécifier une fonction pour le trackage.
Écrire la fonction puis selectionner une clef de référence.
Style Guide
kebab-case
camelCase
snake_Case
PascalCase
Nommage des fichiers, selectors.
Nommage des variables, functions.
Non utilisé.
Nommage des classes.
Naming Style convention
Style Guide
obsName$
Nommage des variables de type Observable.
Naming Style convention
TsLint
ng lint
Linter TypeScript
Commande @angular/cli
Les Bonnes Pratiques
TP:
- Executer la commande du linter et corriger toutes les erreurs associers.
- Appliquer l'optimisation trackBy sur l'affichage de la liste de Cards. (page /cards)
?
Prettier
Clarifier son code sans rien faire.
Prettier
Installation et utilisation
npm install prettier
touch .prettierrc
{
"printWidth": 120,
"singleQuote": true,
"useTabs": false,
"tabWidth": 4,
"semi": true,
"bracketSpacing": true
}
./node_modules/prettier/bin-prettier.js --config ./.prettierrc --write "src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}"
Vous pouvez ajouter une commande spécifique à package.json pour vous faciliter la vie.
Architecture
ng g = votre meilleur ami
Façon de ranger votre projet.
./e2e
src/
admin/ // feature module
admin.module.ts
admin-routing.module.ts
admin-home/
admin-home.component.ts|html|css|spec.ts
home/ // composant page
home.component.ts|html|css|spec.ts
shared/ // module librairie
shared.module.ts
components/ // rangement pluriel
directives/ // rangement pluriel
pipes/ // rangement pluriel
app.module.ts
app-routing.module.ts
app.component.ts|html|css|spec.ts
Change detection : databinding optimization
ngZone
OnChange
ChangeDetectionStrategy.OnPush
ChangeDetectorRef
export class AppComponent {
mouseIsOnTopLeft: boolean = false;
constructor(private ngZone: NgZone) {
this.ngZone.runOutsideAngular(() => {
document.addEventListener("mousemove", this.handleMouseMove);
})
}
handleMouseMove(event) {
console.log(event);
if (!this.mouseIsOnTopLeft && event.x < 100 && event.y < 100) {
this.ngZone.run(() => {
this.mouseIsOnTopLeft = true;
});
} else if (this.mouseIsOnTopLeft) {
this.ngZone.run(() => {
this.mouseIsOnTopLeft = false;
});
}
}
}
Détection de changement
Un service qui permet d'exécuter des taches sans la contrainte de détection de changement d'angular.
ngZone
export class AppComponent {
constructor(private ref: ChangeDetectorRef) {
// detachement de la detection automatique
ref.detach();
// set interval d'une detection toutes les 5secondes
setInterval(() => { this.ref.detectChanges(); }, 5000);
}
}
Détection de changement
Instance du détecteur de changement du composant.
ChangeDetectorRef
Une possibilité d'avoir la main sur les changements de détections.
export class AppComponent implements OnInit, OnChanges {
@Input('src') src: string;
constructor() {
}
ngOnChanges(changes: SimpleChanges): void {
if (changes.src && changes.src.currentValue !== changes.src.previousValue) {
//todo task
}
}
ngOnInit() {
}
}
Détection de changement
L'implémentation OnChange permet de réaliser des effets de bords lors de changement d'un @Input.
OnChange
!!! Le déclenchement de OnChange se produit avant l'implementation OnInit
@Component({
selector: 'ng-emg',
templateUrl: './emg.component.html',
styleUrls: ['./emg.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmgComponent implements OnInit {
@Input("fleur") fleur: string;
}
Détection de changement
Une stratégie de détection de changement
ChangeDetectionStrategy.OnPush
Cette stratégie de changement déclenchera les détections uniquement lors des changements affectant directement sur le composant.
@Component({
selector: 'ng-emg',
templateUrl: './emg.component.html',
styleUrls: ['./emg.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EmgComponent implements OnInit {
@Input("fleur") fleur: string;
get detectionLogger() {
console.log('[EmgComponent] call of ChangeDetection');
return '';
}
}
Détection de changement
Technique pour visualizer les détections de changements dans un composant
...>
{{ detectionLogger }}
<...
Du coté de votre vue vous n'avez plus cas binder la variable de debug
Astuce
Détection
TP:
- Créer un composant TestDetection qui prend un Input
- Utiliser le composant TestDetection.
- Utiliser l'implementation OnChange dans le composant TestDetection.
- Liéer un événement à l'aide de ngZone hors d'angular.
- Forcer une détection toutes les 5 secondes à l'aide de ChangeDetectorRef
- Tester les stratégies de detections et constater les différences.
?
i18n
Internationalization
http://app.com/fr/
http://app.com/en/
http://app.com/pt/
http://app.com/es/
Route | Build
Pipe
&
Différentes Possibilités
- Application traduite dès la compilation (xi18n)
- Application traduite au runtime (ngrx-translate)
Angular xi18n simplifie certains aspects de l'internationalisation :
- Afficher des dates, des nombres et des devises dans un format local
- Préparer les textes dans les templates des composants pour la traduction
- Tradution AOT (traduction avant la compilation)
Xi18n
1. Definition des locales à enregister
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
// the second parameter 'fr' is optional
registerLocaleData(localeFr, 'fr');
Xi18n pipe Installation
@NgModule({
declarations: [...],
entryComponents: [...],
imports: [...],
providers: [{ provide: LOCALE_ID, useValue: 'fr-FR' }],
bootstrap: [AppComponent]
})
export class AppModule { }
2. Ajout de la locale à definir par défaut
Xi18n pipe Installation
*Les pipes vont utiliser l'injection de cette dépendence lors de leur initialisation
DatePipe
CurrencyPipe
DecimalPipe
PercentPipe
Xi18n Helpers
Liste des pipes que Xi18n implémente.
Exemple (FR = 1 000, EN = 1,000)
Xi18n pipes
TP:
- Utiliser currencyPipe, afficher le symbole Euro à droite d'un un grand nombre.
- Changer la configuration de la locale en anglais.
- Visualiser les différences d'affichages en fonction des locales.
?
3. Appliquer la directive i18n sur les tag à traduires.
<div>
<ul>
<li> <a i18n>English text</a></li>
</ul>
<div>
Xi18n Installation
4. Génération du fichier de traduction
ng extract-i18n --output-path locales --format json
$>: ls ./locales (master)formation-angular
total 8
-rw-r--r-- 1 jeremyguyet staff 87 28 avr 20:45 messages.json
-rw-r--r-- 1 jeremyguyet staff 87 28 avr 20:45 messages.fr.json
-rw-r--r-- 1 jeremyguyet staff 87 28 avr 20:45 messages.pt.json
-rw-r--r-- 1 jeremyguyet staff 87 28 avr 20:45 messages.es.json
Xi18n Installation
Format
Commande
Emplacement
Copier le fichier généré et traduiser sont contenu dans la langue choisi.
5. Ajouter la référence des fichiers sources dans votre fichier de configuration angular.json
...
"MonProjet": {
...,
"i18n": {
"sourceLocale": "en-US",
"locales": {
"fr": {
"translation": "locales/messages.fr.xlf"
},
"en": {
"translation": "locales/messages.xlf"
}
}
}
...
}
Xi18n Installation
6. Séléctionner une langue sur vos configurations d'environement.
"dev": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.dev.ts"
}
],
"localize": ["fr"]
},
Xi18n Installation
Vous pouvez maintenant lancer votre serveur avec cette configuration la langue sera automatiquement prise en compte.
Marquer un élément pour la traduction
<h1 i18n>Hello i18n!</h1>
Xi18n
Ajouter des informations au traducteur
<h1 i18n="An introduction header for this sample">Hello i18n!</h1>
Une description
<h1 i18n="site header|An introduction header for this sample">Hello i18n!</h1>
Le sens du texte
Xi18n
<h1 i18n="site header|An introduction header@@siteHeader">Hello i18n!</h1>
Un identifiant fixe
Créer un fichier de traduction
ng xi18n
Générer un build traduit
ng build --prod --i18n-file src/locale/messages.fr.xlf --i18n-format xlf --i18n-locale fr
Servir l'application traduite
ng serve --configuration=fr
Xi18n
*Vous pouvez créer une configuration spécifique pour chaques langues à l'aide de la variable : "localize": ["fr"]
Xi18n
TP:
- Ajouter la directive i18n sur plusieurs éléments html nécésitant une traduction.
- Créer un fichier de traduction pour le français et l'anglais.
- Démarrer votre application en spécifiant un fichier de traduction.
?
Manipulation du DOM
ElementRef
@ViewChild
@ContentChild
<ng-content></ng-content>
Projection de contenu
La projection de contenu est une syntaxe qu’on peut utiliser dans un template de composant et qui permet de réafficher le contenu situé entre les balises d’un composant.
La projection de contenu permet à un composant de réafficher le texte contenu entre ses balises dans son propre template, grâce à la balise
<ng-content></ng-content>
<p>Le temps est ensoleillé.</p>
<ng-content></ng-content>
Emplacement du contenu
<div class="main">
<div class="title">
<monComposant>...</monComposant>
</div>
</div>
Emplacement du contenu.
Template avec projection
Emplacement de la projection.
monComposant
@ViewChild : récupérer la référence
d'un Element, Composant ou d'une Directive.
depuis son template.
import {AfterContentInit, ViewChild, Directive, ElementRef} from '@angular/core';
// dans votre template la selection d'un element ref via id #text sur l'element
@Component({ selector: 'app-li', templateUrl: './app-li.component.ts', styleUrls: ['./app-li.component.css'] })
class SomeDir implements AfterViewInit {
/** Utilisation du décorateur ViewChild pour seletionner le child */
@ViewChild('text', { static: false }) viewChild : ElementRef;
constructor() { }
ngAfterViewInit() {
// viewChild is set
console.log('Je viens de me faire surcharger.', this.viewChild);
}
}
<div>
<p #text></p>
</div>
Utilisation dans un template :
*Possibilité de sélectionner des directives et des composants.
@ContentChild : récupérer la référence
d'un Element, Composant ou d'une Directive.
depuis son <>Content</>
import {AfterContentInit, ContentChild, Directive, ElementRef} from '@angular/core';
@Component({ selector: 'someDir', templateUrl: './toto.component.ts', styleUrls: ['./toto.component.css'] })
class SomeDir implements AfterContentInit {
/** Utilisation du décorateur ViewChild pour selectionner le child */
@ContentChild('toto', { static: false }) contentChild : ElementRef;
constructor() { }
ngAfterContentInit() {
// contentChild is set
console.log('Je viens de me faire surcharger.', this.contentChild);
}
}
<someDir>
<p #toto></p>
</someDir>
Utilisation dans un template :
*Possibilité de sélectionner des directives et des composants.
Manipulation du DOM
TP:
- Utiliser la projection de contenu :
projecter: <p>42</p>
- Utiliser @ContentChild trouver le tag <p>, ensuite verifier que le tag contient bien un text égal à 42.
- Utiliser @ViewChild et afficher en console le composant enfant.
Si vous avez de l'avance, utiliser ng-template.
?
Angular + Material
@angular/material
Note: Des composants et un kit de développement.
Qu’est-ce que Material ?
Note: En 2014 Google à conçu une suite Material Design utilisé sur Android depuis la version 5.
Qu’est-ce qu’Angular Material ?
Composants
Utilitaires
CDK
Theming
Component Dev Kit
Angular Material
Une bibliothèque de composants UI
- Contrôles de formulaire – sélection, entrée, curseurs, case à cocher, etc.
- Composants de mise en page : cartes, grilles, listes et onglets.
- Boutons
- Modèles de navigation – navigation latérale, menus et barre d’outils
- Tableaux de données avec en-têtes
- Modales et fenêtres contextuelles
- Indicateurs : spirales et barres de progression
Angular Material
Icons SVG
<mat-icon>home</mat-icon>
Librairie open source https://materialdesignicons.com
Nom de l'icône
home, clear, ...
Angular Material
Buttons
<button mat-button color="primary">
Nom du bouton
</button>
Directive
Couleur
primary, warn, accent, basic, ...
mat-button, mat-flat-button, mat-raised-button, ...
Angular Material
Typography
CSS class. Name Native elements
.mat-display-4 | display-4 | None |
.mat-display-3 | display-3 | None |
.mat-display-2 | display-2 | None |
.mat-display-1 | display-1 | None |
.mat-h1 or .mat-headline | headline | <h1> |
.mat-h2 or .mat-title | title | <h2> |
.mat-h3 or .mat-subheading-2 | subheading-2 | <h3> |
.mat-h4 or .mat-subheading-1 | subheading-1 | <h4> |
.mat-h5 | None | <h5> |
.mat-h6 | None | <h6> |
.mat-body or .mat-body-1 | body-1 | Body text |
.mat-body-strong or .mat-body-2 | body-2 | None |
.mat-small or .mat-caption | caption | None |
*Voir Page Typography
Importation - implémentation
1. Dédier un module pour importer tout les besoins spécifiques d'Angular Material :
Commande : ng g module modules/material
import { MatAutocompleteModule } from '@angular/material/autocomplete';
const MaterialComponents = [
MatAutocompleteModule
];
@NgModule({
imports: [MaterialComponents],
exports: [MaterialComponents],
})
export class MaterialModule
M
Importation - implémentation
2. Importer le module material.module.ts dans votre module principal app.module.ts :
import { MaterialModule } from './modules/material.module';
@NgModule({
...,
imports: [MaterialModule],
...,
})
export class AppModule
M
Documentations
material.io blog Material design
Documentation Angular Material Composants
material.angular.io/components/categories
Documentation Angular Material CDK (Utilitaires)
https://material.angular.io/cdk/categories
Material CDK
TP :
- Utiliser Angular cdk-accordion sur la page cards (Ce référer à la documentation CDK).
- Cliquer sur un élement affiche ça description.
- Faire que l'accordion sois le plus résponsive possible.
?
Material Components
TP :
- Créer un nouveau composant HeaderComponent dans le dossier app.
- Utiliser mat-toolbar ajouter une icône home sur la gauche et gérer l'authentification sur la droite.
- Utiliser mat-sidenav quand l'utilisateur clique sur l'icône home une sidebar s'ouvre et affiche tout les boutons de navigation.
?
Angular Material
Le Theming
Theming - implémentation
1. Créer un nouveau dossier src/themes.
2. Créer un nouveau fichier src/themes/dark.scss.
3. Créer un nouveau Theme sur un des éditeurs éxistants pour Angular Matérial.
Editeur : https://materialtheme.arcsine.dev
4. Ajouter dans la configuration angular.json à la section style le fichier votre-theme.scss.
?
5. Vérifier le rendu final.
Angular + RXJS
@rxjs
Note: Angular et le fonctionnel
Microsoft
Un peu de programmation fonctionnelle ?
Note: Fait partie du groupe de paradigme d'éclaratif
Impératif, Orienté objet, Déclaratif.
Définition de la programmation fonctionnelle ?
Le fonctionnel c'est écrire un programme essentiellement composé d'évaluation de fonctions pures.
Fonction pure et impure
une fonction pure est une fonction qui possède les propriétés suivantes : Sa valeur de retour est la même pour les mêmes arguments fournis.
let value = 1295;
const add = (number: number)=> {
value += number;
};
add(42);
// Log: 1337
console.log(value);
const pureAdd = (a: number, b: number) => { return (a + b) };
// Log: 1337
console.log(pureAdd(1295, 42));
*Note: La fonction add n'est pas une fonction pure car elle modifie une valeur globale qui créée un effet de bord
Qu'est ce que la programmation réactive ?
Concept Réactif
La programmation réactive se base sur le concept d'observateur.
Si vous n'êtes pas familier avec ce principe, le principe est tout simplement que l'on définit des observables et des observateurs.
Les observables vont émettre des événements qui seront interceptés par les observateurs.
La programmation réactive va étendre ce concept en permettant de combiner les observables, modifier les événements à la volée, les filtrer, etc.
Mécanisme
Observable
Observer
Observer
Observer
Subscribe()
Subscribe()
Subscribe()
Quand une action émet une variable depuis l'observable via la méthode next() les observers sont notifiés et peuvent récupérer la variable.
Observable
L'observable est un moyen de souscrire à une observation.
varX
Il est possible de filtrer l'observation (Concept Reactif)
L'observable
Observable :
Classe qui permet l'association d'une source de donnée à un observeur (souscription) + retourne un moyen d'annuler cette liaison.
type: Observable
Observable
Methods:
function subscribe(): Subscription;
function toPromise(): Promise<T>;
function pipe(): Observable<T>;
Cette classe a l'utilité principale de permettre la souscription.
Le Subject
Héritier d'un observable
Il a un moyen de souscrire à l'observation.
Il est le moyen d'émettre des événements à propos du sujet à observer.
varX
.next('hello');
.subscribe();
*Boisson
import { of } from 'rxjs';
const myObservable = of(42);
// output : 42
myObservable.subscribe((value) => { console.log(value); });
RXJS
Souscrire à des observables
of est la méthode la plus simple qui permet de créer un observable n'envoyant qu'une seule valeur (42 dans notre exemple).
la méthode subscribe va nous permettre d'écouter l'observable en créant nos observateurs.
subscribe prend en paramètre l'observateur, qui est une simple fonction qui recevra les valeurs émises par l'observable. Notre console affichera donc 42 dans notre exemple.
Helper Pipe async
Permet d’afficher des données obtenues de manière asynchrone en utilisant PromisePipe ou ObservablePipe.
Un pipe async retourne une chaîne de caractères vide jusqu’à ce que les données deviennent disponibles (ex: promise résolue, dans le cas d’une promise), puis :
- retourne la valeur obtenue
- déclenche un cycle de détection de changement une fois la donnée obtenue (Observables)
Pipe async
import { Component } from '@angular/core';
import { Observable, of } from 'rxjs';
@Component({
selector: 'ns-greeting',
template: `<div>{{ asyncGreeting | async }}</div>`
})
export class GreetingComponent {
asyncGreeting: Observable = of(42);
}
Exemple utilisant un observable :
TP Observable
TP:
- Créer un Observable en utilisant la fonction of et lui passer en paramètre un nombre.
(N'hesiter pas à observer le code source de la fonction of (IDE shortcut Ctrl+click)).
- Utiliser un Pipe Async avec un Observable.
- Souscrire à un Observable puis afficher la valeur observée en console.
?
Le Subject
Subject :
Classe de type Observable qui permet la gestion du flux de données.
Type: Subject
Subject
Methods:
function next(): Subject<T>;
function complete(): Subject<T>;
function subscribe(): Subscription;
Cette classe est aussi de type Observable.
RXJS
Gestion et création de vos propres Observables.
1. BehaviorSubject
2. AsyncSubject
3. ReplaySubject
4. Subject
Les Subjects ?
Ils en existents quatres :
// RxJS v6+
import { BehaviorSubject } from 'rxjs';
const subject = new BehaviorSubject<number>(123);
// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);
// two subscribers will get new value => output: 456, 456
subject.next(456);
// new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);
// all three subscribers will get new value => output: 789, 789, 789
subject.next(789);
// output: 123, 123, 456, 456, 456, 789, 789, 789
RXJS
Le BehaviorSubject :
Le BehaviorSubject nécessite une valeur initiale et émet la valeur actuelle aux nouveaux abonnés.
// RxJS v6+
import { AsyncSubject } from 'rxjs';
const sub = new AsyncSubject();
sub.subscribe(console.log);
sub.next(123); //nothing logged
sub.subscribe(console.log);
sub.next(456); //nothing logged
sub.complete(); //456, 456 logged by both subscribers
RXJS
Le AsyncSubject :
Le AsyncSubject émet la valeur finale aux abonnés lorsque celui-ci est complété.
// RxJS v6+
import { ReplaySubject } from 'rxjs';
const sub = new ReplaySubject(3);
sub.next(1);
sub.next(2);
sub.subscribe(console.log); // OUTPUT => 1,2
sub.next(3); // OUTPUT => 3
sub.next(4); // OUTPUT => 4
sub.subscribe(console.log); // OUTPUT => 2,3,4 (log of last 3 values from new subscriber)
sub.next(5); // OUTPUT => 5,5 (log from both subscribers)
RXJS
Le ReplaySubject :
Le ReplaySubject émet chaques valeurs depuis le début ou bien dès une souscription de la plus récente à la taille définie en paramètre du constructeur. (3 dans l'exemple)
// RxJS v6+
import { Subject } from 'rxjs';
const sub = new Subject();
sub.next(1);
sub.subscribe(x => {
console.log('Subscriber A', x)
});
sub.next(2); // OUTPUT => Subscriber A 2
sub.subscribe(x => {
console.log('Subscriber B', x)
});
sub.next(3); // OUTPUT => Subscriber A 3, Subscriber B 3 (logged from both subscribers)
RXJS
Le Subject :
Le Subject émet la valeur actuelle aux abonnés.
// Dans notre DataService
getAuthorOfTheMonth(): Observable<Object> {
return this.http.get<Object>('http://localhost:3000/authorofthemonth');
}
getAuthorOfTheMonthArticles(): Observable<Article[]> {
return forkJoin(
this.getArticles(), // la requête http qui récupère la liste des articles
this.getAuthorOfTheMonth() // celle qui récupère l'auteur du mois
).pipe(
map(([articles, authorOfTheMonth]) =>
// le filtre qui exclut les articles qui ne sont pas de cet auteur
articles.filter(article => article['author'] === authorOfTheMonth['name'])
)
)
}
Combiner les observables
forkJoin: combiner la sortie de plusieurs observables
forkJoin prend en paramètre plusieurs observables et résulte en un observable
Nb: Ferme la souscription une fois la liaison terminée.
// Dans notre DataService
getAuthenticatedUserArticles(): Observable<Article[]> {
return combineLatest(
this.getAuthenticatedUser(), // l'observable qui émet l'utilisateur connecté
this.getArticles() // l'observable qui émet la liste des articles
).pipe(
map(([user, articles]) => {
if (user.isAnonymous) {
return [];
}
return articles.filter(article => article['author'] === user['name'])
})
)
}
Combiner les observables
combineLatest: écouter un groupe d'observable
L'opérateur combineLatest est similaire au forkJoin à une distinction près. Il ne se termine pas une fois qu'il a reçu les résultats de chaque Observable.
// Dans notre DataService
// deux calls successifs
getAuthorOfTheMonthArticles(): Observable<Article[]> {
return this.getAuthorOfTheMonth().pipe( // l'observable qui émet l'auteur du mois
mergeMap((authorOfTheMonth) => {
return this.http.get<Article[]>('http://localhost:3000/articles/?author=' + authorOfTheMonth['name'])
})
)
}
Combiner les observables
mergeMap: utiliser la sortie d'un Observable en entrée d'un autre. (Concaténation)
On aura toujours besoin de deux observables ou plus, mais cette fois les traitements seront successifs au lieu d'être simultanés.
mergeMap créer un observable à partir d'un observable source.
Merge TP
TP :
- Créer un nouveau composant (ex: RandomHoneyPotComponent)
- Associer une route à ce nouveau composant /random-honey
- Vous avez à disposition deux routes sur l'api fournie :
1. /random-card-id
2. /card/{id}
- Objectif:
Cliquer sur un bouton pour afficher un nouveau pot de miel aléatoire.
?
Endpoint api: formationangular.eu-gb.mybluemix.net
Mini interaction
TP:
- Coder un composant qui créer des ronds aléatoirement comme sur le gif ci-dessous en utilisant un Canvas ou D3js avec l'aide de la fonction fromEvent d'rxjs.
?
range(1, 200)
.filter(x => x % 2 === 1)
.do(console.log('Do something'))
.map(x => {
console.log(`Processing x=${x}`);
return x + x;
}).subscribe(x => console.log(x));
Evolution de l'API RxJS
Les principaux changements sont d'une part l'introduction des pipeable operators en version 5.5.
Exemple ancien code :
import { range } from 'rxjs';
import { filter, map, tap } from 'rxjs/operators';
range(1, 200)
.pipe(
filter(x => x % 2 === 1),
tap(x => console.log(x)),
// map(x => x + x)
map(x => {
console.log(`Processing x=${x}`);
return x + x;
})
)
.subscribe(x => console.log(x));
Devient :
Quelques références
Le site de la programmation réactive : http://reactivex.io/
Learn rxjs subjects : https://www.learnrxjs.io/learn-rxjs/subjects
Learn rxjs opérators : https://www.learnrxjs.io/learn-rxjs/operators
Rxjs animated visualizer : https://rxviz.com
Angular + Redux
@ngrx
Note: Angular Redux / vuejs vuex
Redux ?
"Redux is a predictable state container for JavaScript apps"
- Facile à comprendre
- Cohérent
- Uniquement les données et la manière de les modifier
- Rien à voir avec le rendu des données (vue)
3 Principes de redux
Single Source of Truth
L'état de toute votre application est stocké dans un arbre d'objets se trouvant lui même dans un store unique.
State is read-only
L'état de votre application est "immutable", la seule manière de le modifier est d’émettre une action (dispatching).
Changes are made with pure functions
L'état sera modifié par des "pure functions", ces fonctions doivent toujours retourner le même output pour un même input.
Architecture
Unidirectional Data Flow
Redux passe les actions a travers le "reducer" et fournit le nouveau state à la vue.
Le reducer peut être divisé en plusieurs reducers spécialisés
Un store unique
Reducer
Dispatch action
Dispatch action
1
1
2
4
3
Single Source of Truth ?
- Facilite le debug
- Les données venant du serveur n'ont pas à se trouver dans plusieurs endroits
State is read-only ?
- La vue ou les requêtes http ne peuvent modifier directement le state
- Toutes les mutations du state sont centralisées et se produisent une par une dans un/des reducer/s
- Les actions décrivent explicitement ce qui se passe
- Les actions sont des "plain old object" qui peuvent être serialisés, loggués ou utilisés pour le debug
Changes are made with pure functions ?
- une fonction "pure" retournera toujours le même output pour un input donné, ce qui évite/interdit les effets de bords indésirables
- Composable
- StoreModule.forRoot({ reducerkey: ReducerFunc, ... })
- StoreModule.forFeature({ reducerkey: ReducerFunc, ... })
- Réutilisable
- reducer spécialisé pour les formulaires par exemple
Un peu de code
counter-reducer.ts
export const counterReducer = (state: ValueState | undefined, action) => {
return createReducer(initialState,
/** definitions des actions possible : */
on(incrementAction, (currentState, newAction) => {
console.log('from reducer :', currentState, newAction);
return currentState + 1;
}),
on(decrementAction, currentState => currentState - 1),
on(resetAction, currentState => initialState),
/** --------------------------------- */
)(state, action);
};
app.module.ts
@NgModule({
declarations: [...],
imports: [
/** definition des reducers suivant leurs particularités */
StoreModule.forRoot({
counter: counterReducer
})
]
})
export class AppModule { }
import { createReducer, on } from '@ngrx/store';
export const incrementAction = createAction('[Counter Reducer] Increment');
export const decrementAction = createAction('[Counter Reducer] Decrement');
export const resetAction = createAction('[Counter Reducer] Reset');
export const initialState = 0;
export const counterReducer = (state: ValueState | undefined, action) => {
return createReducer(initialState,
/** definitions des actions possible : */
on(incrementAction, (currentState, newAction) => {
console.log('from reducer :', currentState, newAction);
return currentState + 1;
}),
on(decrementAction, currentState => currentState - 1),
on(resetAction, currentState => initialState),
/** --------------------------------- */
)(state, action);
};
Reducer & Actions
import { NgModule } from '@angular/core'
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './counter';
@NgModule({
imports: [
BrowserModule,
/** definition des reducers suivant leurs particularités */
StoreModule.forRoot({
count: counterReducer
})
]
})
export class AppModule {}
Dans le module
import { Store } from '@ngrx/store';
import { incrementAction, decrementAction, resetAction } from './counter.action';
interface AppState {
counter: number;
}
@Component({
selector: 'my-app',
template: `
<button (click)="increment()">Increment</button>
<div>Current Count: {{ counter | async }}</div>
<button (click)="decrement()">Decrement</button>
<button (click)="reset()">Reset Counter</button>
`
})
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>) {
this.counter = store.select('counter');
}
increment(){
this.store.dispatch(incrementAction());
}
decrement(){
this.store.dispatch(decrementAction());
}
reset(){
this.store.dispatch(resetAction());
}
}
Au niveau du component
ngRx
Implémentation de redux utilisant rxjs et prévu pour angular2
npm install @ngrx/store -S
5 bibliothèques :
- Store
- Effects
- DevTools
- RouterStore
- Rxjs
Redux
TP :
- Reproduire l'exemple fourni dans la partie Get Started de https://ngrx.io/guide/store
- Si vous avez réussi, essayez de faire passer une variable dans votre store à l'aide des props<> (second argument lors de la création d'une action)
?
1. Pour respecter la philosophie d'une structure d'application à flux de données unidirectionnel.
Dans quelles conditions doit-on utiliser Redux ?
2. Quand vous avez une donnée qui doit être distribuée à plusieurs endroits.
store.select('card')
store.select('card')
La décoration @Input('card') n'est plus necessaire dans le composent 2. (Celle-ci été considéré comme étrangère au composant 2).
1
2
3
Dans quelles conditions doit-on utiliser Redux ?
3. Pour centraliser des interactions en provenance d'un serveur.
store.select('update-cards')
Dans quelles conditions doit-on utiliser Redux ?
store.dispatch(updateCardsAction([
'12', '22', '17'
]))
Affiche une popup si nous sommes sur la page d'un des cards mis à jour.
Text
Kafka connect,
amqp, db changes ...
Redux
TP :
- Créer un dossier redux
- Créer les fichiers suivant :
- redux/cards.action.ts
- redux/cards.reducer.ts
- redux/cards.effects.ts
Faite comme le formateur et suivre les informations fournis dans la documentation @ngrx/effects.
Objectif : Gerer les erreurs http et la récupération des cards dans un seul observable.
?
Lazy
Loading
AppModule
AdminModule
(ChildRoute)
(ParentRoute)
Lazy loading ?
- Chargement plus rapide
- Un module par fonctionnalité
- Découpage du code en "chunks"
*Chargement fainéant en Francais
Avant
- Le navigateur demande le code au serveur
- Il lit le code de toute l'application
- Il génère les templates et les affichent
Après
- Le navigateur demande le code au serveur
- Il lit le code du module principal
- Il génère un template et l'affiche
- Il répète les actions 2. et 3. seulement quand l'utilisateur demande à voir une fonctionnalité supplémentaire
Structure d'un module "Lazy loadé"
(load feature module from route)
Mise en place du lazy loading
- Créer un module de fonctionnalité
- Configurer les routes principales
- Configurer le module de routage (app)
- Configurer les routes de notre lazy
1. Créer un module de fonctionnalité
ng generate module featureOne --routing
L'option --routing permet de créer un module de routage automatiquement pour notre module.
Elle est également utilisable lorque l'on crée une application à l'aide de
ng new app --routing
Ps: l'ajout d'un module routing et demandé a la création d'une application depuis angular 8
const routes: Routes = [
{
path: 'featureOne',
loadChildren: () => import('./feature-one/feature-one.module').then(m => m.FeatureOneModule)
},
{
path: 'featureTwo',
loadChildren: () => import('./feature-two/feature-two.module').then(m => m.FeatureTwoModule)
},
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
app-routing.module.ts
2. Configurer les routes principales
3. Configurer un module de routage
@NgModule({
imports: [
CommonModule,
FeatureOneRoutingModule
],
declarations: [FeatureOneComponent]
})
export class FeatureOneModule { }
4. Configurer les routes du module
const routes: Routes = [
{
path: '',
component: FeatureOneComponent
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class FeatureOneRoutingModule { }
Lancement de l'app
Chargement d'un module "lazy loadé"
Les Lazy-modules
TP:
- Créer un Module AdminModule avec le flag --routing
- Créer un/des composants dans AdminModule
- Créer une route dans app-routing.module qui
loadChildren notre AdminModule.
- Créer une route dans le module de routing de
AdminModule qui load un Composant.*
?
*Attention: dans le childrenRouter vous êtes à la racine de l'url
Route Guards
Guards
CurrentRoute
RouteWanted
Note: Une guard est une vérification de sécurité entre les routes.
Sans Route Guards, l'utilisateur peut naviguer vers n'importe quel composant comme bon lui semble
Plusieurs problèmes possibles :
- l'utilisateur n'y est pas autorisé
- l'utilisateur doit être authentifié
- certaines données doivent être récupérées avant
- certaines données doivent être sauvegardées avant de quitter le composant actuel
- l'utilisateur doit confirmer la sortie du composant
Une Route Guard s'applique à la configuration d'une route
Si elle retourne true, le processus de navigation continue
Si elle retourne false, le processus de navigation est annulé
NB : il est possible d'y effectuer également une redirection
Problème
Dans la plupart des cas, les traitements effectués avant une navigation sont asynchrones.
Solution
Les Guards peuvent recevoir des données asynchrones (Promise ou Observable)
Dans ce cas, la réponse attendue par le routeur doit absolument être de type Observable<boolean> ou Promise<boolean>
Le routeur supporte de multiples interfaces de Guard
- CanActivate pour gérer la navigation vers une route
- CanActivateChild pour gérer la navigation vers une sous-route
- CanDeactivate pour gérer la navigation depuis une route (sortie d'un composant)
- CanLoad pour gérer la navigation vers un module "lazy loadé"
CanActivate
ng generate module ./modules/admin --routing
Générer un module de fonctionnalité admin
Demander l'authentification
ng generate component ./modules/admin/admin-dashboard
Et un composant à l'intérieur
Générer une Guard
ng generate guard auth
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
console.log('AuthGuard#canActivate called');
return true;
}
}
Ajouter la propriété canActivate à la configuration de la route
const routes: Routes = [
{
path: 'admin',
component: AdminComponent,
canActivate: [AuthGuard]
}
];
Créer un service d'authentification
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, delay } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class AuthService {
public isLoggedIn = false;
login(): Observable<boolean> {
return of(true).pipe(
delay(5000),
tap(val => this.isLoggedIn = true)
);
}
logout(): void {
this.isLoggedIn = false;
}
}
Ne pas oublier la déclaration en provide du service dans le module courant.
Mettre à jour la Guard
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable({
providedIn: 'root',
})
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
// fonction implementé de l'interface CanActivate
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLogged) { return true; }
// gestions des droits en fonction de l'url potentiellement ici
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
}
Les Guards
TP:
- Utiliser les guards :
1. canActivate
2. canDeactivate avec une popup pour demander à l'utilisateur de confirmer la sortie du composant.
- Utiliser un canActivate avec un retour asynchrone.
?
Les formulaires
@angular/forms
Les formulaires en Angular
Angular propose 2 types de formulaires:
- "template driven": formulaires simples
=> peu de validation.
- "reactive forms": représentation du formulaire dans le contrôleur
=> plus verbeux, mais aussi plus puissant (validation custom)
Template driven
<h2>Sign up</h2>
<form (ngSubmit)="register()">
<div>
<label>Username</label>
<input type="text" [(ngModel)]="username">
</div>
<div>
<label>Password</label>
<input type="password" [(ngModel)]="password">
</div>
<button type="submit">Register</button>
</form>
Il suffit d'ajouter les directives ngModel, qui crée le FormControl, et le <form> crée automatiquement le FormGroup.
DrivenForm - implémentation
1. Rendre ce FormsModule disponible dans l'application en l'important dans le ngModule courant :
import { FormsModule } from '@angular/forms';
@NgModule({
// import des modules dont vous etes dependants.
imports: [..., FormsModule],
declarations: [...],
exports: [...],
})
export class AppModule
M
Reactive forms :
import { FormBuilder, FormGroup, FormControl } from '@angular/forms';
@Component({
selector: 'ns-register',
templateUrl: 'register-form.component.html',})
export class RegisterFormComponent {
public registerForm: FormGroup;
public usernameCtrl: FormControl = new FormControl('');
public passwordCtrl: FormControl = new FormControl('');
constructor(public fb: FormBuilder) {
this.registerForm = fb.group({
'username': this.usernameCtrl,
'password': this.passwordCtrl
});
}
}
On crée 'manuellement' le formulaire :
Reactive forms :
<h2>Sign up</h2>
<form (ngSubmit)="register()" [formGroup]="userForm">
<div>
<label>Username</label>
<input formControlName="username">
</div>
<div>
<label>Password</label>
<input type="password" formControlName="password">
</div>
<button type="submit">Register</button>
</form>
Que l'on lie ensuite au template :
ReactiveForm - implémentation
1. Rendre ce ReactiveFormsModule disponible dans l'application en l'important dans le ngModule courant :
import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
// import des modules dont vous etes dependants.
imports: [..., ReactiveFormsModule],
declarations: [...],
exports: [...],
})
export class AppModule
M
Du style
input.ng-invalid {
border: 3px red solid;
}
input.ng-valid {
border: 3px green solid;
}
=> ajout / retrait automatique classes CSS selon l'état du formulaire
Exemple: ng-invalid si un de ses validateurs échoue
Et bien d'autres voir https://angular.io/guide/form-validation
Attributs FormGroup
Pour vérifier la validité complette de tout les FormControl d'un FormGroup la variable :
FormGroup.valid permet de vérifier rapidement la validité d'un formGroup.
<div [formGroup]="formGroup" >
...
...
<button (click)="execute()" [disabled]="!formGroup.valid">Execute</button>
</div>
Attributs FormControl
Un FormControl a plusieurs variables/méthodes :
• valid : si le champ est valide, au regard des contraintes et des validations qui lui sont appliquées.
• invalid : si le champ est invalide, au regard des contraintes et des validations qui lui sont appliquées.
• errors : un objet contenant les erreurs du champ.
• value : la valeur contenue dans le champ.
+ quelques méthodes comme hasError('required') pour savoir si le contrôle a une erreur donnée.
Le FormControl
const password = new FormControl('abc');
console.log(password.touched);
console.log(password.value);
console.log(password.hasError('required'));
On peut donc écrire :
Ces contrôles peuvent être regroupés dans un FormGroup ("groupe de formulaire") pour constituer une partie du formulaire qui a des règles de validation communes. Un formulaire lui-même est un groupe de contrôle.
Le FormGroup
const form = new FormGroup({
username: new FormControl('Jérémy'),
password: new FormControl() });
console.log(form.valid);
Un FormGroup a les mêmes propriétés qu’un FormControl, avec quelques différences :
• valid : si tous les champs sont valides, alors le groupe est valide.
• invalid : si l’un des champs est invalide, alors le groupe est invalide.
• Etc ...
FormGroup best practice
public formGroup: FormGroup;
public username: FormControl = new FormControl('', [Validators.required]);
public password: FormControl = new FormControl('', [Validators.required]);
constructor(public formBuilder: FormBuilder) {
this.formGroup = this.formBuilder.group({
'username': this.username,
'password': this.password
});
}
Validation
constructor(fb: FormBuilder) {
this.userForm = fb.group({
username: fb.control('', [Validators.required,
Validators.minLength(3)]),
password: fb.control('', Validators.required)
});
Reactive form :
Angular nous permet de rajouter des paramètres de validation facilement.
Validation
<h2>Sign up</h2>
<form (ngSubmit)="register(userForm.value)"
#userForm="ngForm">
<div>
<label>Username</label><input name="username"
ngModel required minlength="3">
</div>
<div>
<label>Password</label><input type="password"
name="password" ngModel required>
</div>
<button type="submit">Register</button>
</form>
Template driven :
Validation
<input
required
required="false"
minlength="0"
maxlength="100"
</input>
Template driven :
Validation
Quelques validateurs sont fournis par le framework :
• Validators.required pour vérifier qu’un contrôle
n’est pas vide ;
• Validators.minLength(n) pour s’assurer que la valeur
entrée a au moins n caractères ;
• Validators.maxLength(n) pour s’assurer que la valeur
entrée a au plus n caractères ;
• Validators.email() (disponible depuis la version 4.0)
pour s’assurer que la valeur entrée est une adresse email valide
• Validators.min(n) (disponible depuis la version 4.2)
pour s’assurer que la valeur entrée vaut au moins n ;
• Validators.max(n) (disponible depuis la version 4.2)
pour s’assurer que la valeur entrée vaut au plus n ;
• Validators.pattern(p) pour s’assurer que la valeur
entrée correspond à l’expression régulière p définie.
Erreurs et soumission
<h2>Sign up</h2>
<form (ngSubmit)="register()" [formGroup]="userForm">
<div>
<label>Username</label>
<input formControlName="username">
</div>
<div>
<label>Password</label>
<input type="password" formControlName="password">
</div>
<button type="submit" [disabled]="userForm.invalid">
Register
</button>
</form>
Reactive form:
Les Formulaires
TP:
- Importer ReactiveFormsModule & FormsModule dans votre ngModule
- Crée un nouveau composant (ex: AddCardComponent)
- Associer une route à ce nouveau composant /create
- Créer un formulaire pour créer un nouveau card
- Utiliser le service CardService et le endpoint /card POST
- Redirection vers la page Cards
?
Nb: Aucune Validation pour le moment
Validation
new FormControl(
'defaultValue', // 1er argument une valeur
[SyncValidators], // un array ou un seul validateur
[AsyncValidators]); // un array ou un seul validateur
Reactive form :
Angular nous permet de rajouter des paramètres de validation facilement.
Custom Validation
export function cColorValidator(color: String): ValidatorFn {
return (c: AbstractControl): {[key: string]: any} | null => {
const hasColor = c.value.includes(color);
return !hasColor ? {color: c.value } : null;
};
}
//1er argument defaultValue
//2eme argument 1seul Validateur ou un array de Validateur
//3eme argument 1seul AsyncValidateur ou un array de AsyncValidateur
new FormControl('red', cColorValidator('red'));
Vous pouvez créer vos propres validateurs :
Custom Async Validation
export function asyncColorValidator(color: String): AsyncValidatorFn {
return (c: AbstractControl): Observable<{[key: string]: any} | null> => {
const hasColor = c.value.includes(color);
//potentiels appels vers des API
return of(!hasColor ? {color: c.value} : null);
};
}
//1er argument defaultValue
//2eme argument 1seul Validateur ou un array de Validateur
//3eme argument 1seul AsyncValidateur ou un array de AsyncValidateur
new FormControl('red', [], asyncColorValidator('red'));
Vous pouvez créer vos propres validateurs asynchrones :
Les Validateurs
TP:
- Créer un Validateur synchrone (ex: isEmptyValidator)
- Créer un Validateur asynchrone (ex: accountExistsValidator, cardExistsValidator)
- Disable le bouton submit si une erreur de validation est survenue.
?
Server-side rendering
SSR
Angular Universal
Server Side Rendering
Rendu côté serveur (SSR) avec Angular Universal
Server Side Rendering
Avantages d'utiliser le rendu côté serveur ?
Pas besoin d'interpréteur javascript.
Premiere page rendu rapidement.
SEO facilité.
Inconvenients d'utiliser le rendu côté serveur ?
L'application fonctionne en mode dégradé.
Ajouter SEO Title et des Meta Descriptions dans Angular
import { Title, Meta } from '@angular/platform-browser';
BrowserModule
Utilisable en injection de dépendance.
SEO Meta - Title
Open Graph Protocol
2010
SEO Meta - Title
Twitter Cards
<meta content="Formation Angular" property="twitter:title">
<meta content="Use Binance Smart Chain network." property="twitter:description">
<meta content="https://formation.io/assets/img/thumbnail.png" property="twitter:image">
Twitter est compatible avec og protocol.
SEO Meta - Title
SEO Meta - Title
TP:
- Utiliser le service Title et appliquer un titre different sur plusieurs composants chargés par le routeur.
- Créer un meta Open Graph Protocol complet au démarrage de l'application.
?
Industrialisation
@angular/cli - Compilation anticipée (AOT)
Planifier plusieurs configurations en fonction de vos environements.
Industrialisation
angular.json (Fichier de configuration)
package.json (Ajouter des commandes specifiques pour vos environements)
Le versionning de vos applications
Industrialisation
Norme semantic de versionning
Tester et deployer une application pour des applications Angular.
Industrialisation
Les outils d'intégration continue :
Exemple d'integration continue avec Jenkins
Industrialisation
Il vous faudras monter un serveur Jenkins sur votre réseau entreprise.
C'est gratuit jusqu'a 100 configurations de build ensuite la license est de 299 $ par an.
Une fois Jenkins configuré (Travail DevOps)
Industrialisation
git add .
git commit -m "add toogle button"
git push
Le devOps vous demanderas d'ajouter un fichier Jenkinsfile. (Fichier semblable à un fichier DockerFile).
Flow
Industrialisation
Detection du push par Jenkins
Lancement du pipeline
Lancement des tests U, build de l'application, monté de version.
Pipeline OK, vous pouvez deploy votre application sur un environement.
Ahead-of-time (AOT) compilation
Deux types de compilations sont possible lors du build de votre application.
Just-in-Time (JIT) - Compilation au Runtime
Ahead-of-Time (AOT) - Compilation au moment du build (Par defaut depuis Angular 9)
Progressive Web App
Cordova
cordova.apache.org
Capacitor
capacitorjs.com
Angular - Concepts avancés v2
By AdapTeach
Angular - Concepts avancés v2
Présentation des concepts avancés du framework Angular. Formation 2 jours
- 1,834