Angular

Formateur: Fabio Ginja

Introduction

Multi Page Application

Chaque changement dans notre page web force un rafraîchissement de la page, et donc le chargement d'une nouvelle page html. Application centrée serveur.

Single Page Application

Chaque changement provoque un appel AJAX au serveur. Ce dernier renvoie des données et ne me à jour que certaines parties de la page. Application centrée client.

Frameworks Javascript

  • Vue.js - Alibaba, Tencent, Baidu
  • Angular - Forbes, Xbox, Parrot
  • React - Facebook, Netflix, Airbnb, Slack
  • Ember.js - Linkedin
  • Polymer - Youtube, Google Play Music, Coca-Cola
  • Backbone.js - Reddit, Amazon
  • Aurelia - seloger
  • Meteor - SFR
  • Mithril - Vimeo, Nike

Les trois frameworks les plus populaires restent Angular, React et Vue.js.

Définition d'un composant

Un composant est une bout de code indépendant et réutilisable. Il sert le même but qu'une fonction mais celui-ci renverra de l'HTML.

 

On va ensuite pouvoir assembler différents composants entre eux pour construire un interface utilisateur (UI) complexe.

 

Sur Angular, un composant est  nécessairement défini par:

  • une classe typescript (qui décrira son comportement)

Mais peut aussi contenir:

  • un fichier HTML (template/affichage du composant)
  • un fichier CSS qui va définir le style du composant
  • un fichier spec qui comprendra les tests unitaires

Définition d'Angular

Angular est un framework frontend javascript pour créer des applications web optimisée en terme de rendu et performances et vous aide à améliorer votre productivité.

Angular repose sur la création de composants pour découper notre application web.

Le framework a été créé par Google et est basé sur une surcouche du javascript: le TypeScript (développé par Microsoft).

Pour installer typescript, il faudra entrer le commande:

npm install -g @angular/cli

Pour installer Angular CLI, il faudra entrer le commande:

npm install --global typescript

Découper son application en composants

Exemple de solution

NavBar, MainPage, SelectLanguage,  et Card sont tous des composants.

Créer une nouvelle application

Angular CLI

Angular CLI (Command Line Interface ou Interface en ligne de commande) nous permet de générer un nouveau projet Angular facilement.

Pour cela il faudra écrire dans notre terminal:

cd mon-application
ng serve --open
# On peut aussi écrire: ng serve -o

Le CLI nous posera quelques questions sur les fonctionnalités que l'on souhaite dans notre application. On peut taper entrer pour les paramètres par défaut. Cela pendra ensuite plusieurs minutes pour installer toutes les dépendances.

ng new mon-application

Pour lancer notre application, il faudra entrer les commandes:

Créer un composant

Sur angular, il nous est possible de générer automatiquement un nouveau composant plutôt que de créer chaque fichier par nous même. Il suffira pour cela de taper la commande:

ng generate component nom-du-composant

Ou alors sa version raccourcie:

ng g c nom-du-composant

Cela aura pour effet de créer un nouveau dossier ainsi que 4 autres fichiers:

Template d'un composant

La template est l'interface utilisateur (UI) du composant. On peut le définir soit dans le modèle (.component.ts), soit dans un fichier HTML séparé (.component.html).

// navbar.component.ts
@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
<!-- nabbar.html -->
<nav>
  <h1>My website</h1>
</nav>

Template directement dans le fichier typescript.

// navbar.component.ts
@Component({
  selector: 'app-navbar',
  template: '<nav><h1>My website</h1></nav>',
  styleUrls: ['./navbar.component.css']
})

Template dans son propre fichier HTML.

Sélecteur

Un composant devra avoir un moyen d'être appelé. On utilisera le sélecteur défini dans notre fichier .component.ts. Ce sélecteur s'utilisera comme une balise classique:

