Angular

Concepts avancés

Angular rappel

@loading

Note: Databinding, Modules, Components, Directives, Pipes...

Utiliser la CLI

Pour :

 

La création de vos composants, directives, pipes `ng g`

 

!!! Toujours rester à la racine de son projet !!!

(Option) CLI NX *depuis angular 14

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');

Principales fonctionnalités ES6

Liste d'arguments : (Ellipses)


// combiner plusieurs tableaux
[... ['1', '2'], ... ['3', '4']] // = ['1', '2', '3', '4']

// combiner des objets
{
  ... { id: 1, email: 'ninja@gmail.com', message: 'COUCOU' },
  message: 'Je remplace ton message de m*rde :p'
}
// = {
// id: 1,
// email: 'ninja@gmail.com',
// message: 'Je remplace ton message de m*rde :p'
// }

Principales fonctionnalités ES6

Selectors : (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 pizzas contenu dans la page du site web suivant:

?

?

À coder directement dans la console.

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 (à faire sois même)
      |----- environment.prod.ts|ts
  |----- favicon.ico
  |----- index.html
  |----- main.ts
  |----- polyfills.ts    // Rétrocompatibilité
  |----- styles.css
|- .gitignore          // .gitignore près généré
|- karma.conf.js       // configuration serveur de test (à ajouter sois même)
|- package.json        // configuration dependences js principal
|- angular.json        // configuration build, serve angular
|- tsconfig.app.json   // extends tsconfig.json
|- tsconfig.json       // configuration compiler typescript

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>

Décoration @HostBinding

@Directive({selector: '[ngModel]'})
class NgModelStatus {
  constructor(public control: NgModel) {}
  @HostBinding('class.valid') get valid() { return this.control.valid; }
  @HostBinding('class.invalid') get invalid() { return this.control.invalid; }
}

Utilisation cas 2 value = 1 :

 

<app-card class="invalid" [(ngModel)]="value" min="2"/>

Lier la valeur d'une propriété sur son hôte.

Utilisation cas 1 value = 3 :

 

<input class="valid" [(ngModel)]="value" min="2"/>

Décoration @HostBinding

@Directive({selector: 'button[counting]'})
class CountClicks {
  numberOfClicks = 0;

  @HostListener('click', ['$event.target'])
  onClick(btn) {
    console.log('button', btn, 'number of clicks:', this.numberOfClicks++);
  }
}

Utilisation :

<button counting>Increment</button>

Écouter un événement sur son hôte.

Construction du support de formation

Note: DTO & librairies nécessaires pour le fils de la formation.

URL swagger API:

 https://formationangular.eu-gb.mybluemix.net

Création d'une nouvelle application SPA (Angular 14 ou 15)

Application Support

?

Installer @angular/material

Installer @ngrx/store

Installer @ngrx/effects

Ajouter un fichier d'environements dans src/environements/environement.ts qui export environement = {};

Installer angular-oauth2-oidc

Ajouter des fichiers d'environements

Application Support

?

src/environements/environement.ts qui contient:  export environement = {};

src/environements/environement.prod.ts qui contient:  export environement = {};

Spécifier dans la configuration angular.json le "fileReplacements" en fonction de la configuration sélectionné . (Je vous laissent rechercher.)

Créer un module shared:

Application Support

?

ng generate module shared

Créer un module Admin avec un module de routage dédié:

ng generate module admin --routing

export class Card {
    public id?: string;
    constructor(
        public title: string,
        public description: string,
        public price: number,
        public type: string
    ) { }
}

Card model

Application Support

Formation API :

Ajouter le model dans un dossier shared/models :

?

https://formationangular.eu-gb.mybluemix.net

export class User {
    constructor(
        public id: string,
        public email: string,
        public picture: string
    ) { }
}

User model

Application Support

Formation API :

Ajouter le model dans un dossier shared/models :

?

https://formationangular.eu-gb.mybluemix.net

export class ChannelMessage {
    constructor(
        public id: string,
        public userId: string,
        public email: string,
        public picture: string,
        public message: string,
        public timestamp: number,
        public updated?: boolean,
        public deleted?: boolean
    ) {}
}

ChannelMessage model

Application Support

?

Ajouter le model dans un dossier shared/models :

import { ChannelMessage } from "./channel-message";

export class Channel {
    constructor(
        public title: string,
        public messages: ChannelMessage[],
        public picture: string
    ) {}
}

ChannelMessage model

Application Support

Formation API :

?

https://formationangular.eu-gb.mybluemix.net

Ajouter le model dans un dossier shared/models :

Angular - Le Futur Sans ngModules

Angular 14

Composants Autonomes

Composants Autonomes

Précedent context de spécification des dépendances.

C’était un passage obligatoire à la fois pour les developpeurs et pour l'interprétation du compilateur.

Pourquoi?

Composants Autonomes

Définition d'un composant Autonome :

Comment?

@Component({
  standalone: true,
  selector: 'app-root',
  imports: [
    RouterOutlet,
    NavbarComponent,
    SidebarComponent,
  ],
  provides: [],
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
    [...]
}

1/4

Composants Autonomes

Il faut voir la création d'un composant autonome comme un composant qui serait à la fois un NgModule et un Component :

2/4

Comment?

Composants Autonomes

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import { AppComponent } from './app/app.component';
import { APP_ROUTES } from './app/app.routes';
import { provideRouter } from '@angular/router';
import { importProvidersFrom } from '@angular/core';

[...]

bootstrapApplication(AppComponent, {
  providers: [
    importProvidersFrom(HttpClientModule),
    provideRouter(APP_ROUTES),
    provideAnimations(),
    importProvidersFrom(TicketsModule),
    importProvidersFrom(LayoutModule),
  ]
});

Plus besoin de ngModule, maintenant tout se passe dans le main.ts au bootstrap de l'application.

3/4

Comment?

Composants Autonomes

import { AsyncPipe, JsonPipe, NgIf } from "@angular/common";

[...]

@Component({
  standalone: true,
  imports: [
    // CommonModule, // ou specifier directement les Pipes - Directives ->
    NgIf,
    AsyncPipe,
    JsonPipe,

    FormsModule, 
    FlightCardComponent,
    CityValidator,
  ],
  selector: 'flight-search',
  templateUrl: './flight-search.component.html'
})
export class FlightSearchComponent implements OnInit {
    [...]
}

Compatibilité avec le code existant :

4/4

Comment?

Composants Autonomes

4/4

ngModules

devient facultatif

Nouvelle Architecture d'une application

main.ts

app.component.ts

MatButton

login.component.ts

login.service.ts

Standalone

Standalone

Composants Autonomes

TP:

 

- Créer une nouvelle application angular sur la version 14 ou 15.

- Créer un composant pages/LoginComponent Standalone.

- Supprimer LoginComponent du tableau des "declarations" de votre module appModule.

- Ajouter LoginComponent dans le tableau des "imports" de votre module appModule.

- Spécifier l'utilisation du composant LoginComponent sur la route "login" dans votre routingModule.

?

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éait 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 le Pipe Async avec un Observable (Le pipe souscrit à votre place).

 

- Souscrire manuellement à 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 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 card aléatoire.

?

Endpoint api: formationangular.eu-gb.mybluemix.net

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/

Rxjs animated visualizer : https://rxviz.com

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 ./shared

Générer un module de fonctionnalité

Demander l'authentification

ng generate component pages/login

Et un composant login

ng generate service shared/services/login

Ainsi qu'un service login

Générer une Guard

ng generate guard shared/guards/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: 'login',
    component: LoginComponent,
    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 (normalement shared.module.ts).

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.

?

Angular + Material

@angular/material

Note: Des composants et un kit de développement.

Google

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 que Angular Material ?

Composants

Utilitaires

CDK

Theming

Component Dev Kit

Material

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

Importer les modules dans votre module principal app.module.ts ou dans vos composants standalones :

import { MaterialModule } from 'exemple de 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

 

Icons fonts.google.com/icons?selected=Material+Icons

Material Components

TP :

- Créer un composant headers/sidebar/sidebar.component.ts

- Créer un composant headers/topbar/topbar.component.ts

- Créer un composant pages/home/home.component.ts

- Utiliser mat-sidenav, mat-list, mat-toolbar, mat-icon, mat-button, mat-icon-button.

 

 

?

Rendu final attendu:

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 thème sur un des éditeurs éxistants pour Angular Matérial.

4. Ajouter le thème dans la configuration angular.json, dans la section style ajouter "mon-style.css".

?

5. Vérifier le rendu final.

Angular + OAuth2

@angular-oauth2-oidc

Note: L'authentification pour l’accès aux ressources back-end

Open-Source

L'authentification OAuth2 est composée d'un serveur d'authentification et d'une ou plusieurs resources back-end qui vérifieront la signature de votre jeton d'authentification.

OAuth2

Il suffit de fournir le jeton aux différents back-end d'un domain spécifique. *Il est possible d'effectuer la vérification de l'authentification sur une Gateway.

Angular Application

Client OAuth2

Auth Server

Interception
+
Renvoi d'un jeton valide.

Back-end

token

Authorization: Bearer token

Procédure d'authentification

OAuth2

1. Fournir au serveur d'authentification email et password.

2. Le serveur vous renvoi un clientId à fournir pour l'authentification OAuth2.

3. Appliquer le clientId à la configuration de votre client OAuth2.

4. Lancer la procédure d'authentification.

5. Vous avez maintenant un jeton de preuve d'authentification.

https://jwt.io

npm install angular-oauth2-oidc

OAuth2

Créer un service AuthService

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { OAuthService, AuthConfig } from 'angular-oauth2-oidc';

export interface LoginResponse { error?: string; success: boolean; }

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  
  get isLogged() {
        return this.oauthService.hasValidAccessToken();
  }
  
  constructor(public oauthService: OAuthService, public httpClient: HttpClient) {
          this.setConfiguration();
  }
  
  public login(email: string, password: string): Observable<LoginResponse> {}
  
  public logout() {}
  
  private setConfiguration() {}
  
}

Définition de la configuration

OAuth2

public setConfiguration() {
    this.oauthService.configure({
      scope: environement.oauth2.scope,
      requireHttps: environement.oauth2.requireHttps,
      tokenEndpoint: environement.oauth2.tokenEndpoint
    });
}

// dans un fichier environement.ts ajouter
export const environement = {
    ...
    oauth2: {
        scope: 'profile email',
        requireHttps: false,
        tokenEndpoint: `https://formationangular.eu-gb.mybluemix.net/token`
    }
};

Login

OAuth2

public login(email: string, password: string): Observable<LoginResponse> {
    return this.httpClient.post<any>(`${environement.formationApi}/user/login`, {
        email: email,
        password: password
    }).pipe(
      catchError((response: any) => {
        return of(false);
      }),
      mergeMap((account) => {
        if (account === false) {
          return of({ error: "Email or password failed", success: false } as LoginResponse);
        }
        return from(this.oauthService.fetchTokenUsingGrant(`${environement.oauth2.scope}`, {
          clientId: account.clientId,
          clientSecret: account.clientSecret
        })).pipe(map(x => {
            return { success: this.oauthService.hasValidAccessToken() } as LoginResponse
        }));
      })
    );
 }

Logout

OAuth2

public logout() {
  this.oauthService.logOut();
}

Maintenant il ne vous reste plus cas créer un formulaire d'authentification qui utilisera la fonction login du service AuthService.

OAuth2

Spécifier les serveurs de type 'resources' à qui fournir le jeton d'authentification lors de leurs utilisations.


imports: [
  ...,
  OAuthModule.forRoot({
    resourceServer: {
      allowedUrls: [environment.formationApi],
      sendAccessToken: true
    }
  })
]

OAuth2

À chaque appel vers vos resources le jeton d'authentification est fourni en paramètre de header 'Authorization' de type Bearer.

Session Storage: 

Appel d'une resource API:

Autres protocoles d'authentifications pour les entreprises:

OAuth2

S'appuie sur OAuth2, fourni des jetons spécifiques en fonction des domain's resources

Protocol signé et encapsulé en XML.

La librairie oidc est compatible OpenID

TP Oauth2

TP:

 

- Reproduire le service d'authentification présenté.

 

- Créer un composant login et une route login.

 

- Utiliser la fonction login du service.

 

- Déplacer l'utilisateur vers la page principale de l'application si l'authentification réussie.

?

WebSockets

new WebSocket('ws://');

Note: Créer des événements dans vos applications.

Quesque c'est le WebSocket ?

WebSockets

- Un protocol de communication utilisé pour envoyer et recevoir des messages sur internet.

- Comme Http, mais sous stéroïdes ...

- Une connexion http persistante entre un serveur et un client.

- Facilite la création d'applications comme :

  - Chat
  - Notifications

  - Jeux en ligne

  - Plateformes de trading

  - Data visualisation dashboards

  - Applications collaboratives

 

Mise en application sur Angular

WebSockets

ng g service services/Websocket

Créer un service:

Utiliser le WebSocket Service disponible dans ce répertoire :

TP WebSocket

TP:

 

- Reprendre le service WebsocketService.
 

- Utiliser la route GET /channels.

 

- Lister les canaux sur la gauche de l'écran.

 

- Ajouter un événement pour déclencher la sélection d'un channel.

?

Partie 1

TP WebSocket

TP:


- Liste des routes à utiliser:

      - GET /channel/{title}  (Au load d'un channel)

      - POST /channel/{title}/message/add

 

- Quand un channel est sélectionné afficher ça liste de messages déjà postés.

 

- Utiliser websocketService, souscrire à tous les canaux puis quand un utilisateur poste un nouveau message il faut le voir apparaître.

?

Partie 2

Angular + Redux

@ngrx

Note: Angular Redux / vuejs vuex

Redux ?

"Redux is a predictable state container for JavaScript apps"

  • Déroulement facile à comprendre étape par étape.
  • Sauvegarde uniquement les données sous forme d’état.

3 Principes de redux

Une seule source de confiance

L'état de toute votre application est stocké dans un arbre d'objets se trouvant lui même dans un store unique.

Etat read-only

L'état de votre application est "immutable", la seule manière de le modifier est d’émettre une action (dispatching).

 Chaque changement est fait en fonction pure

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

Une seule source de confiance ?

  • Facilite le debug
     
  • Les données venant du serveur n'ont pas à se trouver dans plusieurs endroits

Etat 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

Chaque changement est fait en fonction pure ?

  • une fonction "pure" retournera toujours le même output pour un input donné, ce qui évite/interdit les effets de bords indésirables
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({
        counter: counterReducer
    })
  ]
})
export class AppModule {}

