Angular X

Formateur

29ans

Formateur Angular

Architecte technique

Tour de table

Noms prénoms

Parcours

Division et site

Expérience en dev. et JS/Angular

Vos attentes

Déroulement de la formation

Déroulement de la formation

 Installation des outils
> npm install -g typescript
> npm install -g @angular/cli
Git

NodeJs

Un IDE
Depuis l'invite de commandes 
> npm uninstall -g @angular/cli
> npm cache clean
> npm install -g @angular/cli

OU

Répertoire d'installation en local : M:\Env formation Angular

Déroulement de la formation

Github de la formation

> git clone -b step-00 https://github.com/Jmadih/angularX
> cd angularX
> npm install
> ng serve

 

●une branche d’exercice : step-00

●une branche de solution : step-00-solution

Un concept => une branche

Avant d'aller plus loin

  • Javascript (ES6, ES5)

  • Typescript (ES6+, types, annotation)

  • Dart

Les languages supporter par Angular :

Javascript & Typescript

@Component({
   selector: 'my-app',
   template: '<h1 id="output">My First Angular 2 App</h1>'
})
class AppComponent { }
var AppComponent = ng.core.Component({
   selector: 'my-app',
   template: '<h1 id="output">My First Angular 2 App</h1>'
})
.Class({
   constructor: function () { }
});

Typescript

Javascript

Javascript & Typescript

Javascript

  • Arrow functions
  • Classes
  • Collections
  • get / set transparents
  • let / const
  • ES6 modules
  •  ....

Typescript

  • Decorators
  • Classes
  • Interfaces
  • Typings
  • Function return
  • Optional params
  •  ....

DEMO

Angular Ecosystem

Angular 10
Angular 9

Configuration

ES5 est désactivé par défaut, pour ajouter un navigateur => .browserlistrc

 

Quickstart

CLI

  1.  Génère l’arborescence de l’application

  2.  Initialise un repo Git + 1er commit

  3.  Installe les deps NPM + Typings

> ng new my-best-app
Nouveau projet :
> ng g component home
      create src/app/home/home.component.html (23 bytes)
      create src/app/home/home.component.spec.ts (614 bytes)
      create src/app/home/home.component.ts (262 bytes)
      create src/app/home/home.component.scss (0 bytes)
      update src/app/app.module.ts (390 bytes)
Nouveau composant :
Component : ng g component [name] --flat
Directive : ng g directive [name]
Pipe : ng g pipe [name]
Service : ng g service [name]
Class :	ng g class [name]
Interface : ng g interface [name]
Enum : ng g enum [name]
Build : ng build -prod
Lint : ng lint
Directives : ng doc directives

Toutes les commandes :

  • Module

  • Component

  • Template syntax

  • Directive

  • Pipe

Les concepts Angular

  • Router

  • Service Provider

  • Forms

  • HTTP

  • Observable

(Advanced: Angular elements, Ivy, Schematics ...)

Architecture globale

Une application est un arbre de modules et de composants

Module

Arbre de module

Déclaration d'un module

Une classe avec un décorateur @NgModule

Paramètres :  declarations, imports, exports, providers, bootstrap.

Charger l’application dans la page

Bootstrapping

Component

3 concepts à retenir

Déclaration d'un composant

Composant

Template

Lifecycle Hooks

Lifecycle Hooks

Déclaration

Exercice

1

git checkout -f step-01

  • Créez un composant Home (HomeComponent)
  • Intégrez le dans le template du composant parent (Ici AppComponent)
  • Créez un module Home (HomeModule)
  • Déclarez le composant Home dans le module Home

Solution

1

git checkout -f step-01-solution

Des questions ?

Template

Chaque composant a son 

template

Un template

Un template :

3 catégories de bindings

Un template :

3 catégories de bindings

<!-- Interpolation -->
<h1>{{introduction.firstname}}</h1>

<!-- One way binding -->
<h1 [innerText]="introduction.firstname"></h1>
<span [style.backgroundColor]="introduction.favouriteColor"></span>

<!-- Two way binding -->
<input [(ngModel)]="introduction.firstname"/>

<!-- Event binding -->
<button (click)="sendForm()">Send</button>

Exemples

Aperçu

attributs vs propriétés

Les attributs c'est du HTML, les propriétés c'est du DOM

Les attributs servent à initialiser les propriétés mais pas vice versa !

  • Exceptions :
  • Propriété sans attribut : (textContent)
  • Attribut sans propriété : (colspan)