// navbar.component.ts
@Component({
  selector: 'app-navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
<!-- app.component.html -->
<app-navbar></app-navbar>
<main>Contenu...</main>

On peut également appelé le composant comme si cela était un attribut:

// navbar.component.ts
@Component({
  selector: '[app-navbar]',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})
<!-- app.component.html -->
<div app-navbar></div>
<main>Contenu...</main>

Style de l'application

Si on souhaite appliqué du CSS global à notre application, on pourra le faire dans le fichier src/style.css.

/* src/style.css */
li {
  list-style-type: none;  
}

Dans cet exemple, aucun li de notre application n'aura de puce.

C'est le comportement classique auquel on s'attend lorsqu'on n'utilise aucun framework, cependant, on va voir qu'il est possible de rendre la portée du CSS locale à un composant. 

Style d'un composant

Dans notre fichier .component.ts on aura le choix entre deux options pour styliser notre composant:

@Component({
  selector: 'navbar',
  templateUrl: './navbar.component.html',
  styleUrls: ['./navbar.component.css']
})

On peut faire référence au fichier .component.css avec styleUrls. Cela aura pour effet d'avoir une portée locale du CSS (il ne sera appliqué que dans ce fichier et n'impactera pas le reste de l'application).

@Component({
  selector: 'navbar',
  templateUrl: './navbar.component.html',
  styles: [`p { color: brown } span { background-color: lightblue}`, `h2 { margin: 10px }`]
})

Ou alors utiliser styles. La portée sera toujours locale, mais cela nous permet de se passer de fichier CSS séparé (moins utilisé).

p { color: brown }
span { background-color: lightblue}
h2 { margin: 10px }

String Interpolation

Dans notre fichier .component.ts on pourra définir une variable et vouloir l'afficher par la suite dans notre template. Pour ce faire, on utilisera une string interpolation:

// navbar.component.ts
export class NavbarComponent {
  title: string = "Mon super titre"
  name: string = "Ma Marque" 
}

On peut faire référence à une variable (depuis le fichier .component.html) en mettant des doubles accolades. Cela sera éauivalent au résultat suivant:

On notera que l'on peut écrire une expression javascript qui retourne une chaîne de caractères.

<!-- navbar.component.html -->
<nav>
  <h1>{{title}}</h1>
  <em>{{name.toUpperCase()}}</em>
</nav>
<!-- navbar.component.html -->
<nav>
  <h1>Mon super titre</h1>
  <em>MA MARQUE</em>
</nav>

Property Binding

Il est possible de lier une propriété d'un élément HTML avec une variable déclarée dans notre fichier typescript.

// navbar.component.ts
export class NavbarComponent {
  url: string = "http://localhost:4200"
}

Dans cet exemple, on précise que la valeur de la propriété href sera le contenu de la variable url.

Cela est possible avec toutes les propriétés d'un élément HTML. En revanche, ceci n'est pas valable:

<!-- navbar.component.html -->
<nav>
  <a [href]="url">Accueil</a>
</nav>
<!-- navbar.component.html -->
<nav>
  <a href="http://localhost:4200">Accueil</a>
</nav>
<!-- navbar.component.html -->
<a [href]="{{url}}">Accueil</a>

Event Binding

Afin de lier un évènement (lors de click par exemple) avec une méthode, on devra utiliser la syntaxe suivante:

// button.component.ts
export class ButtonComponent {
  onStart() {
    console.log('Start button clicked')
  }
}

Tout les évènements existants en HTML existe également sur angular. La façon de l'écrire diffère un peu (on n'écrit pas le "on" et on met une paire de parenthèses):

  • onclick devient (click)
  • onchange devient (change)
  • onmouseenter devient (mouseenter)
  • onfocus devient (focus)
  • etc...

 

<!-- button.component.html -->
<button (click)="onStart()">Start</button>

Event Binding (2)

Si on a besoin de récupérer l'event, on peut le faire via la variable $event:

// input.component.ts
export class InputComponent {
  onChangeInput(event: Event) {
    const value = (event.target as HTMLInputElement).value
    console.dir(event)
    console.log(value)
  }
}

Le type de cet évènement est Event et le type de d'un input est HTMLInputElement.

<!-- input.component.html -->
<input type="text" (input)="onChangeInput($event)" placeholder="Name">

Template reference

On peut avoir accès à la référence d'un élément HTML dans le fichier .component.html en plaçant un # devant un nom de variable qu'on aura choisi:

// formulaire.component.ts
export class FormulaireComponent {
  onSubmit(nameInput: HTMLInputElement) {
    console.dir(nameInput)
  }
}

Dans le template, on pourra ensuite passer cette référence en paramètre d'une méthode. En revanche, cette référence n'est pas directement accessible dans notre fichier typescript.

<!-- formulaire.component.html -->
<input type="text" #nameInput placeholder="Name">
<button (click)="onSubmit(nameInput)">Submit</button>

@ViewChild

On peut avoir accès à la référence d'un élément HTML dans le fichier .component.ts, on utilisera le décorateur @ViewChild:

// formulaire.component.ts
export class FormulaireComponent {
  @ViewChild('nameInput', {static: true}) nameInput?: ElementRef;
  someMethod() {
    console.log(this.nameInput?.nativeElement.value)
  }
}

Le premier paramètre est la référence ou le (premier) composant que l'on souhaite sélectionner. Le second (optionnel) est nécessaire si on souhaite accéder à cet élément dans la méthode ngOnInit.

<!-- formulaire.component.html -->
<input type="text" #nameInput placeholder="Name">

Enfant d'un composant

Par défaut, ce qui est entre la balise ouvrante d'un composant et sa balise fermante est perdu. Mais on peut avoir accès à l'enfant de ce composant grâce la balise ng-content:

Dans cet exemple, ce qui est entre la balise ouvrante de app-button et la balise fermante sera injecté à la place de ng-content. Très pratique dans la création d'un layout par exemple.

<!-- app.component.html -->
<app-button>Hey!</app-button>
<!-- button.component.html -->
<button>
  <ng-content></ng-content>
</button>

Installer TailwindCSS

Installation

TailwindCSS est un framework CSS basé sur des classes utilitaires. Sa popularité est de plus en plus grande car facile à utiliser, booste notre productivité tout en n'ayant pas d'identité visuelle reconnaissable (et s'adapte donc à tout les chartes graphiques).

Pour l'utiliser sur Angular, il faudra installer les dépendances:

Il faudra ensuite créer les fichiers de configuration:

npm install tailwindcss@latest postcss@latest autoprefixer@latest postcss-cli@latest
npx tailwindcss init -p

Cela aura pour effet de créer deux nouveau fichiers: tailwind.config.js et postcss.config.js.

Purge CSS

module.exports = {
 purge: ['./src/**/*.html', './src/**/*.ts', './projects/**/*.html', './projects/**/*.ts'],
 theme: {
   extend: {},
 },
 variants: {},
 plugins: [],
}

Avec tailwind, il nous est possible de ne pas inclure les classes CSS inutilisé du framework. Pour cela, il faudra spécifier à la ligne purge du fichier tailwind.config.js:

module.exports = {
  plugins: {
    tailwindcss: {}
  }
}

Cela aura pour effet de réduire le temps de chargement de notre page et donc une meilleur expérience utilisateur.

Il faudra aussi supprimer la ligne ayant autoprefixer (car Angular l'utilise déjà) du fichier postcss.config.js.

Créer notre fichier PostCSS

@tailwind base;
@tailwind components;
@tailwind utilities;

Il faudra créer un fichier nommé src/styles.pcss avec le contenu:

{
  "scripts": {
    "start": "ng serve --open",
    "postcss:build": "postcss src/styles.pcss -o src/styles.css",
    "prestart": "npm run postcss:build",
    "prebuild": "npm run postcss:build -- --env=production"
  }
}

Pour finir, il faudra modifier notre fichier package.json afin d'ajouter et modifier les lignes suivantes:

C'est terminé, on peut désormais profiter de TailwindCSS 🎉

Directives (Structurelles)

Définition

Une directive est une classe Angular qui permet d'intéragir avec nos éléments HTML.

Il y a deux types de directives:

On va dans un premier temps s'intéresser aux directives structurelles:

  • ngIf (else)
  • ngSwitch
  • ngFor

On ne pourra appliquer qu'une seule directive structurelle à un seul et même élément.

NgIf

La directive *ngIf peut conditionner l'affichage d'un élément HTML.

On pourra aussi utiliser cette syntaxe plus courte:

<!-- .component.html -->
<div *ngIf="condition; then thenBlock else elseBlock"></div>
<ng-template #thenBlock>Content to render when condition is true.</ng-template>
<ng-template #elseBlock>Content to render when condition is false.</ng-template>
<!-- .component.html -->
<div *ngIf="condition; else elseBlock">
  Ne s'affiche que si la condition est vraie
</div>
<ng-template #elseBlock>
  Ne s'affiche que si la condition est fausse
</ng-template>

Si la condition est fausse, le contenu sera absent de la page (et non pas juste caché).

Le else est bien sûr optionnel.

NgSwitch

La directive *ngSwitch peut aussi conditionner l'affichage de différents éléments HTML si tenter qu'on définisse chaque valeur possible.

<!-- .component.html -->
<container-element [ngSwitch]="switch_expression">
  <some-element *ngSwitchCase="match_expression_1">...</some-element>
  ...
  <some-element *ngSwitchDefault>...</some-element>
</container-element>

On remarquera qu'on pourra écrire une directive structurelle entre crochets [] ou avec l'astérisque *.

NgFor

La directive *ngFor permet de rendre une template pour chaque élément d'une collection (d'un tableau).

<!-- .component.html -->
<ng-template ngFor let-item [ngForOf]="items" let-i="index">
  <li>...</li>
</ng-template>

Il existe aussi une syntaxe plus courte:

<!-- .component.html -->
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
  • item de let-item sera la valeur courante du tableau
  • index de let-i sera l'index de le valeur courante du tableau (notre itérateur)

Cette directive fonctionne comme une boucle for of.

(Built-in) Directives

NgStyle

La directive [ngStyle] permet d'affecter une propriété CSS dont la valeur serait une variable définie dans .component.ts.

Sans ngStyle, ou pourrait aussi donner du style avec:

<!-- .component.html -->
<some-element [ngStyle]="{'font-style': styleExp}">...</some-element>
<some-element [ngStyle]="objExp">...</some-element>
// .component.ts
export class InputComponent {
  /* . . . */
  objExp = {
    'font-style':  this.canSave      ? 'italic' : 'normal',
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal',
    'font-size':   this.isSpecial    ? '24px'   : '12px'
  };
}
<!-- .component.html -->
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
  This div is x-large or smaller.
</div>

NgClass

La directive [ngClass] permet d'affecter une classe CSS dont la valeur serait une variable.

On pourra aussi utiliser cette syntaxe plus courte:

<!-- .component.html -->
<some-element [ngClass]="'first second'">...</some-element>
<some-element [ngClass]="['first', 'second']">...</some-element>
<some-element [ngClass]="{'first': true, 'second': true, 'third': false}">...</some-element>
<some-element [ngClass]="stringExp|arrayExp|objExp">...</some-element>
<some-element [ngClass]="{'class1 class2 class3' : true}">...</some-element>

<div [ngClass]="isSpecial ? someClass : ''">This div is special</div>
<div [ngClass]="isSpecial ? 'special' : ''">This div is special</div>

DataBinding

@Input

Si on souhaite passer une variable depuis le composant parent au composant enfant, on pourra la faire via le décorateur @Input()

// enfant.component.ts
export class EnfantComponent {
  @Input() prenom: string;
}

Je peux aussi renommer la variable qui m'est passée via un @Input(). Voici comment faire:

<!-- parent.component.html -->
<app-enfant prenom="Fabio"></app-enfant>
// enfant.component.ts
export class EnfantComponent {
  @Input("prenom") prenomUtilisateur: string;
}

@Output - Child

Si on souhaite passer un évènement depuis le composant enfant au composant parent, on pourra la faire via le décorateur @Output()

// child.component.ts
import { EventEmitter, Output } from '@angular/core';

export class ChildComponent {
  @Output() newItemEvent = new EventEmitter<string>();
  addNewItem(value: string) {
    this.newItemEvent.emit(value);
  }
}
<!-- child.component.html -->
<label>Add an item: <input #newItem></label>
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>

@Output - Parent

On doit maintenant configurer le parent pour qu'il puisse recevoir cet évènement.

// parent.component.ts
export class ParentComponent {
  items = ['item1', 'item2', 'item3', 'item4'];

  addItem(newItem: string) {
    this.items.push(newItem);
  }
}
<!-- parent.component.html -->
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>
<ul>
  <li *ngFor="let item of items">{{item}}</li>
</ul>

Two-Way-Binding

Le two-way-binding est une combinaison entre le property binding et l'event binding:

// input.component.ts
export class InputComponent {
  content = 'content value';
}

Il permet de lier la valeur d'un input avec une variable présente dans notre .component.ts

⚠️Pour que le two-way-binding soit possible, il faudra importer FormsModule dans notre fichier app.module.ts.

<!-- input.component.html -->
<input type="text" [(ngModel)]="content">
<p>{{content}}</p>

Routing

Principe

Dans une Single Page Application, on a... qu'une seule page. Cependant, il nous est possible de simuler un site avec plusieurs pages et plusieurs routes grâce au routeur d'Angular.

 

Cela permettra qu'un utilisateur partage un lien d'une page en particulier plutôt que de toujours pointer vers le page d'accueil (/).

 

Nous allons voir comment rendre cela possible.

Définition des routes

Dans notre fichier app-routing.module.ts, on a une variable routes. Cette variable est un tableau d'objets où chaque objet correspond à une route.

// app-routing.module.ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'products', component: ProductListComponent },
  { path: '404', component: NotFoundComponent},
  { path: '**', redirectTo: '/404'}
];

Une fois cela fait, on doit préciser où afficher le composant donné une fois sur la bonne route. Pour ce faire, on doit appeler la balise router-outlet dans une template HTML:

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

Naviguer entre les routes

Pour naviguer entre les différentes pages, il ne faut pas utiliser l'attribut href de balise <a> classique car cette dernière aura pour effet de rafraîchir notre page (or dans une SPA, l'intérêt est de ne pas rafraîchir la page).