Dans le module

Key

Reducer associé

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 :

  1. Store
  2. Effects
  3. DevTools
  4. RouterStore
  5. Rxjs

Outils développeurs

 

@ngrx/store-devtools

 

Avec l'extension Chrome/Firefox de debug

Effets secondaires

https://ngrx.io/guide/effects

 

npm install @ngrx/effects

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 suivants :

   - redux/cards.action.ts

   - redux/cards.reducer.ts

   - redux/cards.effects.ts 

 

Faite comme le formateur, suivre les informations fournis dans la documentation @ngrx/effects.

Objectif : Gérer les erreurs http et la récupération des cards dans un seul observable. 

?

Tests Unitaires

2 + 2 = 4

ng test

Pourquoi tester ?

1. Pour rendre votre application non régressive.

 

2. Verifier la redondance de la relation d'entrée / sortie d'un cas donné.

 

3. Permettre les montées de versions sans problèmes.

Karma

Présentation du fichier de configuration karma.conf.js

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 service nommé IpService.

- Ajouter une variable ip = '127.0.0.1' dans le scope du service.

- Dans le fichier .spec.ts vérifier que la variable ip corresponde bien à un format attendu.

- Executer la commande `ng test`.

?

Jasmine Spying Créer des mocks

        // 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.and.returnValue('stub value');