Aperçu

attributs vs propriétés

<input type="text" value="foo"> <!-- attributs value et type -->

Le navigateur interprète cette balise et crée un objet node en mémoire :

input:
  ...
  value: "foo" // <-- propriétés value et type
  type: "text"
  ...

Exercice

2

git checkout -f step-02

  • Dans le Home Component : Remplissez un model de type Introduction
  • Affichez le model dans le template du Home Component
  • Ajoutez un événement qui déclenche"alert('edit')"sur le bouton Edit 
  • Ajoutez la propriété disable sur le bouton Edit, cette propriété s'active quand on clique sur le bouton

Rendu

Exercice

2

Solution

2

git checkout -f step-02-solution

Des questions ?

Directives

Directives

3 types :

  • Structurel
  • Attribut
  • Composant

Directives structurels

Modifie le DOM en ajoutant ou en supprimant des éléments.

<!-- *ngIf -->
<div *ngIf="hero; else notHero" class="name">{{hero.name}}</div>

<ng-template #notHero >
        I'm not a Hero bro
</ng-template>

<!-- *ngFor -->
<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<!-- *ngSwitch -->
<div [ngSwitch]="hero?.emotion">
  <app-happy-hero    *ngSwitchCase="'happy'"    [hero]="hero"></app-happy-hero>
  <app-sad-hero      *ngSwitchCase="'sad'"      [hero]="hero"></app-sad-hero>
  <app-confused-hero *ngSwitchCase="'app-confused'" [hero]="hero"></app-confused-hero>
  <app-unknown-hero  *ngSwitchDefault           [hero]="hero"></app-unknown-hero>
</div>

Directives d'attributs

Change l'apparence ou le comportement d'un élement

<div [style.width.px]="size"></div>

<td [attr.colspan]="5*5">cell</td>

<div [class.isSpecial]="isSpecial">Special class</div>

<div [style.color]="isSpecial ? 'red' : 'green'">Special class</div>

Directives d'attributs

CUSTUM

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

@Directive({
  selector: '[custumDirective]'
})
export class custumDirective implements OnInit {

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

  ngOnInit() {
    this.renderer.addClass(this.el.nativeElement, 'wild');
  }

}

Directives de composant

Juste une directive avec un template

<app-component>
  Hello world...
</app-component>

Exercice

3

git checkout -f step-03

  • Affichez la liste des liens introduction.links avec la balise image et
    la directive
    mat-card-avatar

  • Affichez selon le status (introduction.status) la bonne icône.

  • Ajoutez les couleurs rouge et vert sur les icônes

  • Ajoutez une custum directive (yearsOfExperience)

  • Appliquez cette directive dans une div après la date de naissance

Rendu

Exercice

3

Solution

3

git checkout -f step-03-solution

Des questions ?

Router

Les composants sont

routables

Navigation

http://localhost:4200/home

http://localhost:4200/skills

Configuration

  • path : l’URL de route (ex: /skills/:id)

  • component : le composant associé à cette route (ex: HomeComponent)

  • redirectTo : le fragment d'URL vers lequel rediriger la route courante (ex : '/home')

  • pathMatch : stratégie de redirection (full/ prefix)

    • full :  Reconnaissance depuis la racine de la route

    • prefix : Reconnaissance partielle de la route

Simple

Configuration

Complète

  • path

  • component

  • redirectTo

  • pathMatch

 

  • data
  • canActivate / canDeactivate
  • resolver
  • children

Routes

import {ModuleWithProviders} from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {IntroductionComponent} from "./introduction/introduction.component";
import {SkillsComponent} from "./skills/skills.component";
import {AboutComponent} from "./about/about.component";

const routes: Routes = [
  {path: '', redirectTo: 'introduction', pathMatch: 'full'},
  {path : 'introduction', component: IntroductionComponent},
  {path : 'skills', component: SkillsComponent },
  {path : 'about',  component : AboutComponent},
  {path : '**', component: IntroductionComponent}
];

export const AppRouting: ModuleWithProviders = RouterModule.forRoot(routes, {useHash: true});

Déclaration

Routes

<!-- app.component.html -->
<router-outlet></router-outlet>

Déclaration de l’emplacement dans lequel le composant doit s’afficher

Routes

<button mat-raised-button color="primary" [routerLink]="['/page1']">Page1</button>
<button mat-raised-button color="primary" [routerLink]="['/page2']">Page2</button>
<button mat-raised-button color="primary" [routerLink]="['/page3']">Page3</button>