Pour palier à ce comportement, on utilisera la directive routerLink sur un lien:

Cette écriture est également possible:

<!-- app.component.html -->
<nav>
  <a routerLink="/">Home</a>
  <a routerLink="/products">Products</a>
</nav>
<!-- app.component.html -->
<nav>
  <a [routerLink]="['/']">Home</a>
  <a [routerLink]="['/products']">Products</a>
</nav>

Lien Actif

Si ou veut qu'un lien ou un élément prenne un style particulier lorsque la page est active, on peut le faire via routeLinkActive:

Pensez à rajouter des options pour le lien ne s'active uniquement que lorsque le chemin correspond parfaitement:

<!-- app.component.html -->
<nav>
  <a routerLink="/" routerLinkActive="text-red-500">Home</a>
  <a routerLink="/products" [routerLinkActive]="['text-red-500']">Products</a>
</nav>

Cela est particulièrement utile par exemple pour différencier le lien de la page active du reste des autres liens dans la barre de navigation.

<!-- app.component.html -->
<nav>
  <a routerLink="/" 
     routerLinkActive="text-red-500"
     [routerLinkActiveOptions]="{exact: true}">
    Home
  </a>
</nav>

Navigation typescript

Il se peut qu'on veuille déclencher le changement d'une page depuis une méthode. Voici comment faire:

