INTRODUCTION TYPESCRIPT / ES6

 

ES6, le nouveau JavaScript

TypeScript, le typage du JavaScript

Les bases en TypeScript

 

(TP) : s'initier au TypeScript

ES6, le nouveau Javascript

 

Note: ES6 = ES2015 = ECMAScript 6

Principales fonctionnalités

let / const versus var :

- const : constantes

- let : variables

Ce qui équivalait auparavant à var.

 

Différence :

- var : "top hoisted"

=> Une variable pouvant être utlisée avant d'être déclarée.

- let / const : "block scoped"

=> Ne peuvent être utilisées hors de leur scope

Principales fonctionnalités

function f() {
  var x = 1;
  let y = 2;
  const z = 3;
  {
    var x = 100;
    let y = 200;
    const z = 300;
    console.log('x in block scope is', x);
    console.log('y in block scope is', y);
    console.log('z in block scope is', z);
  }
  console.log('x outside of block scope is', x);
  console.log('y outside of block scope is', y);
  console.log('z outside of block scope is', z);
}

Principales fonctionnalités

Array helpers :

- forEach :

var colors = ['red', 'green', 'blue'];

function print(val) {
  console.log(val);
}

colors.forEach(print);

Principales fonctionnalités

Fonction Lambda :

const fonctionNormal = function(a, b) { return (a + b); }
const fonctionLambda = (a, b) => a + b;

console.log(fonctionNormal(1, 2)); // 3
console.log(fonctionLambda(1, 2)); // 3 

La différence entre les deux et que la lambda garde le context du scope.

Principales fonctionnalités

Array helpers :

- map :

var colors = ['red', 'green', 'blue'];

function capitalize(val) {
    return val.toUpperCase();
}

var capitalizedColors = colors.map(capitalize);

console.log(capitalizedColors);

Principales fonctionnalités

Array helpers :

- filter :

var values = [1, 60, 34, 30, 20, 5];

function lessThan20(val) {
    return val < 20;
}

var valuesLessThan20 = values.filter(lessThan20);

console.log(valuesLessThan20);

Principales fonctionnalités

Array helpers :

- find :

var people = [
  {name: 'Jack', age: 50},
  {name: 'Michael', age: 9}, 
  {name: 'John', age: 40}
];

function teenager(person) {
    return person.age > 10 && person.age < 20;
}

var firstTeenager = people.find(teenager);

console.log('First found teenager:', firstTeenager.name);

Principales fonctionnalités

Classes :

"Sucre syntaxique" ajouté aux héritages et chaînes de prototypes

class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }

    toString() {
        return '[X=' + this.x + ', Y=' + this.y + ']';
    }
}
let P1 = new Point(3, 4);// Nouvelle instance de Point

Principales fonctionnalités

Template strings :

function hello(firstName, lastName) {
  return `Good morning ${firstName} ${lastName}! 
How are you?`;
}

console.log(hello('Jan', 'Kowalski'));

` backquote

Principales fonctionnalités

Arguments de fonctions par défaut :

function sort(arr = [], direction = 'ascending') {
  console.log('I\'m going to sort the array', 
  arr, direction)
}

sort([1, 2, 3])
sort([1, 2, 3], 'descending')

Principales fonctionnalités

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');
sort([1, 2, 3], ... ['descending', 'blabla']);

Principales fonctionnalités

Les promesses :

But : traiter des opérations asynchrones.

 

const maPromesse = new Promise((resolve) => { resolve(true) });

 

Elles peuvent être chaînées par :

- then() : callback

- catch() : erreur

Principales fonctionnalités

Les promesses :

function asyncFunc() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const result = "Un résultat une seconde plus tard !";
      resolve(result);
    }, 1000);
  });
}

for (let i = 0; i < 10; i++) {
  asyncFunc()
    .then(result => console.log('Result is: ' + result))
    .catch(result => console.log('Error: ' + result));
}

Principales fonctionnalités

Les promesses :

const maPromesseQuiAttend1seconde = new Promise((resolve, reject) => {
  setTimeout(() => {
    const result = "Un résultat une seconde plus tard !";
    resolve(result);
  }, 1000);
});

async function asyncFunc() {
  await maPromesseQuiAttend1seconde();
}

// maintenant j'ai plus cas executer cette fonction
// dans un context asynchrone (Ici dans index.js à l'aide de node index.js):
(async () => {
  await asyncFunc();
})();

Les bases en TypeScript

 

Caractéristiques :

- syntaxe proche de Javascript

- extension .ts 

- typage fort

const fooNumber: number = 0;
const fooString: string = 'Blah !';

// Types génériques
const fooArray: Array<string> = ['Blah !'];

// Custom types
const fooType: FooType = new FooType();
const fooTypeArray: Array<FooType> = [new FooType()];

Des paramètres typés

Concrètement :

const fooBlah: Array<string> = [];

fooBlah.push('Blah blah');
// Tout fonctionne correctement

fooBlah.push({parler: true, contenu: 'Blah blah'});
// error TS2345
// Argument of type {} is not assignable to parameter of 
// type 'string'.

Typage dynamique, unifier

Et si on ne connaît pas le type ?

=> type dynamique "any"

const undefinedType: any

On peut aussi utiliser l'union de types : 

const undefinedType: string | number;
undefinedType = 'Blah';
undefinedType = 25; // ✌️

Que peut-on typer ?

let n: number = 1;
const s: string = 'Hello';

Paramètres de fonctions : 

function f(i: number) { ... }

Retour de fonction : 

function f(): number {
  return 42;
}

Variables :

Types basiques :

Booleans: 

let fini: boolean = false

Numbers:

Nb : En plus des décimales et hexadécimales, Typescript supporte aussi les types de littéraux binaires et octaux introduits par ES6.​

let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;

Types basiques :

Strings:

let color: string = "blue";
color = 'red';

Nb : Typescript supporte aussi les "template strings", introduites par ES6.​

let fullName: string = `Bob Bobbington`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ fullName }.

I'll be ${ age + 1 } years old next month.`;

Types basiques :

Arrays :

Deux façons de typer les arrays :

let list: number[] = [1, 2, 3]; //elemType[]
let list: Array<number> = [1, 2, 3];  // Array<elemType>:

Tuples : 

Permet de typer un tableau où le type de certains éléments :

// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error

Typages implicites :

Lorsqu'il s'agit de types basiques, le compilateur associe par défaut les types des variables non annotées :

let n = 1                         // let n: number = 1

let s = "Hello World"             // let s: string = "Hello World"

n = s;                            // COMPILATION ERROR
s = n;                            // COMPILATION ERROR

function f() {                    // function f(): string {
    return "hello"                
}

Les bases de Typescript : 

Déclarer un type custom :

=> créer une classe "Format classique"

class Canard {
    race: string;
    pleinAir: boolean;
    fermier: boolean;

    constructor(race: string, pleinAir: boolean, fermier: boolean) {
        this.race = race;
        this.pleinAir = pleinAir;
        this.fermier = fermier;
    }
}

const canard1 = new Canard('x', false, false);

Les bases de Typescript : 

Déclarer un type custom :

=> créer une classe "Format Optimisé"

class Canard {
    constructor(
  		public race: string,
        public pleinAir: boolean,
        public fermier: boolean) {
    }
}

const canard1 = new Canard('x', false, false);

Les bases de Typescript : 

Déclarer un type custom :

=> créer une interface

interface Animal {
    nom: string;
    bruit: string;
}

const anime = (arg: Animal) => `${arg.name} fait ${arg.bruit}`;

const canard: Animal = {nom: 'Canard', bruit: 'coin coin'};
anime(canard) // Ok 

Les bases de Typescript : 

Classes et interfaces :

Une interface peut être implémenté dans une classe :

(Cela force le développeur à appliquer la structure définie). 

interface Animal {
    race: string;
    pleinAir: boolean;
    fermier: boolean;
}

class Canard implements Animal {
    race: string;
    pleinAir: boolean;
    fermier: boolean;

