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:
- les directives d'attributs (attribute directives)
- les directives structurelles (structural 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%" -->
Mais il existe aussi:
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
// .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