Tribu Angular
Oxyl
NGULAR

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
- ...
- Novembre 2024 : Angular 19
- Fin mai 2025 : Angular 20

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
- Transpilé en JavaScript.
- Typage fort : Autocomplétion, Erreur à la compilation, Maintenabilité
- Suit les normes ECMAScript.
- Javascript : typage dynamique => Typescript : typage statique.
- Développé par Microsoft et Open source

Survol d'Angular


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

Component

Life Cycle Hooks
@Component({
selector: 'parent-component',
template: '<child-component></child-component>',
styleUrls: string[],
...
})
export class ParentComponent {}
@Component({
selector: 'child-component',
template: 'Salut les grands malades !'
})
export class ChildComponent {}
Module (deprecated)
@NgModule({
//Declare components, directives, and pipes that belong to this module
declarations: any[],
//Import modules whose exported classes are needed in this module
imports: any[],
//Make components, directives, and pipes available to other modules
exports: any[],
//Register services (injectables) that this module provides
providers: any[],
...
})
export class MonMondule {}
Standalone Component
@Component({
selector: 'my-standalone_component',
standalone: true, // This makes it a standalone component
imports: [CommonModule], // Can import standalone or NgModules
template: `
<h1>Hello from a standalone component!</h1>
<p>This component doesn't need to be declared in any NgModule.</p>
`,
styles: ...
})
export class HelloWorldComponent {
}
Injectable
@Injectable({
providedIn: 'root'
})
export class MonService {}
export class MonComponent {
constructor(private readonly service: Service) {}
}
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>
Data binding
{{ value }}
[property]="value"
(event)="handlerFunction($event)"
[(ngModel)]="property"
DOM
Component

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, ...

Signals
import { signal, computed, effect } from '@angular/core';
// Create a reactive signal that holds a value
const count = signal(0); // ✅ signal<T>(initialValue)
// Create a computed signal that derives its value from another signal
// auto-updates when 'count' changes
const doubleCount = computed(() => count() * 2);
// Set up an effect to run logic whenever a signal changes
effect(() => {
console.log(`Count is ${count()} and double is ${doubleCount()}`);
});
// Update the signal (triggers effect and recomputes doubleCount)
count.set(1); // logs: Count is 1 and double is 2
count.update(n => n + 1); // logs: Count is 2 and double is 4
Signals
increment() {
this.count.update(n => n + 1);
}
reset() {
this.count.set(0);
}
<h2>Signal Counter</h2>
<p>Current count: {{ count() }}</p> <!-- 👈 Use signal like a function -->
<button (click)="increment()">Increment</button>
<button (click)="reset()">Reset</button>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
{ }

Arborescence


Premier pas

Créer et lancer un projet
ng new first-app
cd first-app
npm install
npm start
NE PAS CRÉER EN MODE SSR !!!
Objectif : menu de cocktails
Créer un composant
ng generate component components/recipe
Créer un premier modèle
export interface Ingredient {
name: string;
id?: number;
}
Créer un module
ng generate module custom-material
Créer un cocktail

public recipe: Recipe = {
description: "Les jolis voyages",
instructions: [
"Verser l’absinthe dans un verre. L’idéal serait un verre à absinthe, mais une petite coupe à dégustation peut faire l’affaire. Déposer la cuillère à absinthe sur le verre et y déposer le cube de sucre.",
"Faire couler l’eau de source glacée très doucement, quelques gouttes à la fois, sur le cube de sucre. Enfin, remuer l’absinthe avec la cuillère et servir.",
"À savourer en bonne compagnie, à 17 h, en écoutant de l’accordéon et en récitant Les Fleurs du Mal.",
""
],
name: "l'absinthe",
ingredients: [
{
id:0,
ingredient: {
id:1,
name: "absinthe"
},
quantity: 1,
unit: "Oz"
},
{
id:1,
ingredient: {
id:6,
name: "sucre"
},
quantity: 1,
unit: "cube"
},
{
id:2,
"ingredient": {
"id":4,
"name": "eau de source glacée"
},
"quantity": 3,
"unit": "oz"
},
{
id:3,
"ingredient": {
id: 12,
"name": "cuillère à absinthe perforée"
},
"quantity": 1,
"unit": "cuillère"
}
],
picture: "https://upload.wikimedia.org/wikipedia/commons/e/e2/Absinthe-glass.jpg"
}Conseil : Faire un service avec un getter
*ngFor
<ul>
<li *ngFor="let item of itemList">
{{ item }}
</li>
</ul>
Pour afficher une liste
Conseil : séparer la liste et l'item en deux composants
*ngIf
<div *ngIf="expressionBooleenne">
Hello
</div>
Import une donnée du parent
Paramètre d'entrée du composant :
@Input()
recipe: Recipe;
<mon-composant-enfant [recipe]="maRecette"></mon-composant-enfant>Design
Un peu d'aide côté design : Angular Material
Installation :
ng add @angular/material