    constructor(race: string, pleinAir: boolean, fermier: boolean) {
        this.race = race;
        this.pleinAir = pleinAir;
        this.fermier = fermier;
    }
}

new Canard("Canard", true, false);

Les bases de Typescript : 

Le réel intérêt des interfaces :

 

Interface (classe 'allégée') =>

 

- l'interface sert à vérifier les types --> effacée de l'output final

 

- L'utilisation de librairies sans interfaces c'est un calvaire 

Les bases de Typescript : 

Déclarer un type :

=> créer un typage

type numberAndString = number | string;

const jeuSwitch: numberAndString = 1; // Ok
const jeuSwitch2: numberAndString = '1'; // Ok

const jeuSwitchCasse: numberAndString = true; // Error
// Impossible d'assigner le type 'boolean' au type
// 'numberAndString'.

Les bases de Typescript : 

Les décorateurs :

 

Déclarations particulières :

- propres à Typescript

- attachées à des classes, méthodes, paramètres, etc.

- Équivalent aux Annotations en Java / .net

 

Syntaxe: @expression ('expression' --> une fonction appelée à l'instanciation de l'élément décoré) 

 

En Angular:  attacher des "métadonnées" propres au framework

Les bases de Typescript : 

Les décorateurs :

 

Un exemple de décorateur très commun : le composant !

import { NgModule, Component } from '@angular/core';

@Component({
  selector: 'composant-exemple',
  template: '<div>Woow un composant !</div>',
})
export class ExempleComposant {
  constructor() {
    console.log('Hello, je suis un composant');
  }
}

Les bases de Typescript : 

Les décorateurs :

 

Décorateur = Macro qui permet d'obtenir des informations sur un objet sans avoir à l'instancier.

 

=> ici permet à Angular de :

- définir 'ExempleComposant' comme composant.

- configurer 'ExempleComposant'.

- Savoir qu'il doit instancier t'elle ou t'elle classe au démarrage.

- Trouver un composant.

Les bases de Typescript : 

TP :

 

Installer le compilateur Typescript: npm install -g typescript

 

Créer un fichier en Ts :

- Déclarer une interface "Animal"

- Créer deux classe (Chat et Chien) qui implemente l'interface

- Instancier les deux classes dans une fonction et tester les fonctionnalités ES6 (helper functions, template strings...)

 

Puis compiler en Js: tsc nomDuFichier.ts

Ensuite exécuter votre fichier `node nomDuFichier.js`

?

Les nouveaux opérateurs :

Les Null coallesing:  

const result = firstResult ?? secondResult;
// firstResult ?? = si firstResult === null || firstResult === undefined

L'optional Chaining:  

// avant
let result = data ? (data.key1 ? data.key1.key2 : undefined) : undefined;
// après
let result = data?.key1?.key2;
// de même sur les arrays
array?.[0]?.['key'];
// sur les méthodes possibles
obj.method?.();
// le mix des deux
let result = data?.key1?.key2 ?? 'default';

O

Depuis la version 3.7 TypeScript Disponible depuis Angular 9

Les nouveaux opérateurs :

Les Options

interface Animal {
  name?: string;
  legCount?: number;
  armCount?: number;
}

class Chat implements Animal {
  public legCount = 4;
  constructor(public name: string) {}
}

const jarvis = new Chat('Jarvis');
console.log(jarvis.legCount === 4); // true
console.log(jarvis.armCount === undefined); // true

O

Depuis la version 3.7 TypeScript Disponible depuis Angular 9

Typescript,

le typage du Javascript

 

Note: *version latest 5.7.2

Qu'est-ce que TypeScript ?

Définition : 

TypeScript est un 'superset' de JavaScript

 

 

Compilation :

Avant exécution, le code TypeScript est transpilé en Javascript

par le compilateur TypeScript.
 

 

TypeScript = JavaScript + typage statique
Remarque: En revanche, du code Javascript n'a pas besoin de transpilation pour être inclus dans du code TypeScript.

(sur-couche)

Pourquoi TypeScript ?

Autres langages compilant en JavaScript : 

CoffeeScript, Dart, Clojure, Nim, Reason, Haxe...

 

Langages ajoutant du typage dynamique: Dart, Elm...

 

Avantage de TypeScript:

Etant une sur-couche, TypeScript permet l'inclusion de code JS

=> flexibilité


 

 

Exemple: Inclusion directe de dépendances JS, sans recours à librairie / package externe.

(langages fonctionnels)

Typescript - typage statique

- Le typage dynamique vs statique

 

- Cas d'usages de typages dynamiques / statiques

Et ensuite ?

Typage dynamique / statique

Définition :

Le typage désigne l'attribution d'un 'type' de données aux variables manipulées présentes dans un script.

- dynamique:

     - pas de typage lors de l'instanciation d'une variable

     (- en réalité, un typage 'implicite' pouvant évoluer)

- statique:

     - typage à l'instanciation d'une variable

     - étape de compilation vérifiant la cohérence du typage

Le typage dynamique

let fooBar = 'Foo Bar';
fooBar = 25;      // Ok!

Application :

En TypeScript:

En Javascript:

let fooBar: string = 'Foo Bar';

fooBar = 25;
// error TS2345
// Type 'number' is not assignable to parameter of 
// type 'string'.

Types statiques vs dynamiques


JavaScript est typé dynamiquement :

let fooBar = 'Foo Bar'; fooBar = 25;      // Ok!

Le problème :

const fooCat = { name: 'Chat', color: 'brown' }; 
const fooDog = { play: true, color: 'black' }; 
const printColor = animal => console.log(animal.color);// Ok!
const printPlay = animal => console.log(animal.play);// Err

Sans typage statique aucune structuration possible.

Note: dynamique équivaux au runtime

Types statiques vs dynamiques

Sans typage, peu de ressources disponibles :

- lire la documentation

- lire le code de la fonction

- ..

=> complexifie code + utilisation API / librairies...

 

Avec le typage Typescript :

 

- un code plus efficace (IDE) 

- debuggage et testabilité simplifiés

 

Programme - Jour 1-2

LES BASES DU FRAMEWORK
Comprendre la philosophie du framework
Angular CLI, un outil pour tout générer
TP: Première application et outillage
 

COMPOSANTS
Web Components
Décorateurs Angular Composants standalones
Property binding, envoyer des données au composant
Event binding, évènements personnalisés
Cycle de vie
TP : Mon Premier composant


DIRECTIVES
Directive : fonctionnement et création
Les directives fournies par Angular
Attribute directives
Structural directives
Directives custom
TP : Première directive

ANGULAR 17

Nouvelles fonctionnalités du framework

@defer

@if, @else if, @else, @for, @empty, @switch

 

PIPES
Les transformateurs fournis
Formater une chaîne
Formater des collections
Utiliser un pipe comme un service
TP : Créer ses propres pipes

LES BASES DU FRAMEWORK


Comprendre la philosophie du framework
Angular CLI, un outil pour tout générer


(TP) : Première application et outillage

Comprendre la

philosophie

du framework

A

framework

Angular ("Angular 2") est un framework :

 

- Placé côté client

- Application compilé

- Fonctionnel sur navigateur, web workers, mobiles

- Utilisant le gestionnaire de package npm pour vos dépendances

- Maintenu à jour (3 maj / an)

 

 

Note : Développé par l'Angular Team paru en septembre 2016.

Note : Il s'agit d'une refonte totale d'AngularJS (créé en 2009) avec lequel il ne doit pas être confondu.

Framework côté client 

Différences du framework front / back :

- back: navigateur construit le DOM en "parsant" un document HTML prêt à être rendu

- front: navigateur construit le DOM en interprétant un script

 

Avantages : limite les intéractions avec les serveurs

=> navigation très fluide (en particulier en web mobile).

 

Inconvénient : Un chargement au démarrage qui peut être long (conseillé < 250kb)

Exemple de réponse d'un serveur au lancement d'une application Angular

Text

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Angular Sample App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>