Redirection

Routes

import {ModuleWithProviders} from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import {IntroductionComponent} from "./introduction/introduction.component";
import {SkillsComponent} from "./skills/skills.component";
import {SkillsResolve} from "./skills/skills.resolve";
import {AboutComponent} from "./about/about.component";

const routes: Routes = [
  {path: '', redirectTo: 'introduction', pathMatch: 'full'},
  {path : 'introduction', component: IntroductionComponent},
  {path : 'skills', component: SkillsComponent, resolve: {skills: SkillsResolve} },
  {path : 'about',  component : AboutComponent},
  {path : '**', component: IntroductionComponent}
];

export const AppRouting: ModuleWithProviders = RouterModule.forRoot(routes, {useHash: true});

Utilisation d'un resolver

Routes

import {Injectable} from '@angular/core';
import {ActivatedRouteSnapshot, Resolve, RouterStateSnapshot} from '@angular/router';

@Injectable()
export class SkillsResolver implements Resolve<any> {

constructor() {}

resolve(activatedRoute: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
 const elem: number = activatedRoute.params['param'];
}

}

Déclaration d'un resolver

Routes

@Component({
  selector: 'app-my',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss']
})
export class MyComponent implements OnInit {

  infos: any;

  constructor(private route: ActivatedRoute) {
  }

  ngOnInit() {
    this.route.data.subscribe(data => {
      this.infos= data['skills'];
    });
  }

Récupérer les données du resolver

Routes

export const appRoutes: Routes = [
  {path: 'info/:id', component: InfoComponent, resolve: { info: InfoResolverService}},
  {path: 'about', component: AboutComponent, canActivate: [MyGuard]},
  {path: '**', component: NotFoundComponent},
  {path: '404', component: NotFoundComponent}
];

Utilisation des Guards

Routes

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';

@Injectable()
export class ConnectedGuardGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return true;
  }
}

Déclaration d'un Guard

Exercice

4

git checkout -f step-04

  • Créez un fichier "app.routes.ts" puis ajoutez les deux routes home et skills
  • Ajoutez le routerOutlet dans le app.component.html
  • Ajoutez dans le menubar les deux routerLink (home et skills)
  • Ajoutez le routerLink dans le fichier not-found.component.html pour rediriger vers home.

Solution

4

git checkout -f step-04-solution

Des questions ?

Exercice

5

git checkout -f step-05

  • Créez un resolver "HomeResolver" :

    • Déplacez le tableau introductions dans le resolver

    • Récupérez l'id de la route à l'aide de la classe ActivatedRoute
    • Retournez le bon profil à l'aide de la fonction _find de lodash (https://lodash.com/docs/#find) en ajoutant l'import   ( import * as _ from 'lodash'; )
    • Récupérez la données dans le composant HomeComponent à l'aide de la classe ActivatedRoute

  • Ajoutez un guard "ConnectedGuard" dans le module SecurityModule pour interdire l'accès à la page Skills

Solution

5

git checkout -f step-05-solution

Des questions ?

Service

Injection de dépendance
import { Engine, Tires, Doors } from './shared';
 
 class Car {
   
   constructor(
       private _engine: Engine,
       private _tires: Tires,
       private _doors: Doors
   ) {
   }
}

Typescript

Injection de dépendance

● L’Injector expose l’API pour créer des instances de dépendances

● Le Provider indique à l’Injector comment créer la dépendance

● La dépendance est le type d’objet à créer

 

Injection de dépendance
  • Par défaut singleton

  • Pour avoir des instances

    • Utiliser une Factory

    • Ou créer un Provider fils

 

Les providers

Services

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

@Injectable()
export class MyService {

name: string;

constructor() {
 this.name = "Angular";
}

getName(): string {
   return this.name;
}

}
Déclaration
  • Une simple classe exportée

  • Un décorateur @Injectable()

Services

import { Component, OnInit } from '@angular/core';
import {MyService} from './my.service';

@Component({
  selector: 'app-my',
  templateUrl: './my.component.html',
  styleUrls: ['./my.component.scss'],
  providers: [MyService] //Ou  {provide: MyService, {useClass: MyService}}
})
export class MyComponent implements OnInit {


  constructor(private myService: MyService) {
  }

  ngOnInit() {}
}
Injection locale

Services