Il faudra ajouter la variable router au constructeur afin de l'utiliser ensuite dans notre code. On a ensuite (entre autres) accès à la méthode navigate qui permet d'aller vers d'autres pages.

<!-- exemple.component.html -->
<button (click)="goToAboutPage()">About Page</button>
// exemple.component.ts
import { Router } from "@angular/router";

export class ExempleComponent {
  constructor(private router: Router) {}

  goToAboutPage() {
    this.router.navigate(["/about"])
  }
}

Route parameters

Pour passer un paramètre à une route, on utilisera :variable dans l'objet de définition d'une route:

Il faudra ajouter la variable router au constructeur afin de l'utiliser ensuite dans notre code. On a ensuite accès (entre autres) à la méthode navigate qui permet d'aller vers d'autres pages.

<!-- exemple.component.html -->
<button (click)="goToAboutPage()">About Page</button>
// app-routing.module.ts
const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'products', component: ProductListComponent },
  { path: 'products/:id', component: ProductComponent },
];

Get Route parameters

Pour ensuite avoir accès au paramètre passé dans l'URL, il nous faudra utiliser ActivatedRoute dans notre .component.ts:

La variable route contient des propriétés et des méthodes utiles pour retrouver la route sur laquelle on se trouve, ou bien les paramètres de cette route. Avec snapshot, on obtient un instantané des paramètres, alors qu'avec le méthode subscribe, ou s'abonne aux futurs changements de paramètres qui pourraient subvenir.