<body>

// Le composant racine de notre application
<app-root></app-root>    

// L'ensemble des scripts permettant la construction du DOM à partir du composant root
<script type="text/javascript" src="inline.bundle.js"></script>
<script type="text/javascript" src="polyfills.bundle.js"></script>
<script type="text/javascript" src="scripts.bundle.js"></script>
<script type="text/javascript" src="styles.bundle.js"></script>
<script type="text/javascript" src="vendor.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>

</body>
</html>

Le concept des SPA

Single Page Application :

une fois instanciée au chargement, l'application n'a plus besoin de reload auprès du server pour fonctionner.

 

 

Angular CLI,

un outil pour

tout générer

>_

Note: ng --help*

Angular CLI, un outil pour tout générer

Installation :

npm install -g @angular/cli@latest

 

Principaux CLI :

- ng new <nomDeVotreFutureApplication>

- ng generate || ng g : génération d'éléments (conseil essaie 'ng g --help')

- ng serve : Démarre un serveur de développement

- ng build : Compile votre application dans le dossier ./dist

?

Création de votre première application Angular

Extrêmement simple :

ng new applicationAngular
cd applicationAngular
ng serve

=> http://localhost:4200   \^^/

?

Architecture standard d'une application Angular

// les dépendances avec npm
|- node_modules/

// l'endroit où les fichiers de build seront mis
|- dist/
|- src/
  |----- public/       // Ressources
      |----- favicon.ico
  |----- app/          // ici ca va coder sec !
      |----- app.component.css|html|spec.ts|ts
      |----- app.config.ts // config principal (Equivalent à Module)
      |----- app.routes.ts // routing
...
  |----- index.html
  |----- main.ts
  |----- styles.css
  |----- test.ts
|- .gitignore          // .gitignore près généré
|- angular.json        // configuration des commandes angular
|- package.json        // configuration principal
|- README.md
|- tsconfig.app.json   // tsconfig.json (Configuration typescrypt)
|- tsconfig.json       // configuration compiler typescript

Les principaux fichiers

main.ts :

 

import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig)
  .catch((err) => console.error(err));

*"rdv dans le dossier src/"

 

Définition de la racine de l'application.

Les principaux fichiers

app.config.ts :

 

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    
    provideZoneChangeDetection({ eventCoalescing: true }),
    // Service Gestionnaire des detections de changement Angular
    
    provideRouter(routes)
    // Service Gestionnaire des routes
    
  ]
};

*"rdv dans le dossier src/app/"

 

Définition du router principal en tant que premier élément à instancier à l'aide de la balise providers.

Les principaux fichiers

app.component.ts - notre premier composant !

 

import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true, // par default depuis angular 17
  imports: [CommonModule, RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'formation';
}

Les principaux fichiers

app.component.html

 

<div>
  hello world
</div>

Le fichier ou l'on va écrire le html du composant.

Les principaux fichiers

index.html

 

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Formation</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

Le fichier static

Les principaux fichiers

Ordre de l'initialisation :

 

index.html

main.config.ts

main.ts

app.component.ts

main.config.ts

app.component.css

app.component.html

Les principaux fichiers

Prenons le temps pour décortiquer le fonctionnement de base ensemble de façon chill !

 

Les principaux fichiers

Des Néophytes de nodejs dans la salle?

 

Explications approfondies du manager de package npm

Les principaux fichiers

angular.json

 

Est-il possible de faire plusieurs projets dans le même répertoire ? OUI
 

C'est ici que l'on peut ajouter des fichiers js pure, des fichiers à inclure dans le build final.

 

...

COMPOSANTS

 

Décorateurs Angular

Property binding, envoyer des données au composant
Event binding, évènements personnalisés
Cycle de vie
 

(TP) : Premier composant

Composants

Angular

Note: Voir cela comme un tag custom.

<div> </div>

Composants Angular :

Reprennent le principe des tag custom natifs.

 

 

+ ajout de nombreuses API (encapsulation du style, injection de dépendances). 

 

 

 

 

- Composants Angular => propre système de création d'éléments custom (ngFactories).

Composants Angular :

@Component({
    selector: 'greet', 
    standalone: true,
    templateUrl: './greet.component.html',
    styleUrl: './greet.component.css'
})
class Greet {
  name: string = 'World';
}
<greet>Hello World!</greet>

Ts:

Html résultat une fois utilisé:

Décoration

Classe

Hello {{ name }}

Html:

Un nouveau composant :

ng generate component exemple

Création d'un nouveau composant :

La CLI se charge de :

1. création de 4 fichiers :

   |exemple

     | exemple.component.(css,html,spec.ts,ts)

2. utilisation du composant dans app.component

   |app.component.ts (Importation)
   |app.component.html (Déclaration)

?

Importation du composant :

...
import { ExempleComponent } from './exemple/exemple.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CommonModule, RouterOutlet, ExampleComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  title = 'formation';
}

Fichier app.component.ts :

Déclaration du composant :

<app-exemple></app-exemple>

Fichier app.component.html :

Résultat:

import {Component} from '@angular/core';

@Component({
  selector: 'app-uncomposant',
  standalone: true,
  imports: [],
  templateUrl: './uncomposant.component.html',
  styleUrl: './uncomposant.component.css'
})
class UnComposant {}

Décorateurs composants

<app-uncomposant></app-uncomposant>

Un composant est une classe pourvue d'un décorateur @Component({}) :

Déclaration

@Component({ 
  selector?: string
  standalone?: boolean
  templateUrl?: string
  template?: string
  styleUrls?: string[]
  styles?: string[]
  changeDetection?: ChangeDetectionStrategy
  encapsulation?: ViewEncapsulation
  interpolation?: [string, string] // par defaut à ["{{", "}}"]
  providers?: Provider[],
  imports?: any[]
})

Décorateurs composants

Le décorateur @Component({}) permet de configurer le composant en passant des métadata en paramètre au décorateur :

import {Component} from '@angular/core';

@Component({
    selector: 'un-composant',
    standalone: true,
    template: `<h1>Wow - un composant !</h1>
               <div>Je suis un composant</div>`, 
    styles: [`h1 { color: red}
              div {color: blue}`]
})
class UnComposant {}

Décorateurs composants

En pratique, on se sert principalement des paramètres selector, standalone, template (ou templateUrl) et styles (ou styleUrls) :

@Component({
    selector: 'no-encapsulation',
    templateUrl: './no-encapsulation.html',
    styleUrls: ['./no-encapsulation.css'], 
    encapsulation: ViewEncapsulation.None // Supprime le principe d'encapsulation
})
class NoEncapsulation {}

Décorateurs composants

Question : Le style défini dans un composant parent sera-t-il appliqué à un composant enfant ?

Non => principe de View Encapsulation, ( composant = vue isolée)

Pour forcer l'héritage : 

*L'encapsulation d'angular utilise Shadow DOM

sans quoi angular doit utiliser les composants web natifs.

Composants

Data binding

Note: Échange de données entre vue et composants.

[bind out]     <-

(bind event) <-

{{var bind }} ->

Data binding

échanger des données

One-way :

Data binding

échanger des données

Two-way :

Data binding

échanger des données

Angular nous permet d'échanger des données entre :

- le composant et la vue

     - one way : (vue => composant)

                         {{ composant => vue}}

                         [composant => vue]

     - two ways [(vue <=> composant)]

- entre les différents composants à l'aide des décorateurs

     - par @input (parent => enfant)

     - par @output (enfant => parents & siblings)

Data binding

composant <=> vue