import { NgModule } from '@angular/core';
import {SharedModule} from '../shared/shared.module';
import {MyResolverService} from './my-resolver.service';
import {MyService} from './my.service';

@NgModule({
  imports: [
    SharedModule
  ],
  declarations: [],
  exports: [],
  providers: [MyResolverService, MyService] //Ou  {provide: MyService, {useClass: MyService}}
})
export class MyModule { }
Injection globale

Communication serveur

import { NgModule } from '@angular/core';
import {HttpClientModule} from '@angular/common/http';

@NgModule({
   imports: [HttpClientModule],
   ...
});

Intégrer HttpModule dans notre NgModule

Communication serveur

import { Injectable } from '@angular/core';
import {HttpBackend, HttpClient} from '@angular/common/http';

@Injectable()
export class MyService {

  constructor(private http: HttpClient) { }

  getSkills(id: number): Observable<Skill>{
    const headers = { 'Authorization': 'secret' };
    const params = { 'page': '1' };
    return this.http.get<Skill>('/api/skills', { headers, params });
  }
}

Importer le service HttpClient

RxJs

Reactive extensions for JavaScript

Une librairie de flux reactifs qui vous permet de travailler avec des flux de données asynchrones.

Le Pattern Observer

RxJs

Reactive extensions for JavaScript

  • Flux événements

  • Un événement peut être une valeur, une erreur ou une terminaison

  • On peut s'abonner au flux

  • Observables != Promises

RxJs

Reactive extensions for JavaScript

var observer = rx.Observer.create(
  function onNext(result){ 
    console.log(result); 
  },
  function onError(err){ 
    console.log(err); 
  },
  function onCompleted(){ 
    console.log('Completed'); 
  }
);
observable.subscribe(observer);

Création

RxJs

Reactive extensions for JavaScript

Transformation

Filter

RxJs

Reactive extensions for JavaScript

Transformation

Map

RxJs

Reactive extensions for JavaScript

Lecture d'un marble diagram

Map

RxJs

Reactive extensions for JavaScript

 Pipe avec des littable operators

  • littable operators => Let

  • Similaire à l'opérateur de map

  • Il permet de composer plusieurs opérations

RxJs

Reactive extensions for JavaScript

import { ajax } from "rxjs/observable/dom/ajax";
import { of } from "rxjs/observable/of";
import { catchError, map } from "rxjs/operators";

const name = ajax
  .getJSON<{ name: string }>("/api/employees/alice")
  .pipe(
    map(employee => employee.name),
    catchError(error => of(null))
);

Communication serveur

import { Injectable } from '@angular/core';
import {HttpBackend, HttpClient} from '@angular/common/http';

@Injectable()
export class MyService {

  constructor(private http: HttpClient) { }

  getSkills(id: number): Observable<string>{
    const headers = { 'Authorization': 'secret' };
    const params = { 'page': '1' };
    return this.http
           .get<Skill>('/api/skills', { headers, params })
           .pipe(
             map(skill => skill.name),
             catchError(err => of(null))
            );
  }
}

HttpClient Observable

Unit test 

Jasmine est un Framework de test JavaScript orienté Behavior-Driven Development, ou BDD.
Karma est un outil qui nous permet d'exécuter des navigateurs et d'exécuter des tests Jasmine
//helloWorld.ts
function helloWorld() {
  return 'Hello world!';
}


//helloWorld.spec.ts
describe('Hello world', () => { (1)
  it('says hello', () => { (2)
    expect(helloWorld()) (3)
        .toEqual('Hello world!'); (4)
  });
});

Exemple:

Unit test 

1 - describe(string, function):  définit une série de tests

2 - it(string, function):  définit un test individuel

3- expect(actual): expectation, ça décrit le comportement attendu

    matcher(expected): la valeur attendu, une comparaison est faite

4 - expected: le test casse si le "actual" n'égale pas le "expected"

describe('Hello world', () => { (1)
  it('says hello', () => { (2)
    expect(helloWorld()) (3)
        .toEqual('Hello world!'); (4)
  });
});

Exercice

6

git checkout -f step-06

  • Créez un service : HomeService

  • Refactoring: Déplacez le code de récupération des introductions dans ce service
  • Injectez le HomeService dans le HomeResolver afin de récupérer les introductions

Solution

6

git checkout -f step-06-solution

Exercice

7

git checkout step-07

Le répertoire _server contient des services REST avec les endpoints suivants :

