Julien Lepasquier & Jean-Marc Debicki
Excilys - 2021
NGULAR
v12.x.x

Prérequis
- NodeJS (LTS) v12.1.x+
- NPM v6.9.0+
-
Angular CLI v12.x.x+
- IDE Front : Visual Studio Code, Atom, Sublim Text, Webstorm (1 mois gratuit)
- (opt) Git
npm install @angular/cli

Présentation


Un peu d'histoire ...
- Juin 2012 : AngularJS (Miško Hevery et Adam Abronsw puis Google)
- Septembre 2016 : Angular 2
- Mars 2017 : Angular 4
- ...
- Mai 2021 : Angular 12

Application traditionnelle
- Échanges client-serveur lourds
- Rechargement de la page
- Pas d'offline

Initial Request
Form Post
Client

Server


Page reload !
Single Page Application

- Fluidité
- User experience (UX)
- Offline
Initial Request
XHR
{ ... } JSON
Client

Server
Typescript
Sur-ensemble de Javascript
Open source
Développé par Microsoft
Transpilé en Javascript
Typage statique
ECMAScript 6
Class, Interfaces

Syntaxe de Typescript

export class MonComponent {
private maVariable: number | boolean | string = 'abc';
protected array: any[];
public abc: null | undefined;
static readonly CONST = "CONSTANTE";
constructor(private readonly service: Service) {}
maFonction(attr: number): void {
const constante = 123;
let variable = "abc";
let monArrowFunction = (a, b) => a+b;
}
}
Survol d'Angular


Component tree

Root Component
Root Template
< >
Root Class
{ }
Child A Component
Child A Template
< >
Child A Class
{ }
Child B Component
Child B Template
< >
Child B Class
{ }
Grandchild Component
Grandchild Template
< >
Grandchild Class
{ }
Component

Life Cycle Hooks
@Component({
selector: 'parent-component',
template: '<child-component></child-component>',
styleUrls: string[],
...
})
class ParentComponent {}
@Component({
selector: 'child-component',
template: 'Salut les grands malades !'
})
class ChildComponent {}

Injectable

@Injectable({
providedIn: 'root'
})
class MonService {}
class MonComponent {
constructor(private readonly service: Service) {}
}
Module
@NgModule({
declarations: any[],
imports: any[],
exports: any[],
providers: any[],
...
})
export MonMondule {}

Directive
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}

<p appHighlight>Je suis surligné</p>
<p>Pas moi</p>
*ngIf
<div *ngIf="expressionBooleenne">
Hello
</div>

[hidden]
<div [hidden]="expressionBooleenne">
Hello
</div>

*ngFor
<ul>
<li *ngFor="let item of itemList">
{{ item }}
</li>
</ul>

Template binding {{ }}
<div>
Hello {{ name }}
</div>

Dom events
<button (click)="doSomething()">
Click me !
</button>
Liste des HTML DOM Events sans le "on" :
click, dblclick, mousedown, keypress, change, resize, blur, focus, ...

Data binding

{{ value }}
[property]="value"
(event)="handlerFunction($event)"
[(ngModel)]="property"
DOM
Component
Vue d'ensemble

Template
< >
Component
{ }
Injector
Service
{ }
LoginModule
{}
{{ person.name }}
(click)="handle()"
Metadata
Directive
{ }
Metadata
UserModule
{}
CardModule
{}
ListModule
{}


A vos claviers !
Excilys Cocktails
Génération "boilerplate" :
ng new first-app
> CSS ?
> SCSS
> Routing ?
> no

Excilys Cocktails
Lancement du serveur :
ng serve
Ou :
npm start

Excilys Cocktails
Arborescence
package.json

Excilys Cocktails
Un peu d'aide côté design : Angular Material
Installation :
ng add @angular/material

Excilys Cocktails
Ajout d'un header
Dans src/app, créer le composant Header :
ng generate component header

Excilys Cocktails
Dans src/app :
ng generate module custom-material

Excilys Cocktails
Affichage d'une recette
Composant Recipe :
ng generate component recipe

Excilys Cocktails
Création du modèle
recipe.model.ts
recipe-ingredient.model.ts
ingredient.model.ts

Excilys Cocktails
Création du modèle
import { RecipeIngredient } from './recipe-ingredient.model';
export class Recipe {
id?: number;
name: string;
picture: string;
description: string;
ingredients: RecipeIngredient[];
instructions: string[];
}