Un grand avantage des composants est de nous permettre de manipuler des données dans le DOM - pour cela, nous avons besoin de pouvoir lier les datas entre le composant (plus exactement, son instance, déclarée dans le fichier .ts - que l'on peut assimiler au 'Model') et son template (la 'Vue'). 

 

Pour cela, Angular met à notre disposition plusieurs façons de 'binder' les datas. 

composant / vue: one-way

​A. Interpolation : {{ .. }}

La syntaxe {{ var }} permet de lier d'inclure une variable déclarée dans le composant dans le template

@Component({ ... })
export class ExempleComponent {
  public foo: string = 'Folle';
}

1. Composant => vue :

Je suis une {{ foo }}

ts

html

composant <=> vue: one way

​B. Property binding : [ .. ]

La syntaxe [prop] = "var" permet de lier la propriété d'un attribut d'un élément du DOM à une variable.

@Component({ ... })
export class ExempleComponent {
  public potDeMiel: string = 'Miel Orange';
}

Note: une modification depuis le DOM de la variable ce répercute dans le composant.

<div [title]="potDeMiel">
	Le titre de la div est {{ potDeMiel }}
</div>

ts

html

composant <=> vue: one way

​Event binding : ( .. )

La syntaxe (event) = "methode()" permet de lier un événement provenant de la vue à une variable ou méthode déclarée dans le composant.

2. Vue => composant :

(focus)="myMethod()"  // An element has received focus
(blur)="myMethod()"   // An element has lost focus

(submit)="myMethod()"  // A submit button has been pressed

(scroll)="myMethod()"

(cut)="myMethod()"
(copy)="myMethod()"
(paste)="myMethod()"

(keydown)="myMethod()"
(keypress)="myMethod()"
(keyup)="myMethod()"

(mouseenter)="myMethod()"
(mousedown)="myMethod()"
(mouseup)="myMethod()"

(click)="myMethod()"
(dblclick)="myMethod()"

(drag)="myMethod()"
(dragover)="myMethod()"
(drop)="myMethod()"

composant <=> vue: two ways

La directive ngModel : [( ngModel )]

La syntaxe [(ngModel)] = "variable" permet de lier la valeur d'un input à une variable d'un composant - qui peut ensuite être renvoyée puis actualisée à la vue.

 

En réalité, ngModel est une combinaison de l'event binding et de la property binding.

2. Vue => composant => vue :

composant <=> vue: two ways

La directive ngModel : [( ngModel )]

<div>
  <!-- la variable 'model' est updatée dans le composant
       à chaque fois que l'utilisateur modifie
       la valeur de l'input
   -->
  <input [(ngModel)]="model" type="text">
  <!-- le composant 'renvoie' la valeur actualisée
       à la vue 
  -->
  <p>Valeur actualisée : {{model}}</p>
</div>

Dans exemple.component.html

composant <=> vue: two ways

La directive ngModel : Implémentation

// exemple.component.ts

import { FormsModule } from '@angular/forms'; 

@Component({ 
    imports: [ FormsModule, ...], 
    ... })

Nb: ngModel nécessite l'import préalable de FormsModule : 

composant <=> vue: two ways

La directive ngModel : Implémentation

// exemple.component.ts

import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [FormsModule],
  templateUrl: './example.component.html',
  styleUrl: './example.component.css',
})
export class ExampleComponent {
  public model: string;  // Déclaration de la variable de binding
}

Data binding

composant <=> vue

TP 1:

 

Créer un composant "exemple" avec:

- une interpolation (controller => vue)      ex: {{ x }}

- un property-binding (controller => vue) ex: [style]=""

- un event-binding (vue => controller)       ex: (event)=""

- un ng-model (controller <=> vue)       ex: [(ngModel)]=""

?

Data binding

parent <=> enfant

Parent (controller / vue)  => enfant (vue)

- principe de transclusion (projection de contenue)

 

Parent (controller / vue)  => enfant (controller / vue)
- [property] / (event) bindings (parent)
- @Input / @Output (enfant)

 

Note: Parents / enfants directs uniquement.

Inter-comp : 

Transclusion : Parent (controller / vue)  => enfant (vue)

@Component({
  selector: 'app-transclusion',
  templateUrl: './transclusion.component.html'
})
export class TransclusionComponent {}
<!-- transclusion.component.html -->
<div class="toto-header">Je suis un header fixé</div>
<ng-content></ng-content><!-- contenu dynamique SALUT -->
<div class="toto-footer">Je suis un footer fixé</div>
<!-- app.component.html -->
<app-transclusion>SALUT</app-transclusion>

Application :

Component html :

Component :

Inter-comp : 

Input : Parent (controller / vue)  => enfant (controller)

Principe: 

- instanciation enfant avec donnée à transmettre en attribut

- 'reception' de l'attribut par le controller enfant via la décoration @Input

Inter-comp : 

Input : Parent (controller / vue)  => enfant (controller)

Instanciation enfant :

// Data non liée au controller parent
<child data="dataFromParent"></child>

// Data liée au controller parent
<child [data]="dataFromParent"></child>

Inter-comp : 

Input : Parent (controller / vue)  => enfant (controller)

Controller enfant :

// Ne pas oublier d'importer le décorateur Input
import { Component, Input} from '@angular/core';

@Component({
  selector: 'child',
  ...
})
export class Child {

  @Input() data: WineItem;

}

Inter-comp : 

Output : Enfant (controller)  => parent (vue puis controller)

Principe :

- à l'instanciation, lier un 'event' enfant à un callback parent

 

=> envoi de données: 

- l'enfant émet un 'event'

- l'event déclenche un callback au parent

- le parent est notifié de la donnée

 

Inter-comp : 

Output : Enfant (controller)  => parent (vue puis controller)

Composant parent :

// ParentComponent.html
<child (receivedData)="newDataCallback($event)"></child>


// ParentComponent.ts
@Component({ ...})
export class ParentComponent {
    data: any;
    newDataCallback(event: any): void {
        // update d'une variable locale
        this.data = event
    }
}

Inter-comp : 

Output : Enfant (controller)  => parent (vue puis controller)

Controller enfant :

import { Component, Output, EventEmitter } from '@angular/core';

@Component({ selector: 'child',... })
export class ItemDetailsComponent {

  // Lier la propriété 'dataToSend' à l'attribut 'receivedData'
  @Output('receivedData') dataToSend: EventEmitter<any>;

  constructor() {
    this.dataToSend = new EventEmitter<ISelectEvent>()
  }

  sendData(data): void { this.itemSel.emit({ data })  }
}

Data binding

Parent <=> Enfant(s)

TP 2:

 

Créer un ensemble composant parent / enfant avec :

- une transclusion (vue parent => vue enfant)

- un input statique et dynamique (vue/controller parent => controller enfant)

- un output (controller enfant => controller parent) avec un objet de données (ex: click + origine)

?

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

Cycles de vie d'un composant

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.

Nb2: ngOnInit est le moment ou nous avons toutes nos variables de prêttent car le ngOnChanges est passé avant.

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

 4. afterContentInit

 5. afterViewInit

 

Émettre un message en console et visualiser le comportement. *doc: https://angular.dev/guide/components/lifecycle

?

DIRECTIVES


Fonctionnement et création

Les directives fournies par Angular
Attribute directives
Structural directives

Custom directives
 

(TP) : Première directive

Directives fonctionnement

et création

Note: Composant sans vue.

Attribute

Structural

Custom

  • ngClass
  • ngStyle
  • *ngIf
  • *ngFor
  • *ngSwitch

Your custom directives

Les directives :

Définition :

Classe associée à une balise (idem composant), mais sans vue.

Nb : composants = directives particulières jusqu'à Angular 2.

 

Intérêt :

Faire exécuter du code au navigateur qui modifie des éléments présents dans le DOM.

Les directives :

Le problème :

Composants => créer des vues dans de le DOM

 

Mais ne permettent pas de modifier des éléments existants du DOM, comme :

- structure des noeuds

- comportement des élements

 

Les directives :

import { Directive, Renderer2, ElementRef, HostListener } from '@angular/core';

@Directive({selector: '[appHoverEffect]'})
export class HoverEffectDirective {

  constructor(private renderer : Renderer2, private elementRef: ElementRef) { }

  @HostListener('mouseover')
  applyHover() { 
    this.renderer.setStyle(this.elementRef.nativeElement, 'color', 'red');
  }

}

