Tribu Angular
Oxyl
NGULAR
Initial Request
Form Post
Client
Server
Page reload !
Initial Request
XHR
{ ... } JSON
Client
Server
Template
< >
Component
{ }
Injector
Service
{ }
LoginModule
{}
{{ person.name }}
(click)="handle()"
Metadata
Directive
{ }
Metadata
UserModule
{}
CardModule
{}
ListModule
{}
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 {}@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 {}@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({
providedIn: 'root'
})
export class MonService {}
export class MonComponent {
constructor(private readonly service: Service) {}
}@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(el: ElementRef) {
el.nativeElement.style.backgroundColor = 'yellow';
}
}<p appHighlight>Je suis surligné</p>
<p>Pas moi</p>{{ value }}
[property]="value"
(event)="handlerFunction($event)"
[(ngModel)]="property"
DOM
Component
<div>
Hello {{ name }}
</div><button (click)="doSomething()">
Click me !
</button>Liste des HTML DOM Events sans le "on" :
click, dblclick, mousedown, keypress, change, resize, blur, focus, ...
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 4increment() {
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>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
{ }
ng new first-app
cd first-app
npm install
npm startNE PAS CRÉER EN MODE SSR !!!
Objectif : menu de cocktails
ng generate component components/recipeexport interface Ingredient {
name: string;
id?: number;
}ng generate module custom-materialpublic 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
<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
<div *ngIf="expressionBooleenne">
Hello
</div>Paramètre d'entrée du composant :
@Input()
recipe: Recipe;<mon-composant-enfant [recipe]="maRecette"></mon-composant-enfant>Un peu d'aide côté design : Angular Material
Installation :
ng add @angular/materialPour les rapides: Mettre Angular Material dans un module; Créer un composant Header; Styliser tes composants; Ajouter un compteur de verre global
ng generate service recipenew Promise((resolve, reject) => { /* execution en asynchrone */ })
.then(result => { /* Traiter le résultat */ })
.catch(error => { /* Traiter l'erreur */ });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ù ?
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]
Créer un module dédié : AppRoutingModule
L'importer dans le module principal
@NgModule({
exports: [
RouterModule
],
imports: [
RouterModule.forRoot(routes)
]
})
export class AppRoutingModule { }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()]
};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
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
http://52.47.189.244:8080/swagger-ui/index.html
TIPS :
https://angular.io/guide/reactive-forms
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...
http://52.47.189.244:8080/swagger-ui.html#/
https://angular.io/guide/reactive-forms
@Pipe({
name: 'orderBy'
})
export class OrderByPipe implements PipeTransform {
transform(param : InputType): OutputType {
doSomething();
return newValue;
}
}https://angular.io/guide/animations#setup