Autre Spy :

  spyOnAllFunctions(obj)

Le plus utilisé

it('#getValue should return stubbed value from a spy', () => {
  // Valeur de test
  const stubValue = 'stub value';
  
  // Espionnage
  let valueServiceSpy = spyOn(ipService, 'getValue');
  valueServiceSpy.and.returnValue(stubValue);

  // Évaluations
  expect(ipService.getValue())
    .toBe(stubValue, 'service returned stub value');
  expect(valueServiceSpy.calls.count())
    .toBe(1, 'spy method was called once');
  expect(valueServiceSpy.calls.mostRecent().returnValue)
    .toBe(stubValue);
});

Test du/des retour(s) d'une fonction éspionnée.

Les Tests Unitaires

TP:

 

- Dans le service IpService ajouter une fonction nommée getIp qui retourne un Observable<string>, retourner une valeur factice pour le moment. (of(''))

 

- À l'aide d'un des Spy forcer la fonction getIp à retourner une string '127.0.0.1'.

 

- Tester l'appel de la fonction getIp et vérifier que la valeur de retour correspond bien à une adresse ip mocké.

 

 

?

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

beforeEach(() => {
  TestBed.configureTestingModule({
      providers: [
        { provide: HttpClient, useClass: MockHttp },
        { provide: OAuthService, useClass: MockOAuth }
      ]
    });
  service = TestBed.inject(AuthService);
  ...
});