Déclaration :

Les directives :

// Element standard du DOM
<div appHoverEffect> App hover ! </div>

// Autre
<directedComponent appHoverEffect><directedComponent>

Instanciation :

Note: nouvelle instance de directive créée à chaque rencontre de la balise.

Les directives :

Exemples d'utilisation:

- itération d'un élément DOM existant sur une liste - structure

- ajout / retrait dynamique d'un nœud - structure

- règles CSS à des éléments - comportement

- réponse à un événement - comportement

- etc...

Les directives :

Types de directives :

- les directives structurelles : modifient la structure du DOM

- les directives d'attributs : modifient l'apparence ou le comportement de certains éléments

 

 

Directives structurelles :

Syntaxe : *directive = "template expression"

- ' * ' : obligatoire pour directives structurelles - Nb : contrairement aux directives d'attribut, pas de [..] ou de (..).

- 'template expression' : expression retournant une valeur évaluée par Angular (contexte : leur composant) 

<div *ngIf="Angular"> Ok // </div>
<div *ngIf="AngularVersion > 9"> 🔥🔥 </div>

Note : les directives structurelles sont des sucres syntaxiques. D'ou le wildcard pour les différencier.

Dir. structurelles : NgIf

Permet d'afficher un élément selon la valeur d'une expression booléenne.

<div *ngIf="false"></div>
<div *ngIf="a > b"></div>
<div *ngIf="str == 'yes'"></div>
<div *ngIf="myFunc()"></div>

Note : appliquée à un composant, ngIf induit l'initialisation ou la destruction de celui-ci - cf la notion de 'cycle de vie' d'un composant.

Attention, NgIf fait apparaître ou disparaître l'élément auquel elle est attibuée du DOM - c'est donc par exemple différent d'un 'display: none'.

Dir. structurelles : NgSwitch

Fonctionne comme un 'switch case' traditionnel :

<div *ngIf="myVar == 'A'">Var is A</div>
<div *ngIf="myVar == 'B'">Var is B</div>
<div *ngIf="myVar == 'C'">Var is C</div>
<div *ngIf="myVar != 'A' && myVar != 'B' && myVar != 'C'"></div>
<ng-container [ngSwitch]="myVar">
  <div *ngSwitchCase="A">Var is A</div>
  <div *ngSwitchCase="B">Var is B</div>
  <div *ngSwitchCase="C">Var is C</div>
  <div *ngSwitchDefault>Var is something else</div>
</ng-container>

équivaut à :

Note : ng-container ne sera pas dans le html final. Très utile !

Dir. structurelles : NgFor

Permet d'itérer sur un array :

import {Component} from '@angular/core';

@Component({
  selector: 'todo-list',
  template: `
    <h2>Todos</h2>
    <ul>
      <li *ngFor="let todo of todos">{{todo}}</li>
    </ul>
  `
})
export class TodoList {
  todos = ['Walk the dog', 'Stay in bed', 'Code more'];
}

Directives d'attributs

Modifier le comportement ou l'apparence d'un élément.

 

=> Doit avoir un sélecteur CSS (exemple: doNothing)

@Directive({   
    selector: '[doNothing]'
}) 
export class DoNothingDirective {
  constructor() {     
    console.log('Do nothing directive');   
  } 
}

Directives d'attributs

Les sélecteurs :


• un élément : footer

• une classe (rare) : .alert

• un attribut (le plus fréquent) : [color]

• un attribut avec une valeur : [color=red]

• une combinaison : footer[color=red]

Directives d'attributs

@Directive({   
  selector: 'div.loggable[logText]:not([notLoggable=true])' 
}) 
export class ComplexSelectorDirective {
  constructor() {     
    console.log('Complex selector directive');   
  } 
}

Un exemple de sélecteur complexe :

Dir. d'attributs 'built-in" :

Angular nous fournit 2 directives d'attributs principale :

- ngStyle

- ngClass

 

+ d'autres fournies par modules supplémentaires:

 

FormsModule (ngModel)

RouterModule (routerLink),

Angular Material...

Dir. d'attributs - NgStyle

currentStyle = {};
setCurrentStyle() {
  // Propriétés CSS
  this.currentStyle =  {
    'color': this.color,
    'fontSize': this.fontSize
  };
}

Attribuer un objet de style CSS dynamique:

<!-- Methode Traditionnel binding de la couleur -->
<div [style.color]="color">Un style moins spécial</div>

<!-- Avec ngStyle -->
<div [ngStyle]="currentStyle">Un style spécial !</div>

Dir. d'attributs - NgClass

<div [ngClass]="
{
    'saveable': this.canSave, //true
    'modified': !this.isUnchanged, //true
    'special':  this.isSpecial //true
}
">Un style moins spécial</div>

Attribuer des classes CSS par :

- string
- array

- objet

<div class="saveable special">Un style moins spécial</div>

Resultat natif :

Dir. d'attributs - Custom

@Directive({   
    selector: '[loggable]' 
}) 
export class InputDecoratorOnSetterDirective {
  @Input('logText')   
  set text(value) {     
        console.log(value);   
  } 
}
<div loggable logText="Hello">Hello</div> 
// notre directive console.log "Hello"

On peut facilement créer nos propres directives d'attributs : 

Il faut ensuite rendre cette directive disponible dans l'application en le déclarant dans le ngModule courant :

@NgModule({
  imports: [..],
  declarations: [..., InputDecoratorOnSetterDirective],
  // export notre pipe si le module et importé
  exports: [..., InputDecoratorOnSetterDirective]
})
export class AppModule

M

Dir. d'attributs - Custom

