Création du modèle de données
Un modèle de données sur Angular désigne généralement une structure TypeScript (souvent une classe ou une interface) qui définit à quoi ressemblent les objets manipulés dans l’application.
- C’est un “plan” ou un “schéma” pour les données que tu échanges entre le front (Angular) et le back (API/serveur).
- Le modèle fixe les propriétés attendues : leur nom, leur type (string, number, boolean, etc.), et parfois de petites méthodes pour transformer ou valider ces données.
- Il sert surtout à :
- Tiper et sécuriser le code (maîtriser les informations qui circulent dans l’app)
- Lier les formulaires, affichages et requêtes API de façon claire, robuste et prévisible.
frontend/shopping-list/src/app/ingredients/ingredient.model.ts
export interface Ingredient {
id: string;
title: string;
picto: string;
}
Création du composant
Afin d’améliorer la lisibilité et la maintenabilité du code, j’ai choisi de diviser la gestion des ingrédients en plusieurs composants distincts :
- ingredients
- ingredient-list
- ingredient-item
- new-ingredient
Pour générer un composant depuis la ligne de commande, on utilise la syntaxe suivante :
ng generate component <name>
Ingredients
Ce composant ne sera utile que pour afficher un formulaire d’ajout d’ingrédient et la liste des ingrédients
frontend/shopping-list/src/app/ingredients/ingredients-list/ingredients-list.component.ts
import { Component } from '@angular/core';
import { IngredientsListComponent } from "./ingredients-list/ingredients-list.component";
import { NewIngredientComponent } from "./new-ingredient/new-ingredient.component";
@Component({
selector: 'app-ingredients',
imports: [IngredientsListComponent, NewIngredientComponent],
templateUrl: './ingredients.component.html',
styleUrl: './ingredients.component.css'
})
export class IngredientsComponent {}
frontend/shopping-list/src/app/ingredients/ingredients.component.html
<app-new-ingredient />
<app-ingredients-list />
Ingredient list
Ce composant affichera une liste de tous les ingrédients enregistrés dans la base de données.
frontend/shopping-list/src/app/ingredients/ingredients-list/ingredients-list.component.ts
import { Component, computed, inject } from '@angular/core';
import { IngredientsService } from '../../shared/services/ingredients.service';
import { IngredientItemComponent } from './ingredient-item/ingredient-item.component';
@Component({
selector: 'app-ingredients-list',
imports: [IngredientItemComponent],
templateUrl: './ingredients-list.component.html',
styleUrl: './ingredients-list.component.css'
})
export class IngredientsListComponent {
private ingredientsService = inject(IngredientsService);
ingredients = computed(() => this.ingredientsService.allIngredients());
}
Pour récupérer les ingrédients, on va créer une valeur réactive dérivée de signaux. La variable ingredients
sera automatiquement recalculé chaque fois qu’un signal utilisé dans ingredientsService.allIngredients()
change.
Côté template, on fait une boucle pour afficher les données ainsi récupérées
frontend/shopping-list/src/app/ingredients/ingredients-list/ingredients-list.component.html
<header>
<h2>Liste des ingredients</h2>
</header>
<ul>
@for (ingredient of (ingredients()); track ingredient.id) {
<li>
<app-ingredient-item [ingredient]="ingredient" />
</li>
}
</ul>
Ingredient item
Ce composant affichera les données d’un seul ingrédient. On pourra soit l’appeler à un endroit unique, soit l’appeler depuis la liste précédemment créée
frontend/shopping-list/src/app/ingredients/ingredients-list/ingredient-item/ingredient-item.component.ts
import { Component, input } from '@angular/core';
import { Ingredient } from '../../ingredient.model';
@Component({
selector: 'app-ingredient-item',
imports: [],
templateUrl: './ingredient-item.component.html',
styleUrl: './ingredient-item.component.css'
})
export class IngredientItemComponent {
ingredient = input.required<Ingredient>();
}
Ici, on utilise l’input pour récupérer l’ingrédient à affiché, qui est passé en paramètre dans la déclaration du composant (voir ci dessus, dans la boucle qui affiche les données du composant list)
Côté template, on fait un affichage classique des données que l’on veut afficher
frontend/shopping-list/src/app/ingredients/ingredients-list/ingredient-item/ingredient-item.component.html
<p>
<span class="icon {{ ingredient().picto }}"></span>
{{ ingredient().title }}
</p>
New ingredient
Ce nouveau composant sera un formulaire pour la création de nouveaux ingrédients. J’ai opté pour l’ajout de la librairie Material Angular. Cette librairie permettra de m’affranchir de la partie design du formulaire.
frontend/shopping-list/package.json
"dependencies": {
"@angular/material": "^19.1.0",
}
Côté template, on utilise les balises mises à disposition par Material
frontend/shopping-list/src/app/ingredients/new-ingredient/new-ingredient.component.html
<form (ngSubmit)="onAddIngredient(title.value, picto.value)" #form class="example-form">
<mat-form-field class="example-full-width">
<mat-label for="title">Title</mat-label>
<input matInput placeholder="Ex. Pizza" value="Sushi" type="text" id="title" name="title" #title>
</mat-form-field>
<mat-form-field class="example-full-width">
<mat-label for="picto">Picto</mat-label>
<input matInput placeholder="Ex. Pizza" value="Sushi" type="text" id="picto" name="picto" #picto>
</mat-form-field>
<div class="example-button-row">
<button mat-flat-button>Save</button>
</div>
</form>
frontend/shopping-list/src/app/ingredients/new-ingredient/new-ingredient.component.ts
import { Component, ElementRef, inject, viewChild } from '@angular/core';
import { IngredientsService } from '../../shared/services/ingredients.service';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import {FormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
@Component({
selector: 'app-new-ingredient',
imports: [FormsModule, MatFormFieldModule, MatInputModule, MatButtonModule],
templateUrl: './new-ingredient.component.html',
styleUrl: './new-ingredient.component.css'
})
export class NewIngredientComponent {
private formEl = viewChild<ElementRef<HTMLFormElement>>('form');
private ingredientService = inject(IngredientsService);
onAddIngredient(title: string, picto: string) {
this.ingredientService.addIngredient({title, picto});
this.formEl()?.nativeElement.reset();
}
}
- Utilise
viewChild
pour obtenir une référence directe au formulaire HTML dans le template (repéré par le nom ‘form’). ElementRef<HTMLFormElement>
permet de manipuler le formulaire DOM depuis le TypeScript (ex : le réinitialiser après soumission).- Ajout de l’ingrédient : appelle
addIngredient
du service pour ajouter un nouvel ingrédient avec les valeurs reçues. - Reset du formulaire : réinitialise le formulaire via la référence DOM obtenue avec
formEl
, pratique après un ajout réussi.
Le service
Enfin, on s’aperçoit dans le composant du formulaire qu’on fait une injection vers un service. Ce service centralise les données et les opérations relatives aux ingrédients.
frontend/shopping-list/src/app/shared/services/ingredients.service.ts
import { Injectable, signal } from '@angular/core';
import { Ingredient } from '../../ingredients/ingredient.model';
@Injectable({
providedIn: 'root'
})
export class IngredientsService {
private ingredients = signal<Ingredient[]>([]);
allIngredients = this.ingredients.asReadonly();
addIngredient(ingredientData: {title: string, picto: string}) {
const newIngredient: Ingredient = {
...ingredientData,
id: Math.random().toString()
};
this.ingredients.update((oldIngredients) => [...oldIngredients, newIngredient]);
}
}
En premier lieu, on doit permettre d’injecter le service, pour cela on passe le paramètre provindeIn à root. Cela signifiera qu’une seule instance du service sera partagé dans toute l’appli.
Ensuite, on enregistre les ingrédients dans un signal, pour automatiser la mise a jour des composants qui l’écoute à chacun de ses changements.
Pour protéger les données, le signal est exposé en lecture seule via asReadonly()
. Cela permet aux composants d’accéder à la liste des ingrédients sans pouvoir la modifier directement.
Le service comporte des méthodes qui vont venir mettre à jour les données. Ici on ajoutera un ingrédient:
addIngredient
reçoit un objet avec les propriétéstitle
etpicto
.- Il crée un nouvel ingrédient avec un identifiant unique généré aléatoirement (
id
). - Puis met à jour le signal
ingredients
en ajoutant ce nouvel ingrédient à la liste existante. - Cette mise à jour déclenche automatiquement la réactivité dans tous les composants abonnés.
“Ces notes techniques sont à but informatif, non formatif, et reflètent mon expérience à la date de rédaction.”