/**
 * Introductions
 */
app.get('/api/intros', api.getIntros);
app.get('/api/intros/:id', api.getIntro);
app.put('/api/intros/:id', api.updateIntro);


/**
 *  Skills
 */
app.get('/api/skills', api.getSkills);
app.put('/api/skills/:id', api.updateSkill);
app.post('/api/skills', api.addSkill);
app.delete('/api/skills/:id', api.deleteSkill);

Exercice

7

git checkout step-07

# cd _server
# npm start

✔Express server listening on http://localhost:9000/

Démarrer le serveur

http://localhost:9000/api/intros

#npm install

Exercice

7

On veut appeler directement le endpoint /api/intros/:id

  • Améliorez le service HomeService

  • Améliorez le resolver HomeResolver

    • Utilisez le pipe et les opérations lettable pour gérer l'exception 404

#npm start

Solution

7

git checkout -f step-07-solution

Des questions ?

Component

Input & Output

State management

State management

import {Component, OnInit, Output, Input, EventEmitter} from '@angular/core';
import {FormGroup, FormControl} from "@angular/forms";

@Component({
  selector: 'app-intro-form',
  templateUrl: 'form.component.html',
  styleUrls: ['form.component.scss']
})
export class IntroFormComponent implements OnInit {

  @Input() model:any = {};

  @Output() submit;

  constructor() {
    this.submit = new EventEmitter();
  }

  ngOnInit() {
    console.log(model);
  }

  submit(model){
    this.submit.emit(model);
  }

State management

// parent.component.html

<app-intro-form [model]="model" (submit)="save($event)" > </app-intro-form>

Exercice

8

git checkout -f step-08

  • Créez un composant FormComponent à l'intérieur du dossier home

  • Ajoutez un input (model) et deux outputs (submit$ et cancel$)

  • Affichez le model (introduction) dans le form.component.html et créez deux boutons pour envoyer les deux évènements (submit$ et cancel$)

  • Affichez le composant FormComponent dans le template  home.component.html avec les bons paramètres.

Solution

8

git checkout -f step-08-solution

Forms

Formulaires

La gestion des formulaires a toujours été un des points forts d'Angular
Template Driven Forms 
Pilotée par les templates (simple)

Model Driven Forms
Pilotée par le code (plus verbeux mais plus puissant)

Formulaires

Un champ de formulaire est un FormControl :

valid : si le champ est valide avec les validations et contraintes qui lui sont appliquées
errors : objet contenant les erreurs du champ
dirty : false jusqu'à ce que l'utilisateur modifie le champ
pristine : opposé de dirty
touched : false jusqu'à ce que l'utilisateur soit entré et sorti du champ
untouched : opposé de touched
value : la valeur du champ
valueChanges : un observable emettant la valeur du champ à chaque changement

Formulaires

Créer un champ de formulaire

const username = new FormControl('', {
validators: Validators.required,
updateOn: blur}); // Update only when remove focus
console.log(username.dirty);
console.log(username.hasError('required')); //true

New

Formulaires

Un formulaire est représenté par un FormGroup :

valid : si le formulaire est valide
errors : objet contenant les erreurs du formulaire
dirty : false jusqu'à ce que l'utilisateur modifie un des champs du formulaire
pristine : opposé de dirty
touched : false jusqu'à ce que l'utilisateur soit entré et sorti d'un des champs du formulaire
untouched : opposé de touched
value : la valeur du formulaire sous forme de clé/valeur avec le nom du champ en clé
valueChanges : un observable emettant la valeur du formulaire à chaque changement sur un des champs

Formulaires

Créer un groupe de champs

 const form = new FormGroup({
      username: new FormControl(),
      password: new FormControl()
    });
    console.log(form.dirty);
    console.log(form.value);
    console.log(form.get('username'));

Formulaires

Reactive Form

@Component({
   selector: 'app-react-form',
   templateUrl: './react-form.component.html'
   })
export class ReactFormComponent {
    loginForm: FormGroup;
          
    constructor(formBuilder: FormBuilder) {
      this.loginForm = formBuilder.group({username: '', password: ''});
    }
          