Excilys Cocktails
Création du modèle
import { Ingredient } from './ingredient.model';
export class RecipeIngredient {
id?: number;
ingredient: Ingredient;
quantity: number;
unit: string;
}

Excilys Cocktails
Création du modèle
export class Ingredient {
name: string;
id?: number;
}

Excilys Cocktails
Affichage des recettes
Paramètre d'entrée du composant :
@Input()
recipe: Recipe;

Excilys Cocktails
Affichage d'une liste de recettes
Composant Recipe List :
ng generate component recipe-list

Excilys Cocktails
Affichage des recettes
Afficher la liste :
<app-recipe *ngFor="let recipe of recipes"
[recipe]="recipe">
</app-recipe>

Excilys Cocktails
Affichage des recettes
Afficher / masquer les détails :
<button (click)="toggleExpand()">
See more
</button>

Excilys Cocktails
Service RecipeService
Création du service :
ng generate service recipe

Excilys Cocktails
Service RecipeService
Refacto permettant de gérer l'asynchrone :
getRecipes(): Observable<Recipe[]> {
return of(RECIPES);
}

Excilys Cocktails
Récupération des données de l'Observable
Dans un composant appelant le getRecipes() du service.
this.recipeService.getRecipes().subscribe(
(result: Recipe[]) => {
// Traiter le résultat
},
(error) => {
// Traiter l'erreur
}
);

Excilys Cocktails
Passage au HttpClient :
imports: [
...,
HttpClientModule,
...
]
AppModule :

Excilys Cocktails
Passage au HttpClient :
private recipeUrl = 'api/recipes';
getRecipes(): Observable<Recipe[]> {
return this.http.get<Recipe[]>(this.recipeUrl);
}
RecipeService :

Excilys Cocktails
Passage au HttpClient :
getRecipe(id: number): Observable<Recipe> {
return this.http.get<Recipe>(`${ this.recipeUrl }/${ id }`);
}
RecipeService, ajout d'une requêt "by ID" :

Excilys Cocktails
Routing :
ng generate module app-routing --flat
AppRoutingModule

Excilys Cocktails
Routing :
const routes: Routes = [
{
path: 'recipes',
component: RecipesComponent,
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'recipes',
pathMatch: 'full'
}
];
AppRoutingModule

Excilys Cocktails
Routing :
@NgModule({
exports: [
RouterModule
],
imports: [
RouterModule.forRoot(routes)
]
})
AppRoutingModule

Excilys Cocktails
Routing :
<router-outlet></router-outlet>
Point d'entrée des routes

Excilys Cocktails
Routing :
Refacto : Modularisation de Recipe

A vous de jouer


Excilys Cocktails
Nouvelle page : Détail d'une recette
- Ajout d'une nouvelle page + composant : recipe-detail.component
- Navigation par le router : /recipes/:id
- Récupération d'une recette "byId"
TIPS :
- Créer une nouvelle route recipes/:id permet de variabiliser le param id
- L'attribut routerLink="/recipes/{{ recipe.id }}" permet de naviguer vers une la nouvelle route
- this.route.snapshot.paramMap.get('id') permet de récupérer le paramètre de la route à la création du composant (this.activatedRoute doit être injecté depuis le service Angular ActivatedRoute)

Excilys Cocktails
Nouvelle page : Formulaire d'ajout de recette
- Ajout d'une nouvelle page de formulaire de création de recette
- Ajout d'une nouvelle fonctionnalité POST dans le service
TIPS :
- Binding d'une donnée sur un élément HTML : [(ngModel)]="maVariable"

Excilys Cocktails
Suppression de recette
- Bouton de suppression de recette
- Ajout d'une nouvelle fonctionnalité DELETE dans le service

Pour les rapides


Excilys Cocktails
Reactive Forms : Validation dynamique de formulaires
https://angular.io/guide/reactive-forms#add-a-formgroup

Excilys Cocktails
Pipes : Transformer les données
@Pipe({
name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {
transform(param : InputType): OutputType {
doSomething();
return newValue;
}
}

Excilys Cocktails
Tests unitaires !
Karma + Jasmine

Excilys Cocktails
Animations : Animer les composants
https://angular.io/guide/animations#setup

Les liens utiles

Formation Angular
By Julien Lepasquier
Formation Angular
- 354