Directives & décorateurs

  @HostListener('mouseover') onMouseOver() {
    console.log('Host listener ...) 
  }

Quelques décorateurs utiles : 

- @Input() : accéder aux valeurs des attributs (attribuées dans le template du composant parent)

- @HostListener() : accéder aux événements se produisant se l'élément hôte

- @HostBinding() : accéder aux valeurs des propriétés 

Directives

TP:

- Structurelles :

    1. implémenter un *ngIf (ex: toogle button)

    2. implémenter un *ngFor

 

- Attribut : ngStyle et ngClass (exercice des couleurs)

 

- Custom : créer une animation au hover

?

*Me solliciter une fois à cette étape

ANGULAR 17


Chargement Asynchrone de composants @defer
 

(TP) : Charger un composant en utilisant @defer

 

Nouvelles opérations de control flow (https://angular.dev/guide/templates/control-flow)

 

(TP) : Charger un tableau avec @for

@defer

Page1Component

Note: Chargement de composants lazy loaded

LoadingComponent

1sec

@defer { <page1/> } @loading { <loading> }

@defer : 

Un chargement différé :

@defer {
    <app-example></app-example>
}

Permet de charger le code d'un composant seulement quand le framework à terminé les affichages vitaux.

@defer les Blocks

Les differents blocks :

@defer (when displayExample) {
    <app-example></app-example>
} @loading { // Afficher quelque chose durant le chargement
    <div>HELLO</div>
} @placeholder { // Afficher autre chose par defaut
    <div>PlaceHolder</div>
} @error { // Si une erreur peut se produire (Ex: Serveur injoignable)
    <div>Error</div>
}

@defer les Blocks

Conditions:

@defer (when displayExemple) {
    <app-example></app-example>
}

@defer les Triggers

Évènements:

<div #greeting>Hello!</div>
@defer (on viewport(greeting)) {
    <app-example>Greeting has been displayed</app-example>
}


@defer (on idle) {
    <app-example>Window is idle</app-example>
}

@defer (on interaction(greeting)) {
    <app-example>Geeting has been clicked</app-example>
}

@defer (on immediate) {
    <app-example>Loaded immediatly after the rendering</app-example>
}

@defer (on timer(500ms)) {
    <app-example>Loaded 500ms after the rendering</app-example>
}
Admin Works

admin.component.html

Le Test

Créer un Composant Admin

?

Afficher le composant Admin en cliquant sur un bouton tout en utilisant @defer.

Objectif :

@if, @for, @empty

Control flow:

@if, @else if, @else :

=> Angular 17 apporte une syntaxe native au framework.

@if (variableA) {
  <div>block affiché si condition OK</div>
} @else if (variableB) {
  <div>block affiché si condition variableA KO et variableB OK</div>
} @else {
  <div>block affiché si aucune condition OK</div>
}

Control flow:

@for, @empty :

=> Angular 17 apporte une syntaxe native au framework.

@for (element of elements; track element) {
	<div>{{ element }}</div>
} @empty {
    <div> Liste elements et vide.</div>
}

Ici track permet d'éviter un rechargement complet de la liste.

Le Test

Créer une liste de 10 élements de type string.

?

Afficher la liste à l'aide du @for

Tester le @empty.

Tester le @if, @else (Faire un bouton qui affiche un gif différent dans le cas if et else).

 

Pour aller plus loin tester le @switch.

https://angular.dev/guide/templates/control-flow#switch-block---selection

MODULES


Déclarations d’un module: imports et exports
Les providers d’un module
Différents types de modules : bonnes et mauvaises pratiques
 

(TP) : Création d’un module et factorisation d’une librairie externe

Angular Module

Component,

Directive,

Pipe

Module

Declares

Service

Provides

Exports

Imports

Others

Modules

Components,

Directives,

Pipe,

Services

Note: Toujours penser à ses imports/exports

@NgModule

NgModule nous permet de centraliser nos declarations dans un même endroit.

 

Deux types de modules : 

- les déjà installés dans le dossier Node par la CLI ('@angular/..')

- les modules à importer soi-même (librairies)

Attention, une fois installés, les modules doivent être importés par une instance NgModule.

@NgModule

Configuration de @ngModule :

import {CommonModule} from '@angular/common'
import {FormsModule} from '@angular/forms'
import {ServiceModule} from 'service/module'
import {MyComponent} from './my/my.component'

@NgModule({
  // La plupart des modules importés seront déclarés ici
  imports: [CommonModule,
            FormsModule],    
  // Les composants que nous créons sont déclarés ici 
  declarations: [MyComponent],
  // Lorsque les modules contiennent des service, 
  // ils sont déclarés ici - nous y reviendrons
  providers: [GreeterComponent]
})
export class AppModule {}

Modules

TP:

- Créer un module "shared" et importer une lib Angular

(ex: CommonModule, FormsModule) et organiser les imports/exports sous la forme d'un feature Module*

Note: Vous trouverez toutes les librairies du manager de package npm à cette adresse https://www.npmjs.com/

1. Un feature Module c'est un module dédié au fonctionnalité de votre package (Dossier).

?

PIPES


Les transformateurs fournis

Formater une chaîne
Formater des collections
Utiliser un pipe comme un service

 

(TP) : Créer ses propres pipes

Pipes transformation dans vos templates

Note: Un pipe prend des paramètres en entrée puis renvoi la sortie désirée.

{{ 'toto' | uppercase }}

I'm

Piper

{{ 'TOTO' }}

Introduction

Les pipes sont des opérateurs permettant d'appliquer une transformation à un input :

<p>{{ 10.6 | currency:'CAD':'symbol-narrow' }}</p>
<!-- retournera '$10.60' -->

Les pipes peuvent être utilisés :

- directement dans le template (ex : {{ myVar | json }})

- dans le composant => necessite l'import du pipe souhaité, ainsi que 'l'injection' du pipe via le constructeur (cf chapitre sur l'Injection de Dépendances)

Note : Fonctionnement identique aux pipes dans un shell

Utilisation

Utilisation dans la vue :

<p>{{ { "name": "BTC", "value": 42000 } | json }}</p>
// resultat :  { "name": "BTC", "value": 42000 }

Variable

Nom du pipe

Utilisation

Utilisation dans un composant :

import { Component } from '@angular/core';
// Importer le pipe
import { CurrencyPipe } from '@angular/common';
@Component({
  selector: 'app-money',
  template: `<p>{{ stringAsCurrency }}</p>`
})
export class MoneyComponent {
  money: number = 1;
  stringAsCurrency: string;
  // injection du pipe
  constructor(currencyPipe: CurrencyPipe) {
  // appel sur le pipe de la méthode transform
  this.stringAsCurrency = currencyPipe.transform(this.money);
  }
}

Partie 1 :

Utilisation

Ajout du service dans un composant ou Module :

import { Component } from '@angular/core';

// Importer le pipe
import { CurrencyPipe } from '@angular/common';

@NgModule({
  declarations: [
    ...
  ],
  imports: [
    ...
  ],
  providers: [CurrencyPipe], // puis l'ajouter aux providers
  bootstrap: [AppComponent]
})
export class AppModule { }

Partie 2 :

Utilisation

Paramétrisation des pipes :

<p>Une date: {{ myDate | date:"MM/dd/yy" }} </p>

Un pipe peut accepter des paramètres optionnels, pour affiner l'output.

=> {{ ... | nomDuPipe: paramètre1 : paramètre2 : ... }}

Utilisation

Enchaîner les pipes :

Une date enchaînée : 
       {{ myDate | date | uppercase}}

Une date enchaînée : 
       {{ myDate | date: format | uppercase}}

Pipes fournis :  json

Applique simplement JSON.stringify() :

<p>{{ pizzas | json }}</p>

// affichera :
<p>[ { "name": "Margarita" }, { "name": "Quatre fromages" } ]</p>

Nb: json est n'est pas très utilisé en production, mais bien pratique pour le débug.

Utilisation dans un template :

Pipes fournis :  slice

Applique slice() au sous-ensemble d’une collection (pour en afficher qu'une partie).

=> 2 paramètres :

slice(un indice de départ et, <un indice de fin.>); 

<!-- Sur un array -->
<p>{{ pizzas | slice:1:3 }}</p>

<!-- Sur une chaîne de caractères -->
<p>{{ 'Margarita' | slice:0:5 }}</p>

Pipes fournis :  text format

Les pipes uppercase, lowercase et titlecase appliquent différentes transformations à des chaînes de caractères : 

<p>{{ 'Quatre fromages' | uppercase }}</p>
<!-- affichera 'QUATRE FROMAGES' -->

<p>{{ 'Quatre fromages' | lowercase }}</p>
<!-- affichera 'quatre fromages' -->

<p>{{ 'Quatre fromages' | titlecase }}</p>
<!-- affichera 'Quatre Fromages' -->

Pipes fournis :  number

<p>{{ 12345 }}</p>
<!-- affichera '12345' -->

<p>{{ 12345 | number }}</p>
<!-- affichera '12,345' -->

<p>{{ 12345 | number:'6.' }}</p>
<!-- affichera '012,345' -->

<p>{{ 12345 | number:'.2' }}</p>
<!-- affichera '12,345.00' -->

<p>{{ 12345.13 | number:'.1-1' }}</p>
<!-- affichera '12,345.1' -->

Pipes fournis :  percent

<p>{{ 0.8 | percent }}</p>
<!-- affichera '80%' -->

<p>{{ 0.8 | percent:'.3' }}</p>
<!-- affichera '80.000%' -->
<p>{{ 10.6 | currency:'CAD' }}</p>
<!-- affichera 'CA$10.60' -->

<p>{{ 10.6 | currency:'CAD':'symbol-narrow' }}</p>
<!-- affichera '$10.60' -->

<p>{{ 10.6 | currency:'EUR':'code':'.3' }}</p>
<!-- affichera 'EUR10.600' -->

Pipes fournis :  currency

Pipes fournis :  date

<p>{{ myDate | date:'dd/MM/yyyy' }}</p>
<!-- affichera '16/07/1986' -->

<p>{{ myDate | date:'longDate' }}</p>
<!-- affichera 'July 16, 1986' -->


<p>{{ myDate | date:'HH:mm' }}</p>
<!-- affichera '15:30' -->

<p>{{ myDate | date:'shortTime' }}</p>
<!-- affichera '3:30 PM' -->

Nb: ce pipe est très similaire à la librairie Moment.js

Pipes

TP:

 

- Utiliser un Pipe fourni (ex: json, uppercase, slice, ...)

Exemple :

Afficher la date du jour actuel avec le Pipe date.

Dans mon html :

{{ myDate | date:'dd/MM/yyyy' }}

Dans mon Composant :

myDate: Date = new Date();

?

*help

Pipes fournis :  async

Permet d’afficher des données obtenues de manière asynchrone en utilisant PromisePipe ou ObservablePipe.

=> Nous reviendrons sur ce pipe dans le chapitre concernant les Observables dans RxJS.

 

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 (cf Observables)

Pipes fournis :  async

import { Component } from '@angular/core';
@Component({
  selector: 'ns-greeting',
  template: `<div>{{ asyncGreeting | async }}</div>`
})
export class GreetingComponent {
  asyncGreeting = new Promise(resolve => {
  	// promise resolve après 1 seconde
  	setTimeout(() => resolve('hello'), 1000);
  });
}

Exemple utilisant une promesse :

Pipes

TP:

 

- Utiliser un Pipe Async avec une Promesse

?

Ps : la solution est sur le slide précédent :}

Custom pipes - exemple

Nous allons réaliser un Pipe affichant le temps écoulé depuis une date à l'aide de la librairie Moment.js :

=> Nous utiliserons la function fromNow() de Moment.js

npm install moment

1.  installer Moment.js avec NPM :

Custom pipes - exemple

import { PipeTransform, Pipe } from '@angular/core';
import * as moment from 'moment';

@Pipe({ name: 'fromNowPipe' })
export class FromNowPipe implements PipeTransform {
  transform(value, args) {
   return moment(value).fromNow();
  }
}

2.  Créer le pipe : 

Executer la commande : ng g pipe shared/pipes/fromNowPipe

3. Ajuster le code :

Custom pipes - exemple

Il faut rendre ce pipe disponible dans l'application en le déclarant dans le ngModule courant :

@NgModule({
  imports: [..., FromNowPipePipe],
  declarations: [...],
  // export notre pipe si le module et importé
  exports: [..., NewPipe],
})
export class ShareModule

M

4. Declarer et exporter notre Pipe

Pipes

TP:

 

- Custom Pipe (faire: custom-uppercase qui transforme une chaine de character en majuscules).

?

Programme - Jour 2

INJECTION DE DÉPENDANCES
Principes
Configurer son application
L’injection de dépendances : type-based et hiérarchique
Différents types de providers
TP : Créer ses propres services

 

SERVICES
Les services fournis
Injection de service

TP : Injecter les services fournis par Angular
 

ROUTER
RouterModule: Configuration des routes et URLs
Définitions des routes, liens et redirection, paramètres
Hiérarchies de routes
Vues imbriquées
Cycle de vie (Routing lifecycle)
TP : Transformer une application Web en Single Page Application

Injection de Dépendances

Note: Une dépendance n'est instanciée qu'une seul fois.

Object, Services, ...

Providers *myModule

Cache injector   

Injection de dépendances

La Problématique :

- un composant C utilise une fonction getApiVersion

- getApiVersion est déclarée dans un service S

C dépend de S => S est une dépendance de C

 

2 possibilités :

- C créé une instance de S

- le framework créé une instance de S, qu'il 'injecte' dans C

= injection de dépendance

 

Exemple

Une dépendance est simplement une classe que l'on va 'injecter' dans une autre classe.

Généralement un service dans un composant :

export class ApiService {   
    getBooksByIds(booksIds) {     
        // todo: appeler l'api backend   
    } 
}

Exemple

Signaler l'injection : le décorateur @Injectable 

import { Injectable } from '@angular/core';

@Injectable()
export class ApiService {   
    getBooksByIds(booksIds) {     
        // todo: appeler l'api backend 
    } 
}

Injection de dépendances

Pour injecter notre dépendance, on a ensuite besoin :

 

1. d’une façon d’enregistrer la dépendance, pour la rendre disponible à l’injection dans d’autres composants/services.

 

Cela ce passe coté module, déclarez votre service

dans les provides.

 

2. d’une façon de la déclarer dans nos composants ou services.

Déclarer la dependance dans le constructeur du Composant.

M

Injection de dépendances

1. Enregistrer une dépendance injectable :

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class TestService {

  constructor() { }
  
  getApiVersion(): string {
    return "0.0.1";
  }
}

Injection de dépendances

2. Enregistrer la dependance injectable dans le module :

@NgModule({
  imports: [..],
  declarations: [..],
  exports: [..],
  // ajout de la dependance injectable ici
  provides: [TestService]
})
export class AppModule

M

Note: L'enregistrement d'une dependance injectable ce fait toujours manuellement même en créant un service à l'aide de la CLI vous devrez l'ajouter manuellement

Injection de dépendances

3. Déclarer la dépendance dans un composant :

...
import { TestService } from './test-service'; 

@Component({ 
	selector: 'app-component', ...
})
export class Component implements OnInit {

  constructor(public testService : TestService) {}

  ngOnInit() {
    this.testService.getApiVersion();//utilisation du service
  }
}

Services

Les services

Les services correspondent à la façon la plus utilitaire d'injecter des dépendances.

 

Principe:

Quand plusieurs composants ont besoin de faire la même chose :

- factoriser le code correspondant dans un service

- injecter dans les composants

Les services

Créer un service :

import {Injectable} from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class LoginService {

  constructor() {}

  doSomething() {}
}

Les services

Enregistrer le service injectable dans le module :

@NgModule({
  imports: [..],
  declarations: [..],
  exports: [..],
  // ajout du service injectable ici
  provides: [LoginService]
})
export class AppModule

Les services

Utiliser le service dans un composant :

import {Component} from '@angular/core';
import {LoginService} from 'login.service';

@Component({
  selector: 'my-component',
  // Vous pouvez : providers: [LoginService],
  // ici mais ce n'est pas une bonne pratique
  templateUrl: './my.component.html'
})
export class MyComponent {
  constructor(private loginService: LoginService) { }

  ngOnInit() {
    this.loginService.doSomething();
  }
}

Les Services

TP:

 

- Crée un Module shared

- Crée un Service (CardService) dans un dossier services 

- Crée une fonction getCards dans ce service et retourner une Promise<Card[]> à utiliser dans un composant.

?

export class Card {
    constructor(
        public title: String,
        public description: String,
        public price: Number,
        public type: String
    ) { }
}

Model Card :

AppRoutingModule

/shop

/home

Routage

Introduction

But: associer une URL à un état de l’application (meilleure UX)

 

=> routeur : chaque framework a le sien

 

En Angular, il s'agit du module RouterModule.

RouterModule

Configuration du module :

Nb: cela peut se faire dans un fichier dédié, généralement nommé app.routes.ts.

import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { OtherComponent } from './other/other.component';
export const ROUTES: Routes = [
  { path: '', component: HomeComponent },
  { path: 'other', component: OtherComponent }
];

RouterModule

Inclure le tag de projection de la route "routé" dans le template :

<router-outlet></router-outlet> <!-- :) i'm root -->

<header>
  <nav>...</nav>
</header>
<main>
  <router-outlet></router-outlet>
  <!-- le template du composant 'routé' sera 
        inclu ici -->
</main>
<footer>fait avec &lt;3 par ...</footer>

Exemple :

Navigation :

Naviguer entre différents composants ?

 

En effet, avec des liens "classiques" :

click --> reload page --> relance toute l'app (SPA)

 

=> utiliser une directive particulière : routerLink.

RouterLink :

RouterLink - argument :

        - le chemin (string)

        - le chemin + paramètres (array<string>)

 

Nb: importer cette directive ? Avoir le routeur Module.

RouterLink :

Exemples :

<a href="" routerLink="/">Home</a>
<!-- idem -->
<a href="" [routerLink]="['/']">Home</a>

RouterLinkActive - ajouter une classe CSS lorsque le lien pointe sur la route courante :

<a href="" routerLink="/" routerLinkActive="selected-menu">
        Home</a>

navigate() :

Naviguer depuis le composant : 

- injecter le service Router (cf partie sur la DI)

- utiliser sa méthode navigate()

export class navigationComponent {
  // Injection d'une instance du Router
  constructor(private router: Router) {
  }
  saveAndMoveBackToHome() {
  // Route : 
  this.router.navigate(['']);
  }
}

Urls dynamiques :

-  définir une route dans la configuration avec des paramètres dynamiques ( " ../:paramDyn/.. ")

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'newsFeed', component: newsFeedComponent },
  { path: 'profiles/:profileId', component: ProfileComponent }
];