- Créer et lancer le projet.
- Créer votre premier composant: Recipe.
- Créer des modèles: Recipe, Recipe Ingredients, Ingredients.
- Créer une constante de type Recipe dans votre composant.
- Afficher les données dans le composant.
- Créer un composant parent: Recipe list.
- Mettre une liste de Recipe dans le componant Recipe List.
- Afficher une liste de Recipe.
- Ajouter un bouton toggle pour afficher ou retirer les instructions / ingrédients.
- Ajouter une librairie de composants stylisés.
- Ajouter un compteur de verre bu par recette avec un Signal
Pour les rapides: Mettre Angular Material dans un module; Créer un composant Header; Styliser tes composants; Ajouter un compteur de verre global
A vous de jouer
On continue


Créer un service
ng generate service recipe
L'asynchrone en JS:
Les promesses
new Promise((resolve, reject) => { /* execution en asynchrone */ })
.then(result => { /* Traiter le résultat */ })
.catch(error => { /* Traiter l'erreur */ });
RXJS
La library JAVASCRIPT pour faciliter l'utilisation de l'asynchrone.
getRecipes(): Observable<Recipe[]> {
return of(RECIPES);
}
Observable et Subject :
this.recipeService.getRecipes().subscribe({
next: (result: Recipe[]) => { /* Traiter le résultat */ },
error: (error) => { /* Traiter l'erreur */ }
});Pour en arriver où ?
Http Client
En Standalone,
à ajouter dans le component app:
providers : [HttpClient]

private recipeUrl = 'api/recipes';
constructor(private http: HttpClient) { }
getRecipes(): Observable<Recipe[]> {
return this.http.get<Recipe[]>(this.recipeUrl);
}En Module,
à ajouter dans le module app:
providers : [HttpClient]
Le routage 1/2
Créer un module dédié : AppRoutingModule
L'importer dans le module principal

@NgModule({
exports: [
RouterModule
],
imports: [
RouterModule.forRoot(routes)
]
})
export class AppRoutingModule { }Le routage 1/2
Modifier le app.config.js pour ajouter le routing

import { routes } from './app.routes';
import { provideRouter } from '@angular/router';
import { provideHttpCLient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [provideRouter(routes), provideHttpClient()]
};Le routage 2
Définir une stratégie de route :

const routes: Routes = [
{
path: 'recipes',
component: RecipeListComponent,
pathMatch: 'full'
},
{
path: '**',
redirectTo: 'recipes',
pathMatch: 'full'
}
];<router-outlet></router-outlet>Point d'entrée des routes

- Créer un service : Recipe.
- Mettre la liste de recettes dans le service en asynchrone.
- Faire sa première requête http sur le back.
- Ajouter le module de routage.
- Créer une nouvelle page pour voir les détails d'une recette (Get by ID + page de routage spécifique + composant Recipe Detail).
Pour les rapides : Lors de la récupération des recettes, ajouter un tri par nom; Sur la page recipe ID, ajouter un "guard" dans le router pour vérifier que la recette existe
A vous de jouer
http://52.47.189.244:8080/swagger-ui/index.html
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)
Encore un peu plus loin


Reactive Forms
Validation dynamique de formulaires
https://angular.io/guide/reactive-forms


- Créer une nouvelle page d'ajout de recette.
- Créer un formulaire pour créer une recette avec un nom, une url d'image, et une description.
- Ajouter une validation pour rendre les champs obligatoires.
- Ajouter une dropdown pour sélectionner un ou plusieurs ingrédients.
- Ajouter une option de suppression de recette sur la page principale.
Pour les rapides : Sur le formulaire d'ajout, forcer l'utilisation d'au moins une instruction et d'au moins un ingrédient, qu'une instruction ne peut pas être vide etc...
A vous de jouer
http://52.47.189.244:8080/swagger-ui.html#/
https://angular.io/guide/reactive-forms
Pour les rapides



- Gérer l'update des recipes (PUT et PATCH).
- Gestion des ingédients sur une page à part.
- Avoir la liste des recettes où l'ingrédient est utilisé.
- Gestion des erreurs avec Interceptor et snackbar.
- Filtres et Tris :
- Par nom de cocktails, par ingrédient présent, par boisson bue.
- Gestion du compteur de boisson total lors d'une suppression.
- Drag and drop des ingrédients pour créer un cocktail
ENCORE PLUS DE FEATURES
Pipes
@Pipe({
name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {
transform(param : InputType): OutputType {
doSomething();
return newValue;
}
}
Tests unitaires !

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

Les liens utiles

Formation Angular v3
By Vincent Protois
Formation Angular v3
- 51