// exemple.module.ts
import { ActivatedRoute, Params } from "@angular/router";

export class ExempleComponent implements OnInit {
  constructor(private route: ActivatedRoute) { }

  ngOnInit(): void {
    console.log(this.route.snapshot.params.language)
    this.route.params.subscribe((params: Params) => {
      console.log(params.language)
    })
  }
}

Services

Principe

Un service est simplement une classe qui aura le décorateur @Injectable() (utile pour l'injection de dépendances).

Un service est utile pour partager des propriétés ou des méthodes utilisés dans plusieurs composants.

Au lieu de re-écrire la même méthode au sein de deux composant différent, on fera appel à un service qui sera responsable d'éxécuter cette méthode.

Pour créer un nouveau service, on exécutera la commande:

ng generate service nom-du-service
ng g s nom-du-service

Ou la version raccourcie:

Un service peut aussi être un singleton avec providedIn.

Création d'un service

Une fois notre fichier .service.ts créé, on pourra lui donner des propriétés et des méthodes accessibles depuis nos composants.

// languages.service.ts
@Injectable()
export class LanguagesService {
  constructor() {}
  private languages: string[] = ['javaScript', 'ruby', 'java', 'css', 'python'];

  getLanguages() {
    return this.languages;
  }
}

Consommer un service

Pour consommer un service dans notre fichier .component.ts, il faudra rajouter le propriété providers dans notre décorateur @Component, puis faire appel au constructeur de notre classe.

// select-language.component.ts
@Component({
  selector: 'app-select-language',
  templateUrl: './select-language.component.html',
  styleUrls: ['./select-language.component.css'],
  providers: [LanguagesService]
})
export class SelectLanguageComponent implements OnInit {
  constructor(private languageService: LanguagesService) { }
  languages: string[] = []

  ngOnInit(): void {
    this.languages = this.languageService.getLanguages()
  }
}

Portée d'un service

Si on souhaite qu'une même instance du service soit disponible dans toute notre application ainsi que part d'autres services, on l'importera dans AppModule:

// app.moodule.ts
@NgModule({
  providers: [ExempleService],
})
// Ou alors depuis Angular 6 (lazy loading service): exemple.service.ts
@Injectable({providedIn: 'root'})

Si on souhaite qu'une même instance du service soit disponible seulement dans les composants  de toute notre application, on l'importera dans AppComponent.

 

Si on souhaite qu'une même instance du service soit disponible seulement dans un composant et ses descendants alors on l'importera dans ExempleComponent.

Pipes

Liste des différents Pipes

Il est possible de transformer une chaine de caractères grâce à des pipes:

Ipsum

<!-- exemple.component.html -->
<p>{{"exemple" | uppercase}}</p>
<!-- Affichera "EXEMPLE" -->
<p>{{"TITRE" | lowercase}}</p>
<!-- Affichera "titre" -->
<p>{{"auTre exeMple" | titlecase}}</p>
<!-- Affichera "Autre Exemple" -->
<p>{{"exemple" | slice:1:4}}</p>
<!-- Affichera "xem" -->
<p>{{"0.259" | percent}}</p>
<!-- Affichera "26%" -->

Il est possible de transformer une chaine de caractères grâce à des pipes:

Cycle de vie

Liste des différents cycle de vie

  • ngOnChanges (@Input)
  • ngOnInit
  • ngDoCheck
  • ngAfterContentInit
  • ngAfterContentChecked
  • ngAfterViewInit
  • ngAfterViewChecked
  • ngOnDestroy

Lorem

Lorem .component.ts, .

Ipsum

<!-- .component.html -->
<button (click)="goToAboutPage()">About Page</button>
// .component.ts
@Component({
})
export class SelectLanguageComponent implements OnInit {

}

Observables

Définition

Un observable est une source de données (ou un flux de données). Ce flux de données pourra être ensuite observé par un Observer. Un exemple d'observable peut être un appel HTTP comme un click sur un bouton.

On pourra recevoir les données de ces observables mais aussi gérer les erreurs plus facilement s'il y en a.

On a déjà vu un observable lorsqu'on souhaite avoir les modifications des paramètres d'une route:

// exemple.module.ts
this.route.params.subscribe((params: Params) => {
  console.log(params.language)
})

Ce principe d'observable est apporté par la librairie RxJS

Création d'un observable

Si on souhaite créer un nouvel observable, on peut le faire dans le fichier .component.ts comme suit:

export class ExempleComponent implements OnInit, OnDestroy {
  private sub: Subscription

  ngOnInit(): void {
    const monObservable = new Observable((observer: Observer<any>) => {
      let count = 0
      const id = setInterval(() => {
        if (count < 0) observer.error(new Error("What the fork?!"))
        else if (count > 10) observer.complete()
        else observer.next(count)
        count++
      }, 1000)
      return {
        unsubscribe() {
          clearInterval(id)
        }
      }
    })
    this.sub = monObservable.subscribe(count => console.log(count), err => console.error(err), () => console.log("Finish!"))
  }

  ngOnDestroy(): void {
    this.sub.unsubscribe()
  }
}

Observables Pipes

Il est possible de transformer les données que l'on va recevoir d'un observable avec un pipe operators:

// .component.ts
observable.pipe(map((v: number) => (2 * v))).subscribe(...);

Formulaires

FormsModule

Angular nous offre des outils permettant de manipuler et valider nos formulaires avec facilité. Cependant on aura besoin d'importer FormsModule, dans notre fichier app.component.ts afin de l'utiliser par la suite.

Ipsum

// app.module.ts
import { FormsModule } from "@angular/forms";

@NgModule({
  imports: [
    FormsModule,
  ],
})
export class AppModule { }

Utiliser FormsModule

Afin d'avoir accès aux données saisies par l'utilisateur, il faudra préciser que le formulaire utilise ngForm ainsi que ses différents champs dans notre .component.html.

Et on peut ainsi avoir accès à ce formulaire via notre fichier .component.ts.

<!-- .component.html -->
<form (ngSubmit)="onSubmit()" #form="ngForm">
  <input type="text" id="username" ngModel name="username">
  <button type="submit">Submit</button>
</form>
// .component.ts
export class ExempleComponent implements OnInit {
  @ViewChild("form") heroForm: NgForm;
  onSubmit() {
    console.log("Form Submitted", this.heroForm);
  }
}

Accès à un input (template)

On peut savoir si un input est valide directement dans notre fichier .component.html. Voici comment faire:

Il faudra ajouter une template reference dans notre input, et celle-ci devra être égale à ngModel (afin de rajouter la validation).

Enfin, un autre balise de notre choix aura accès à la validité de notre input via sa référence et vérifié s'il est valide et s'il à été modifié.

<!-- .component.html -->
<form (ngSubmit)="onSubmit()" #form="ngForm">
  <input type="text" id="username" ngModel name="username" #usernameInput="ngModel" required>
  <p *ngIf="!usernameInput.valid && usernameInput.touched">Username is required</p>
  <button type="submit">Submit</button>
</form>

Input et One Way Binding

On peut aussi faire référence à une variable déclarée dans notre fichier .component.ts grâce au property binding.

<!-- .component.html -->
<form (ngSubmit)="onSubmit()" #form="ngForm">
  <input
    type="text"
    id="username"
    [ngModel]="defaultUsername"
    name="username">
  <button type="submit">Submit</button>
</form>
// .component.ts
export class ExempleComponent {
  defaultUsername: string = "Tintin";
}

Input et Two Way Binding

On peut aussi faire référence à une variable déclarée dans notre fichier .component.ts ET y avoir accès dans notre template:

<!-- .component.html -->
<form (ngSubmit)="onSubmit()" #form="ngForm">
  <input
    type="text"
    id="username"
    [(ngModel)]="choosenUsername"
    name="username">
  <p>Choosen username: {{choosenUsername}}</p>
  <button type="submit">Submit</button>
</form>
// .component.ts
export class ExempleComponent {
  choosenUsername: string = "";
}

Manipuler depuis le ts

Il est possible de manipuler la valeur d'un input de notre formulaire depuis notre fichier typescript. C'est utile pour manipuler directement une valeur, ou même réinitialiser le formulaire grâce à la méthode reset.

<!-- .component.html -->
<form (ngSubmit)="onSubmit()" #myForm="ngForm">
// .component.ts
export class ExempleComponent {
  onModify() {
    this.myForm.form.patchValue({
      username: "updated"
    });
    onReset() {
      this.myForm.reset();
    }
  }
}

Validateurs

On peut avoir plus d'informations sur les différents validateur qui existent pour nos inputs: documentation officielle

Requêtes HTTP

HttpClientModule

Si on souhaite faire des appels HTTP, on pourra le faire en utilisant le module HTTP fournit par Angular. On aura  alors besoin d'importer HttpClientModule dans notre fichier app.component.ts afin de l'utiliser dans notre application.

Ipsum

// app.module.ts
import { HttpClientModule } from '@angular/common/http';

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

export class AppModule { }

Get Request

Pour récupérer des données du serveur, on fera comme suit:

// .component.ts
import { HttpClient } from "@angular/common/http";

export class ExempleComponent {
  constructor(private http: HttpClient) {}
  
  getTodos() {
    this.http
      .get("https://jsonplaceholder.typicode.com/todos")
      .subscribe(response => console.log(response));
  }
}

Il est possible d'utiliser la méthode pipe entre la méthode get et la méthode subscribe afin de manipuler la réponse et lui donner la structure qu'on souhaite. 

this.http
  .get<string[]>("url.com/exemples/")
  .pipe(map(response => response.data))
  .subscribe(data => console.log(data));

Post Request

Pour envoyer des données au serveur, on fera comme suit:

// .component.ts
import { HttpClient } from "@angular/common/http";

export class ExempleComponent implements OnInit {
  constructor(private http: HttpClient) {}
  
  onPost() {
    const data = {
      userId: 1,
      title: "Learning angular",
      completed: false,
    }
    this.http
      .post("https://jsonplaceholder.typicode.com/todos", data)
      .subscribe(response => console.log(response));
  }
}

Delete Request

Pour supprimer des données au serveur, on fera comme suit:

// .component.ts
import { HttpClient } from "@angular/common/http";

export class ExempleComponent implements OnInit {
  constructor(private http: HttpClient) {}
  
  onDelete() {
    this.http
      .delete("https://jsonplaceholder.typicode.com/todos/5")
      .subscribe(response => console.log(response));
  }
}

Gestion d'erreurs

Il est possible d'avoir une erreur lors d'une requête HTTP. Il est essentiel d'avoir une gestion sensible de ces erreurs. Pour cela, plusieurs solutions sont possibles:

export class ExempleComponent {
  getTodos() {
    this.http.get("https://jsonplaceholder.typicode.com/todos")
      .subscribe(
        response => console.log(response),
        error => console.error(error),
      );
  }
}
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
export class ExempleComponent {
  getTodos() {
    this.http.get("https://jsonplaceholder.typicode.com/todos")
      .pipe(catchError(error => throwError(error)))
      .subscribe(response => console.log(response));
  }
}

Headers

Lorem .component.ts, .

Ipsum

<!-- .component.html -->
<button (click)="goToAboutPage()">About Page</button>
import { HttpClient, HttpHeaders } from "@angular/common/http";

export class ExempleComponent {
  getTodos() {
    this.http.get("https://jsonplaceholder.typicode.com/todos", {
      new HttpHeaders({"token": "mon-user-token"})
    })
      .subscribe(
        response => console.log(response),
        error => console.error(error),
      );
  }
}

Angular

By Fabio Ginja

Angular

Slides de formation

  • 522