- définir des liens dynamiques ("[routerLink] = "['paramStatic', paramDyn, ...]"

<a href="" [routerLink]="['/profiles', profile.id]">
        Voir profil</a>

Routage

TP:

 

- Créer un Composant 'newCard'

 

- Ajouter le composant à la table de routage. '/new/card'

 

- Ajouter le tag <router-outlet></router-outlet> dans app.component.html

 

- Créer un lien <a routerLink="/new/card"></a> pour acceder à la page.

?

Programme - Jour 3

INTRODUCTION À RXJS
Présentation des observables/observers
Le fonctionnel dans tout ça

 

ECHANGER AVEC LE SERVEUR
Implementation de HttpClient

TP : Créer un service qui communique avec une api

 

LES FORMULAIRES

Template driven

Reactive Form

TP : Créer un formulaire de création liée avec le service.

@rxjs

Angular + RXJS

Introduction

Un peu de programmation fonctionnelle ?

Note: Fait partie du groupe de paradigme déclaratif

Note: déclaratif, orienté objet, impératif

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

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 ?

Angular est le fonctionnel

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.

import { of } from 'rxjs';

const myObservable: Observable<number> = of(42);

// output : 42
myObservable.subscribe((value: number) => { console.log(value); });

Angular est le fonctionnel

Souscrire à des observables

of est la méthode la plus simple et 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.

Présentation

Observable :

Fonction qui associe une source de données à un observeur + retourne un moyen d'annuler cette liaison.

type: Observable

 

Subjects :

Objet ayant une méthode next(), et optionnellement complete() et error().

types : Subject

Présentation

Exemple visuel :

La librairie Rxjs

Rxjs permet de créer des observables.

import { Observable, of } from 'rxjs';

const my_observable = new Observable(
(observer) => observer.next(42))

const my_observable2 = of(42);

of est la méthode la plus simple et permet de créer un observable n'envoyant qu'une seule valeur.

ps: attention à bien vérifier vos imports.

Présentation

Présentation

// Exemple :
function monObservable(subject) {
  let array = [1, 2 , 3 , 4]
  array.forEach(
    (el) => subject.next(el)
  )
  subject.complete()
}

let subject = {
  next : (value) => console.log('Nouvelle valeur : ' + value),
  complete : ()  => console.log('Terminé.')
}
  
// vrai création d'un observable
const my_observable = new Observable(
(subject) => subject.next(42));

Exemple visuel :

Rxjs - Subscribe

Méthode subscribe() permet d'exécuter l'observable :

import { of } from 'rxjs';

const my_observable = of(42);

my_observable.subscribe(
  (value) => console.log(value), 
  (error) => console.log(error), 
  ()      => console.log('terminé')
);

my_observable.complete();
// output : 42, terminé

Présentation

Rxjs - Pipe

Méthode pipe() (Angular 6+) permet de transformer l'output, en revoyant un nouvel observable: 

import { of } from 'rxjs';
import { map } from 'rxjs/operators';

const my_observable = of(42);

my_observable
.pipe(
  map( val => val - 10))
.subscribe(
  (value) => console.log(value));

Présentation

Un cas concret

Angular nous propose justement de nombreux services exploitant à fond la programmation réactive, tel que HTTP.

Les différentes méthodes du service http retournent des Observable<Response>. Notez que le type des variables émises par l'observable est précisé entre chevrons, les observables sont en effet génériques.

Rxjs

TP:

 

- Ajouter un composant tpRxjs

- Ajouter une route puis un lien pour charger le composant.

- Créer une liste de nombres.

- Créé puis utiliser un Observable avec la fonction of(votre liste)

- Afficher la liste dans sur la page tpRxjs.

          Note : nom des Observables : myVar$

?

Angular + HTTPClient

Le module HttpClient

Nb :

Traditionnellement, en web on utilise le protocol HTTP, mais il y a des alternatives :

  • WebSockets
  • Bibliothèques, comme l’API fetch (XmlHttpRequest) possibilité de l'utiliser dans Angular, Socket io, dgram ...

HttpClient - implémentation

1. Définir la fourniture HttpClient (app.config.ts):

import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';

import { routes } from './app.routes';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
    providers: [
        provideZoneChangeDetection({ eventCoalescing: true }),
        provideRouter(routes),
        provideHttpClient(withInterceptorsFromDi())
    ],
};