    register() {
      console.log(this.loginForm.value);
    }
}

Formulaires

Reactive Form


<form (ngSubmit)="register(loginForm.value)" [formGroup]="loginForm">
 <div class="form-group">
   <label for="username">Nom d'utilisateur</label>
  <input type="text" class="form-control" 
                        id="username" formControlName="username" 
                        placeholder="Nom d'utilisateur">
 </div>
  <div class="form-group">
    <label for="password">Password</label>
   <input type="password" class="form-control" 
                        id="password" formControlName="password" 
                        placeholder="Mot de passe">
   </div>
  <button type="submit" class="btn btn-success">Se connecter</button>
</form>

Formulaires

On peut être notifié des changements grâce aux observables

@Component({
   selector: 'app-react-form',
   templateUrl: './react-form.component.html'
   })
export class ReactFormComponent {
    loginForm: FormGroup;
          
    constructor(formBuilder: FormBuilder) {
      this.loginForm = formBuilder.group({username: '', password: ''});
      this.loginForm.valueChanges.subscribe(form => console.log(form));
      this.loginForm.get('username').valueChanges
      .subscribe(username => console.log(username));
    }
          
    register() {
      console.log(this.loginForm.value);
    }
}

Formulaires

Validateurs: Retourne une map d'erreurs ou 'null' si aucune erreur est détecté

Quelques validateurs: 
required: permet de vérifier que le champ n'est pas vide
minLength: permet de vérifier la longueur minimum
maxLength: permet de vérifier la longueur maximum
pattern: permet de vérfier le respect d'une regex

Formulaires

@Component({
   selector: 'app-react-form',
   templateUrl: './react-form.component.html'
   })
export class ReactFormComponent {
    loginForm: FormGroup;
          
constructor(formBuilder: FormBuilder) {
  this.loginForm = formBuilder.group({
   username: formBuilder.control('', [Validators.required,Validators.minLength(8)]), 
   password: formBuilder.control('', [Validators.required,Validators.pattern("[^ @]*@[^ @]*")])
      });
    }
}

Validateurs

Formulaires

Affichage des erreurs


<form (ngSubmit)="register(loginForm.value)" [formGroup]="loginForm">
 <div class="form-group">
   <label for="username">Nom d'utilisateur</label>
<mat-input-container 
     [ngClass]="{required: loginForm.get('username').hasError('required')}">
  <input type="text" class="form-control" 
                        id="username" formControlName="username" 
                        placeholder="Nom d'utilisateur">
  <mat-hint align="start" *ngIf="loginForm.get('username').hasError('required')">
          La saisie du nom d'utilisateur est obligatoire
  </mat-hint>
</mat-input-container>
 </div>
  <button type="submit" class="btn btn-success">Se connecter</button>
</form>

Formulaires

Custum validator

export class ValidFormComponent {
    
 loginForm: FormGroup;
    
constructor(formBuilder: FormBuilder) {
 const isKnowUser= (control: FormControl) => 
 ['jmadih', 'rinfrayco']
 .filter(v => v === control.value).length ? null : {unknownUser: true};
 this.loginForm = formBuilder.group({
     username: formBuilder.control('', [Validators.required, 
                                        Validators.minLength(3), isKnowUser]),
     password: formBuilder.control('', Validators.required)
     });
     }
}
Si la fonction renvoie un observable la validation deviendra asynchrone.

Exercice

9

git checkout -f step-09

  • Initiez un FormGroup pour le formulaire Introduction (que les champs visibles sur home) au niveau du service SouscriptionInitFormService

  • Ajoutez le validateurs required pour tous les champs et un validateur minLength(20) pour le champs description

Exercice

9

git checkout step-09

  • Initialiser le formulaire dans le template form.component.html (formGroup, fromControlName)

  • Afficher les messages d'erreurs grâce aux composant mat-hint

  • Appeler le endpoint put '/intros/:id' pour modifier le formulaire après le clique sur le bouton save

Solution

9

git checkout -f step-09-solution

Restart Rest server

Des questions ?

Pipes

Permet de filtrer, transformer, tronquer, ... des données brutes

  • Possibilité de chainer les pipes

  • Possibilité de prendre des paramètres

uppercase et lowercase


    <p>{{ 'Formation Angular' | uppercase}}</p>
    
Affichera 'FORMATION ANGULAR'

    <p>{{ 'Formation Angular' | lowercase}}</p>
    
Affichera 'formation angular'

Affiche en majuscules ou minuscules une chaine de caractères

Slice

        <p>{{users | slice:0:2 | json}}</p>
Affichera les 2 premiers éléments du tableau

        <p>{{ 'Formation Angular' | slice:0:9}}</p> <!-- Formation -->

Affichera 'Formation'