Puis on demande ce service à TestBed quand on en à besoin.

Créer une classe mock

ValueMockService

export class ValueMockService {
  
  getValue(): string {
    return '';
  }

}

Définition des fonction utilisées.

Définition d'un retour factice.

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 de HttpClient

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 la fonction getIp du service IpService recupérer votre adresse ip en contactant la route /get-ip (Utiliser HttpClient).
(Veuillez vous référer au swagger fournis au début de la formation il y'a une transformation à effectuer).

 

- Dans le fichier de spec utiliser TestBed, créer un module de test et fournisais HttpClient à l'aide de l'importation du module HttpClientTestingModule.

 

- Mocker le retour de la fonction get du httpClient.

 

- Tester l'appel de la fonction getIp et vérifier la valeur de retour.

 

 

?

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 = getComputedStyle(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:

- Générer une configuration karma: ./node_modules/karma/bin/karma init

- Ajouter l'option
"karmaConfig":"karma.conf.js" à la commande "test" dans angular.json.

 

- Configurer karma.conf pour que les tests ne soient lancés qu'une seule fois.

 

- Configurer karma.conf pour tester l'application sur Firefox.

 

- (Option) Créer et inspecter de bout en bout un nouveau composant qui contient 3 div's avec du text de 3 couleurs css differentes.

?

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

Angular > 12 ?

Vous avez besoin du super-set protractor.

npm install protractor@7.0.0

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       // classe optionnelle pour la réutilisation de code.
     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)

Protractor Helpers

Naviguer

Cliquer et vérifier :

$$('content').first().click().then(() => {
   browser.sleep(5000);
   expect($$('mat-card mat-card-title').first()
          .getText()).toEqual('sfsfsff');
})
browser.get(browser.baseurl+'/link');

Tests E2E

TP:

 

- Créer un test complet.

 

- Naviguer sur la page login

 

- Attendre le chargement de la page.

 

- Selectionner le premier élément 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 puis corriger toutes les erreurs associés.

 

- Appliquer l'optimisation de la directive *ngFor grâce au paramètre 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çons de ranger votre projet.

1. Un Module par fonctionnalité.

 

2. Ranger les services dans des dossiers services.

 

3. Ranger les composants utilisés par le routing dans un dossier pages.

 

4. Pour redux toujours créer un dossier store dans les modules utilisant les stores. 

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 (Librairies pure JS par exemple).

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

Déclenchement des détections uniquement lors de réinstantiation d'input.

Note: À utiliser dans des composants qui ont des variables de types primitifs (boolean, string, number, float, ...)

@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.

- Faire un petit script dans 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:

 

- Appliquer une locale EN dans le module app.module.ts

 

- Utiliser le pipe currency avec un grand nombre.

 

- Utiliser le pipe number avec un grand nombre.

 

- Changer la locale manuellement en FR puis constater la différence d'affichage des nombres.

?

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.

?

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

  1. Le navigateur demande le code au serveur
  2. Il lit le code de toute l'application
  3. Il génère les templates et les affichent

Après

  1. Le navigateur demande le code au serveur
  2. Il lit le code du module principal
  3. Il génère un template et l'affiche
  4. 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

  1. Créer un module de fonctionnalité
  2. Configurer les routes principales
  3. Configurer le module de routage (app)
  4. 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

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é.

Server Side Rendering

Installer Angular Universal et demarrer un serveur.

?

TP:

Tester des appels vers votre application avec curl.

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

Angular - Using MSW (Mock Service Worker)

Micro REST/GraphQL API pour mocker vos API utilisés par vos applications front-end et back-end en Node.

MSW

Permet un développement de vos applications offline.

Créer des mocks robustes en quelques lignes.

Angular Application

Request REST

MSW

Interception
+
Envoi de la réponse mock

/api/xxx

Installation :

MSW

npx msw init src

1. Executer la commande suivante :

// angular.json
{
  ...
  "build": {
    "builder": "@angular-devkit/build-angular:browser",
    "options": {
      "outputPath": "dist/ng-msw",
      "index": "src/index.html",
      "main": "src/main.ts",
      "polyfills": "src/polyfills.ts",
      "tsConfig": "tsconfig.app.json",
      "aot": true,
      "assets": ["src/favicon.ico", "src/assets", "src/mockServiceWorker.js"],
      "styles": ["src/styles.css"],
      "scripts": []
    }
  }
  ...
}

2. Ajouter src/mockServiceWorker.js au assets du build :

MSW

// ng g service services/GitHub puis inserer ce code.
@Injectable({
  providedIn: 'root',
})
export class GitHubService {
  constructor(private http: HttpClient) {}
  getUser(username: string) {
    return this.http.get(`https://api.github.com/users/${username}`)
  }
}

3. Création d'un service 'Github' sur une application vide.

MSW

4. Utiliser le service dans un composant à l'aide d'un input puis afficher le résultat de l'appel :

MSW

// ./src/mocks/browser.ts

import { setupWorker, rest } from 'msw'
export const mocks = [
  rest.get('https://api.github.com/users/:user', (req, res, ctx) => {
    const { user } = req.params
    return res(
      ctx.status(200),
      ctx.json({
        name: `mocked-${user}`,
        bio: 'mocked-bio',
      }),
    )
  }),
]
const worker = setupWorker(...mocks)
worker.start()
export { worker, rest }

5. Création de notre mock :

MSW

// dans main.ts par exemple ou bien un fichier spec.ts

import './mocks/browser'

6. Importer votre mock là ou vous en avez besoin :

7. `ng serve` puis constaté que le mock intercepte les requêtes et renvoi un résultat mocké.

Enjoy, vous pouvez maintenant profiter pleinement des possibilités offertes par MSW !

Angular - Concepts avancés v2 - Experts

By AdapTeach

Angular - Concepts avancés v2 - Experts

Présentation des concepts avancés du framework Angular. Formation 2 jours

  • 422