M

GET

HttpClient - implémentation

2. Je regarde le format de reponse de l'api.

GET monsiteweb.com/api/get-count

reponse :

50060

Le type de réponse est donc un number.

Privilégiez les API Rest (Réponses Json)

GET

HttpClient - implémentation

3. "L'injecter" partout où on en a besoin.

...
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
}) 
export class MyApiService implements OnInit {
  
  constructor(private http: HttpClient) { }
  
  getCount(): Observable<number> {
   return this.http.get<number>("monsiteweb.com/api/get-count");
  }
}

GET

HttpClient - implémentation

Il propose plusieurs méthodes, correspondant au verbes HTTP communs :
get • post • put • delete • patch • head • jsonp

 

Une requête est effectuée de la façon suivante :

httpClient.get|post|delete|put|patch
  (`${baseUrl}/api/foo/bar`, ... les options)

Cela retourne un Observable => on doit donc s'y abonner pour obtenir la réponse. 

 

HttpClient - implémentation

Abonnement :

http.get<Foobar>(`${baseUrl}/api/foo/bar`)
    .subscribe((response: Foobar) => {
        console.log(response)
    });

Nb : Le corps de la réponse, qui est la partie la plus intéressante, est directement émis par l’Observable. On peut néanmoins accéder à la réponse HTTP complète :  

http.get<Foobar>(`${baseUrl}/api/foo/bar`, { observe: 'response' })   
    .subscribe((response: HttpResponse<Foobar>) => {     
        console.log(response.status); // logs 200     
        console.log(response.headers.keys()); // logs []   
});

HttpClient - implémentation

Envoyer des données est aussi trivial.

Il suffit d’appeler la méthode post(), avec l’URL et l’objet à poster :

 

let observableHttp = httpClient.post(
  'http://api.com/foo/bar', // param1 url
  new FooBar(10), // param2 objet
  { observe: 'response' } // options http
);

observableHttp.subscribe((response: HttpResponse<string>) => {
  console.log(response.status == 200); // verifier le status
});

POST

HttpClient

TP:

- Importer HttpClientModule dans votre sharedModule

- Crée un Service CardApiService

- Utiliser l'injection de dépendence HttpClient dans votre constructeur.

- Crée une fonction qui effectue une request get sur "/search_query"

- Votre fonction doit retourner une liste Observable<Card[]>

- Utiliser la fonction de ce service dans le composant CardComponent.

 

?

Nb: DTO = Data Transfer Object

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 ou Composant 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.

<form [formGroup]="formGroup" >
  ...
  ...
  <button (click)="execute()" [disabled]="!formGroup.valid">Execute</button>
</form>

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:

 

- Crée un nouveau composant (ex: NewCardComponent)

- Importer ReactiveFormsModule

- Associer une route à ce nouveau composant /new-card

- Créer un formulaire pour créer un nouveau card

- Utiliser le service CardService et le endpoint /card POST

- Redirection vers la page qui list les 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.

?