Création du composant d’ajout des ingrédients
frontend/shoppinglist/src/app/ingredients/ingredient-new/ingredient-new.component.ts
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { IngredientCategory } from '../ingredient.utils';
@Component({
selector: 'app-ingredient-new',
imports: [ReactiveFormsModule],
templateUrl: './ingredient-new.component.html',
styleUrl: './ingredient-new.component.css'
})
export class IngredientNewComponent implements OnInit, OnDestroy {
private route = inject(ActivatedRoute);
private routeSubscription: Subscription | null = null;
private fb = inject(FormBuilder);
formGroup = this.fb.group({
name: ['',[Validators.required]],
picto: ['',[Validators.required]],
category: [IngredientCategory.VEGETABLE, [Validators.required]]
})
ingredientCategory = Object.values(IngredientCategory);
ingerdientId = -1;
ngOnInit(): void {
this.routeSubscription = this.route.params.subscribe( params => {
if(params['id']) {this.ingerdientId = parseInt(params['id'])}
})
}
ngOnDestroy(): void {
this.routeSubscription?.unsubscribe();
}
submit(event: Event) {
event.preventDefault();
console.log(this.formGroup.value);
}
isFieldValid(fieldName: string) {
const formControl = this.formGroup.get(fieldName);
return formControl?.invalid && (formControl.dirty || formControl.touched);
}
}
Ce composant fournit un formulaire complet et validé pour créer ou modifier un ingrédient, récupère l’ID d’ingrédient via les paramètres d’URL (permettant la modification), et applique les bonnes pratiques Angular pour la gestion des subscriptions et la validation réactive.
1. Importations et décorateur
- Import des modules Angular nécessaires (
FormBuilder,FormGroup,Validatorspour les formulaires réactifs,ActivatedRoutepour l’accès aux paramètres d’URL). - Déclaration du composant avec son sélecteur, template, style et import du module
ReactiveFormsModule.
2. Injection des dépendances
private route = inject(ActivatedRoute);
private routeSubscription: Subscription | null = null;
private fb = inject(FormBuilder);
- Injection des services
ActivatedRoute(pour lire les paramètres de la route) etFormBuilder(pour créer le formulaire réactif). - Stockage d’une subscription pour la nettoyer ensuite.
3. Définition du formulaire réactif
formGroup = this.fb.group({
name: ['', [Validators.required]],
picto: ['', [Validators.required]],
category: [IngredientCategory.VEGETABLE, [Validators.required]]
})
- Création d’un groupe de contrôles de formulaire avec des validateurs (champs obligatoires).
- La catégorie d’ingrédient est par défaut
VEGETABLE.
4. Variables complémentaires
ingredientCategory = Object.values(IngredientCategory);
ingerdientId = -1;
- Stockage de toutes les valeurs possibles de la catégorie (pour un sélecteur).
ingredientIdpour détecter si on modifie un ingrédient existant (initialisé à -1).
5. Cycle de vie Angular
- ngOnInit:
- S’abonne aux paramètres d’URL via
route.params.subscribe. - Si un ID est présent, le convertit en entier et le stocke (pour potentiellement charger/modifier un ingrédient).
- S’abonne aux paramètres d’URL via
- ngOnDestroy:
- Nettoie l’abonnement pour éviter les fuites mémoire avec
unsubscribe().
- Nettoie l’abonnement pour éviter les fuites mémoire avec
6. Méthodes du composant
- submit(event: Event) :
- Empêche la soumission standard du formulaire.
- Affiche dans la console les données du formulaire (à compléter pour intégrer l’ajout ou la modification).
- isFieldValid(fieldName: string) :
- Vérifie si un champ est invalide et a été touché ou modifié, pour afficher des messages d’erreur ou styles.
Création d’un fichier de configuration
Afin d’avoir des données cohérentes et structurées, on crée un fichier de configuration dans lequel on va stocker plusieurs informations liées aux ingrédients
frontend/shoppinglist/src/app/ingredients/ingredient.utils.ts
Ce code TypeScript définit des catégories d’ingrédients avec des propriétés associées, utilisées probablement pour afficher des informations visuelles dans l’application Angular.
1. Enumération IngredientCategory
frontend/shoppinglist/src/app/ingredients/ingredient.utils.tsexport enum IngredientCategory {
VEGETABLE = 'vegetable',
STARCH = 'starch',
MEAT = 'meat'
}
- Enumération pour représenter les catégories possibles des ingrédients.
- Chaque catégorie a une valeur textuelle claire :
'vegetable','starch','meat'. - Utilisation typique : typer les ingrédients et garantir des valeurs constantes et cohérentes.
2. Interface IIngredientCategoryProperties
frontend/shoppinglist/src/app/ingredients/ingredient.utils.tsexport interface IIngredientCategoryProperties {
imageUrl: string;
color: string;
}
- Interface qui décrit un objet contenant deux propriétés liées à l’affichage :
imageUrl: chemin vers une image représentant la catégorie.color: couleur associée à la catégorie (en format CSS RGB ici).
3. Objet IngredientTypeProperties
frontend/shoppinglist/src/app/ingredients/ingredient.utils.tsexport const IngredientTypeProperties: {[key: string]: IIngredientCategoryProperties} = {
[IngredientCategory.VEGETABLE]: {
imageUrl: 'assets/img/vegetable.png',
color: 'rgb(135, 255, 124)'
},
[IngredientCategory.STARCH]: {
imageUrl: 'assets/img/starch.png',
color: 'rgb(255, 255, 104)'
},
[IngredientCategory.MEAT]: {
imageUrl: 'assets/img/meat.png',
color: 'rgb(255, 104, 104)'
},
}
- Objet associant chaque catégorie (
VEGETABLE,STARCH,MEAT) à ses propriétés visuelles. - Permet de récupérer facilement l’image ou la couleur d’un ingrédient selon sa catégorie.
- Utile pour une UI dynamique, par exemple afficher une icône colorée à côté des ingrédients.
Création du template associé pour afficher le formulaire
frontend/shoppinglist/src/app/ingredients/ingredient-new/ingredient-new.component.html
<form (submit)="submit($event)" [formGroup]="formGroup">
<div class="form-field">
<label for="name">Name</label>
<input id="name" name="name" type="text" formControlName="name" />
@if (isFieldValid('name')) {
@if (formGroup.get('name')?.hasError('required')) {
<div class="error">This field is required.</div>
}
}
</div>
<div class="form-field">
<label for="picto">Picto</label>
<input id="picto" name="picto" type="text" formControlName="picto" />
@if (isFieldValid('picto')) {
@if (formGroup.get('picto')?.hasError('required')) {
<div class="error">This field is required.</div>
}
}
</div>
<div class="form-field">
<label for="category">Category</label>
<select id="category" name="category" formControlName="category">
@for (cat of ingredientCategory; track cat) {
<option [value]="cat">{{cat}}</option>
}
</select>
</div>
<button type="submit" [disabled]="formGroup.invalid">Save</button>
</form>
“Ces notes techniques sont à but informatif, non formatif, et reflètent mon expérience à la date de rédaction.”