        <p>{{ 'Formation Angular' | slice:-7}}</p> <!-- Angular -->

Affichera 'Angular'

Renvoi un sous-ensemble d'un tableau ou chaine de caractères

Percent

 <p>{{ 0.8 | percent }}</p> <!-- 80% -->
 <p>{{ 0.8 | percent:'.3' }}</p> <!-- 80.000% -->

Permet d'afficher ... un pourcentage

Number

        <p>{{ 12345 | number }}</p> <!-- 12,345 -->
        <p>{{ 12345 | number:'9.' }}</p> <!-- 000,012,345 -->
        <p>{{ 12345 | number:'.2' }}</p> <!-- 12,345.00 -->
        <p>{{ 12345.14 | number:'.1-1' }}</p> <!-- 12,345.1 -->
        <p>{{ 12345.16 | number:'.1-1' }}</p> <!-- 12,345.2 -->

Permet de formater un nombre

Prend un seul param sous la forme :

{integerDigits}.{minFractionDigits}-{maxFractionDigits}

Json

    <p>{{ users }}</p>

affichera [object Object].

Pour afficher la représentation JSON de l'objet :

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

Équivalent de JSON.stringify

Date


<p>{{ today | date:'dd/MM/yyyy'}}</p> <!-- 05/02/2017 -->
<p>{{ today | date:'longDate'}}</p> <!-- February 5, 2017 -->
<p>{{ today | date:'HH:mm:ss'}}</p> <!-- 13:48:43 -->
<p>{{ today | date:'shortTime'}}</p> <!-- 1:48 PM -->

Permet de formater les dates

Currency

<p>{{ 20.8 | currency:'EUR' }}</p> <!-- EUR20.80 -->
<p>{{ 20.8 | currency:'USD':true }}</p> <!-- $20.80 -->
<p>{{ 20.8 | currency:'USD':true:'.3' }}</p> <!-- $20.800 -->

Permet d'afficher des monnaies avec les paramètres suivants:

- le code ISO de la devise ('EUR', 'USD')
- un booléen indiquant si on souhaite utiliser le symbole ('€', '$')
- la chaîne de formatage du montant dans le même format que *number*

Async

 @Component({
      selector: 'app-pipes',
      template: `    
        <p> {{usersName$ | async}}</p>
      `
    })
    export class MyComponent {
      usersName$: Observable<string>;
    }

Permet d'afficher des données obtenues de manière asynchrone


Le pipe se charge de se désabonner de la source tout seul.

Custum Pipe

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

usage

    {{myDate | fromNow}}

Exercice

10

  • Créez un pipe UndefinedPipe qui permet d'afficher 'N/A' si aucune valeur n'est renseigner
  • Appliquez ce pipe sur le champs mail de l'introduction

Solution

10

git checkout -f step-10-solution

WebComponents (Angular Element)

Angular Element

Et si on pouvait déployer mon widget Angular sur n'importe quel site ? 

Angular Element

Ne serait-il pas formidable de pouvoir déployer une nouvelle version de notre widget indépendamment du cycle de publication des grandes applications?

Angular Element

 HTTP

WebComponent A

WebComponent B

Application

main.js

main.js

Angular Element

import {BrowserModule} from '@angular/platform-browser';
import {DoBootstrap, Injector, NgModule} from '@angular/core';
import {AppComponent} from './app.component';
import {createCustomElement} from '@angular/elements';
import {NgCardElementComponent} from './ng-card-element/ng-card-element.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  entryComponents: [NgCardElementComponent],
  imports: [
    BrowserModule
  ]
})
export class AppModule implements DoBootstrap {

  constructor(private injector: Injector) {}

  ngDoBootstrap() {
    const el = createCustomElement(NgCardElementComponent, {
      injector: this.injector
    });
    customElements.define('times-up', el);
  }
}

Déclaration du WebComponent

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Time's UP</title>
</head>
<body>
<times-up endtime="27/02/2020 18:00:00" title="Title" 
subtitle="Subtitle" description="description">
</times-up>
</body>
<script src="URL/main.js"></script>
</html>

Angular Element

Utilisation du  WebComponent

Libre

TP

CRUD sur la page skills

 

 

Autres concepts

LazyLoading,

Animation, I18N, Material design, Web worker, RXJS

Server side rendering (Universal)

Liens utiles

TH   NK YOU FOR COMING

Angular

By Sydelab - Shaping Tomorrow