Création d’une application Web Angular + Symfony + MySQL  
Objectif :  
Créer un backend en Symfony connecté à MySQL ;  
Développer un frontend en Angular 19 ;  
Connecter les deux via une API REST ;  
Échanger des données (affichage, ajout, etc.) entre le backend et le frontend.  
Partie 1 Le concept général  
Partie 2 Création du backend Symfony + MySQL  
1. Configuration du projet  
Créez un projet Symfony :  
Installez la base de données MySQL :  
Dans le fichier .env, configurez la connexion MySQL :  
Je commence par créer la base de données  
Et puis dans fichier .env  
2. Création de l’entité User  
Une entité représente une table dans la base de données.  
Exécutez :  
Ajoutez les champs :  
name (string)  
email (string)  
registeredAt (datetime)  
Cela crée le fichier :  
src/Entity/User.php  
<?php  
namespace App\Entity;  
use App\Repository\UserRepository;  
use Doctrine\ORM\Mapping as ORM;  
#[ORM\Entity(repositoryClass: UserRepository::class)]  
class User  
{
#[ORM\Id]  
#[ORM\GeneratedValue]  
#[ORM\Column]  
private ?int $id = null;  
#[ORM\Column(length: 255)]  
private ?string $name = null;  
#[ORM\Column(length: 255)]  
private ?string $email = null;  
#[ORM\Column]  
private ?\DateTimeImmutable $registeredAt = null;  
public function getId(): ?int  
{
return $this->id;  
}
public function getName(): ?string  
{
return $this->name;  
}
public function setName(string $name): static  
{
$this->name = $name;  
return $this;  
}
public function getEmail(): ?string  
{
return $this->email;  
}
public function setEmail(string $email): static  
{
}
$this->email = $email;  
return $this;  
public function getRegisteredAt(): ?\DateTimeImmutable  
{
return $this->registeredAt;  
}
public function setRegisteredAt(\DateTimeImmutable $registeredAt): static  
{
$this->registeredAt = $registeredAt;  
return $this;  
}
}
3. Migration et création de la table  
Pour créer la table user dans MySQL :  
Vous avez maintenant une base de données prête.  
!!!Différence entre DateTime et DateTimeImmutable  
4. Création du contrôleur API  
Créez un contrôleur pour gérer les utilisateurs :  
Code du contrôleur src/Controller/Api/UserController.php :  
<?php  
namespace App\Controller\Api;  
use App\Entity\User;  
use App\Repository\UserRepository;  
use Doctrine\ORM\EntityManagerInterface;  
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;  
use Symfony\Component\HttpFoundation\Request;  
use Symfony\Component\HttpFoundation\JsonResponse;  
use Symfony\Component\Routing\Annotation\Route;  
#[Route('/api/users')]  
class UserController extends AbstractController  
{
#[Route('', methods: ['GET'])]  
public function index(UserRepository $repo): JsonResponse  
{
$users = $repo->findAll();  
$data = array_map(fn(User $u) => [  
'id' => $u->getId(),  
'name' => $u->getName(),  
'email' => $u->getEmail(),  
'registeredAt' => $u->getRegisteredAt()->format('Y-m-d'),  
], $users);  
return $this->json($data);  
}
#[Route('', methods: ['POST'])]  
public function create(Request $request, EntityManagerInterface $em): JsonResponse  
{
$body = json_decode($request->getContent(), true);  
if (!isset($body['name'], $body['email'])) {  
return $this->json(['error' => 'Données invalides'], 400);  
}
$user = new User();  
$user->setName($body['name']);  
$user->setEmail($body['email']);  
$user->setRegisteredAt(new \DateTimeImmutable());  
$em->persist($user);  
$em->flush();  
return $this->json([  
'id' => $user->getId(),  
'name' => $user->getName(),  
'email' => $user->getEmail(),  
'registeredAt' => $user->getRegisteredAt()->format('Y-m-d'),  
], 201);  
}
}
Le backend expose maintenant deux routes :  
GET /api/users → liste des utilisateurs  
POST /api/users → ajout d’un utilisateur  
Lance le serveur Symfony  
Partie 3 Création du frontend Angular 19  
1. Création du projet Angular  
2. Service Angular UserService  
Code :  
import { Injectable } from '@angular/core';  
import { HttpClient } from '@angular/common/http';  
import { Observable } from 'rxjs';  
export interface User {  
id?: number;  
name: string;  
email: string;  
registeredAt: string;  
}
@Injectable({ providedIn: 'root' })  
export class UserService {  
private api = 'http://127.0.0.1:8000/api/users';  
constructor(private http: HttpClient) {}  
getUsers(): Observable<User[]> {  
return this.http.get<User[]>(this.api);  
}
addUser(name: string, email: string): Observable<User> {  
return this.http.post<User>(this.api, { name, email });  
}
}
3. Liste des utilisateurs  
Ts :  
import { Component, OnInit } from '@angular/core';  
import { CommonModule } from '@angular/common';  
import { User, UserService } from '../../services/user.service';  
@Component({  
selector: 'app-user-list',  
standalone: true,  
imports: [CommonModule],  
templateUrl: './user-list.component.html'  
})  
export class UserListComponent implements OnInit {  
users: User[] = [];  
constructor(private userService: UserService) {}  
ngOnInit(): void {  
this.userService.getUsers().subscribe(res => this.users = res);  
}
}
Html :  
<h2>Liste des utilisateurs</h2>  
<ul>  
<li *ngFor="let u of users">{{ u.name }} - {{ u.email }} - {{ u.registeredAt }}</li>  
</ul>  
4. Formulaire dajout  
Ts :  
import { Component } from '@angular/core';  
import { ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';  
import { UserService } from '../../services/user.service';  
import { CommonModule } from '@angular/common';  
@Component({  
selector: 'app-user-form',  
standalone: true,  
imports: [ReactiveFormsModule,CommonModule ],  
templateUrl: './user-form.component.html'  
})  
export class UserFormComponent {  
form: FormGroup;  
constructor(private fb: FormBuilder, private userService: UserService) {  
this.form = this.fb.group({ name: [''], email: [''] });  
}
onSubmit() {  
if (this.form.valid) {  
const { name, email } = this.form.value;  
this.userService.addUser(name, email).subscribe(() => this.form.reset());  
}
}
}
Html :  
<h2>Ajouter un utilisateur</h2>  
<form [formGroup]="form" (ngSubmit)="onSubmit()">  
<input formControlName="name" placeholder="Nom" />  
<input formControlName="email" placeholder="Email" />  
<button type="submit">Ajouter</button>  
</form>  
Et dans app.confi.ts :  
Dans appComponent :  
Si ça n’affiche pas il faut d’abord autorise Angular à accéder à Symfony (CORS)  
Par défaut, Symfony bloque les requêtes venant d’un autre port (par ex. :  
Angular = 4200, Symfony = 8000).  
Installe le CORS Bundle :  
Redémarrer le sereveur symfony :  
Relancer le serveur angular :  
Mise à jour d’un utilisateur (Angular + Symfony)  
Mettre à jour les informations d’un utilisateur existant dans une base de données  
MySQL via une API Symfony et une interface Angular.  
1. Principe général  
L’application est divisée en deux parties  
2. Côté backend (Symfony)  
Route de mise à jour  
On ajoute une nouvelle route dans le contrôleur UserController.php :  
<?php  
namespace App\Controller\Api;  
use App\Entity\User;  
use App\Repository\UserRepository;  
use Doctrine\ORM\EntityManagerInterface;  
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;  
use Symfony\Component\HttpFoundation\Request;  
use Symfony\Component\HttpFoundation\JsonResponse;  
use Symfony\Component\Routing\Annotation\Route;  
#[Route('/api/users')]  
class UserController extends AbstractController  
{
#[Route('', methods: ['GET'])]  
public function index(UserRepository $repo): JsonResponse  
{
$users = $repo->findAll();  
$data = array_map(fn(User $u) => [  
'id' => $u->getId(),  
'name' => $u->getName(),  
'email' => $u->getEmail(),  
'registeredAt' => $u->getRegisteredAt()->format('Y-m-d'),  
], $users);  
return $this->json($data);  
}
#[Route('', methods: ['POST'])]  
public function create(Request $request, EntityManagerInterface $em):  
JsonResponse  
{
$body = json_decode($request->getContent(), true);  
if (!isset($body['name'], $body['email'])) {  
return $this->json(['error' => 'Données invalides'], 400);  
}
$user = new User();  
$user->setName($body['name']);  
$user->setEmail($body['email']);  
$user->setRegisteredAt(new \DateTimeImmutable());  
$em->persist($user);  
$em->flush();  
return $this->json([  
'id' => $user->getId(),  
'name' => $user->getName(),  
'email' => $user->getEmail(),  
'registeredAt' => $user->getRegisteredAt()->format('Y-m-d'),  
], 201);  
}
/*------------------------------Mise à jour ------------------*/  
#[Route('/{id}', methods: ['PUT'])]  
public function update(  
int $id,  
Request $request,  
EntityManagerInterface $em,  
UserRepository $repo  
): JsonResponse {  
$user = $repo->find($id);  
if (!$user) {  
return $this->json(['error' => 'Utilisateur non trouvé'], 404);  
}
$data = json_decode($request->getContent(), true);  
if (!isset($data['name'], $data['email'])) {  
return $this->json(['error' => 'Données invalides'], 400);  
}
$user->setName($data['name']);  
$user->setEmail($data['email']);  
$em->flush();  
return $this->json([  
'id' => $user->getId(),  
'name' => $user->getName(),  
'email' => $user->getEmail(),  
'registeredAt' => $user->getRegisteredAt()->format('Y-m-d'),  
]);  
}
}
-
-
-
-
-
Méthode HTTP : PUT (pour une mise à jour complète).  
Paramètre dynamique {id} : permet de cibler l’utilisateur à modifier.  
json_decode() : lit les données envoyées par Angular.  
flush() : applique les changements dans la base.  
Retour JSON : confirme la mise à jour à Angular.  
3. Côté frontend (Angular)  
a) Méthode du service  
Dans user.service.ts, on ajoute :  
import { Injectable } from '@angular/core';  
import { HttpClient } from '@angular/common/http';  
import { Observable } from 'rxjs';  
export interface User {  
id?: number;  
name: string;  
email: string;  
registeredAt: string;  
}
@Injectable({ providedIn: 'root' })  
export class UserService {  
private api = 'http://127.0.0.1:8000/api/users';  
constructor(private http: HttpClient) {}  
getUsers(): Observable<User[]> {  
return this.http.get<User[]>(this.api);  
}
addUser(name: string, email: string): Observable<User> {  
return this.http.post<User>(this.api, { name, email });  
}
/*------------------------------mise à jr------------------*/  
updateUser(id: number, name: string, email: string): Observable<User> {  
return this.http.put<User>(  
`${this.api}/${id}`,  
{ name, email },  
{ headers: { 'Content-Type': 'application/json' } }  
);  
}
}
Cette méthode envoie une requête HTTP PUT à Symfony, avec :  
-
-
l’adresse /api/users/{id},  
les nouvelles valeurs à enregistrer (name et email).  
b) Intégration dans le composant  
Dans le composant Angular (user-form.component.ts ou user-list.component.ts), on ajoute la  
logique suivante :  
// src/app/components/user-form/user-form.component.ts  
import { Component, OnInit } from '@angular/core';  
import { ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';  
import { UserService, User } from '../../services/user.service';  
import { CommonModule } from '@angular/common';  
@Component({  
selector: 'app-user-form',  
standalone: true,  
imports: [ReactiveFormsModule, CommonModule],  
templateUrl: './user-form.component.html'  
})  
export class UserFormComponent implements OnInit {  
form!: FormGroup;  
users: User[] = [];  
editingUser: User | null = null;  
constructor(private fb: FormBuilder, private userService: UserService) {}  
ngOnInit(): void {  
this.form = this.fb.group({  
name: [''],  
email: ['']  
});  
this.loadUsers();  
}
loadUsers() {  
this.userService.getUsers().subscribe({  
next: (data) => (this.users = data),  
error: (err) => {  
console.error('Erreur getUsers', err);  
alert('Impossible de charger la liste des utilisateurs (voir la console).');  
}
});  
}
startEdit(user: User) {  
this.editingUser = user;  
this.form.patchValue({ name: user.name, email: user.email });  
}
cancelEdit() {  
this.editingUser = null;  
this.form.reset();  
}
onSubmit() {  
if (!this.form.valid) {  
return;  
}
const { name, email } = this.form.value;  
if (this.editingUser) {  
// mode édition  
this.userService.updateUser(this.editingUser.id!, name, email).subscribe({  
next: () => {  
this.loadUsers();  
this.cancelEdit();  
},  
error: (err) => {  
console.error('Erreur updateUser', err);  
alert('Erreur lors de la mise à jour (voir la console).');  
}
});  
} else {  
// mode ajout  
this.userService.addUser(name, email).subscribe({  
next: () => {  
this.loadUsers();  
this.form.reset();  
},  
error: (err) => {  
console.error('Erreur addUser', err);  
alert('Erreur lors de l\'ajout (voir la console).');  
}
});  
}
}
}
Html :  
<!-- src/app/components/user-form/user-form.component.html -->  
<h2>{{ editingUser ? 'Modifier l\'utilisateur' : 'Ajouter un utilisateur' }}</h2>  
<form [formGroup]="form" (ngSubmit)="onSubmit()">  
<input formControlName="name" placeholder="Nom" />  
<input formControlName="email" placeholder="Email" />  
<button type="submit">{{ editingUser ? 'Mettre à jour' : 'Ajouter' }}</button>  
<button type="button" *ngIf="editingUser" (click)="cancelEdit()">Annuler</button>  
</form>  
<hr />  
<h3>Liste des utilisateurs</h3>  
<ul>  
<li *ngFor="let user of users">  
{{ user.name }} ({{ user.email }})  
<button (click)="startEdit(user)">Modifier</button>  
</li>  
</ul>  
Suppression d’un utilisateur depuis le formulaire (UserFormComponent)  
Permettre à l’utilisateur de supprimer la fiche affichée dans le formulaire  
(UserFormComponent)  
1. Côté backend (Symfony)  
On ajouté la route suivante :  
<?php  
namespace App\Controller\Api;  
use App\Entity\User;  
use App\Repository\UserRepository;  
use Doctrine\ORM\EntityManagerInterface;  
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;  
use Symfony\Component\HttpFoundation\Request;  
use Symfony\Component\HttpFoundation\JsonResponse;  
use Symfony\Component\Routing\Annotation\Route;  
#[Route('/api/users')]  
class UserController extends AbstractController  
{
#[Route('', methods: ['GET'])]  
public function index(UserRepository $repo): JsonResponse  
{
$users = $repo->findAll();  
$data = array_map(fn(User $u) => [  
'id' => $u->getId(),  
'name' => $u->getName(),  
'email' => $u->getEmail(),  
'registeredAt' => $u->getRegisteredAt()->format('Y-m-d'),  
], $users);  
return $this->json($data);  
}
#[Route('', methods: ['POST'])]  
public function create(Request $request, EntityManagerInterface $em):  
JsonResponse  
{
$body = json_decode($request->getContent(), true);  
if (!isset($body['name'], $body['email'])) {  
return $this->json(['error' => 'Données invalides'], 400);  
}
$user = new User();  
$user->setName($body['name']);  
$user->setEmail($body['email']);  
$user->setRegisteredAt(new \DateTimeImmutable());  
$em->persist($user);  
$em->flush();  
return $this->json([  
'id' => $user->getId(),  
'name' => $user->getName(),  
'email' => $user->getEmail(),  
'registeredAt' => $user->getRegisteredAt()->format('Y-m-d'),  
], 201);  
}
/*------------------------------Mise à jour ------------------*/  
#[Route('/{id}', methods: ['PUT'])]  
public function update(  
int $id,  
Request $request,  
EntityManagerInterface $em,  
UserRepository $repo  
): JsonResponse {  
$user = $repo->find($id);  
if (!$user) {  
return $this->json(['error' => 'Utilisateur non trouvé'], 404);  
}
$data = json_decode($request->getContent(), true);  
if (!isset($data['name'], $data['email'])) {  
return $this->json(['error' => 'Données invalides'], 400);  
}
$user->setName($data['name']);  
$user->setEmail($data['email']);  
$em->flush();  
return $this->json([  
'id' => $user->getId(),  
'name' => $user->getName(),  
'email' => $user->getEmail(),  
'registeredAt' => $user->getRegisteredAt()->format('Y-m-d'),  
]);  
}
/*---------------------Delete-------------------------------*/  
#[Route('/{id}', methods: ['DELETE'])]  
public function delete(  
int $id,  
EntityManagerInterface $em,  
UserRepository $repo  
): JsonResponse {  
// Recherche de l'utilisateur  
$user = $repo->find($id);  
if (!$user) {  
return $this->json(['error' => 'Utilisateur non trouvé'], 404);  
}
// Suppression  
$em->remove($user);  
$em->flush();  
// Réponse de confirmation  
return $this->json(['message' => 'Utilisateur supprimé avec succès']);  
}
}
-> Cette route est déjà prête à recevoir la requête DELETE depuis Angular.  
2. Côté frontend (Angular)  
a) Méthode de suppression dans le service  
Dans user.service.ts :  
import { Injectable } from '@angular/core';  
import { HttpClient } from '@angular/common/http';  
import { Observable } from 'rxjs';  
export interface User {  
id?: number;  
name: string;  
email: string;  
registeredAt: string;  
}
@Injectable({ providedIn: 'root' })  
export class UserService {  
private api = 'http://127.0.0.1:8000/api/users';  
constructor(private http: HttpClient) {}  
getUsers(): Observable<User[]> {  
return this.http.get<User[]>(this.api);  
}
addUser(name: string, email: string): Observable<User> {  
return this.http.post<User>(this.api, { name, email });  
}
/*------------------------------mise à jr------------------*/  
updateUser(id: number, name: string, email: string): Observable<User> {  
return this.http.put<User>(  
`${this.api}/${id}`,  
{ name, email },  
{ headers: { 'Content-Type': 'application/json' } }  
);  
}
/*---------------------delete-------------*/  
deleteUser(id: number) {  
return this.http.delete(`${this.api}/${id}`);  
}
}
b) Bouton "Supprimer" dans le formulaire  
Dans ton fichier user-form.component.html, ajoute un bouton “Supprimer” :  
<h2>{{ editingUser ? "Modifier l'utilisateur" : "Ajouter un utilisateur" }}</h2>  
<form [formGroup]="form" (ngSubmit)="onSubmit()">  
<input formControlName="name" placeholder="Nom" />  
<input formControlName="email" placeholder="Email" />  
<button type="submit">{{ editingUser ? 'Mettre à jour' : 'Ajouter' }}</button>  
<button type="button" *ngIf="editingUser" (click)="cancelEdit()">Annuler</button>  
</form>  
<hr />  
<h3>Liste des utilisateurs</h3>  
<ul>  
<li *ngFor="let user of users">  
{{ user.name }} ({{ user.email }})  
<button (click)="startEdit(user)">Modifier</button>  
<button (click)="supprimerUser(user.id!)">Supprimer</button>  
</li>  
</ul>  
c) Méthode dans user-form.component.ts  
Ajoute cette méthode :  
import { Component, OnInit } from '@angular/core';  
import { ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';  
import { UserService, User } from '../../services/user.service';  
import { CommonModule } from '@angular/common';  
@Component({  
selector: 'app-user-form',  
standalone: true,  
imports: [ReactiveFormsModule, CommonModule],  
templateUrl: './user-form.component.html'  
})  
export class UserFormComponent implements OnInit {  
form!: FormGroup;  
users: User[] = [];  
editingUser: User | null = null;  
constructor(private fb: FormBuilder, private userService: UserService) {}  
ngOnInit(): void {  
this.form = this.fb.group({  
name: [''],  
email: ['']  
});  
this.loadUsers();  
}
// Récupère tous les utilisateurs  
loadUsers() {  
this.userService.getUsers().subscribe({  
next: (data) => (this.users = data),  
error: (err) => {  
console.error('Erreur getUsers', err);  
alert('Impossible de charger la liste des utilisateurs (voir la console).');  
}
});  
}
// Prépare le formulaire pour la modification  
startEdit(user: User) {  
this.editingUser = user;  
this.form.patchValue({ name: user.name, email: user.email });  
}
// Annule le mode édition  
cancelEdit() {  
this.editingUser = null;  
this.form.reset();  
}
// Soumet le formulaire (ajout ou mise à jour)  
onSubmit() {  
if (!this.form.valid) {  
return;  
}
const { name, email } = this.form.value;  
if (this.editingUser) {  
// Mode édition  
this.userService.updateUser(this.editingUser.id!, name, email).subscribe({  
next: () => {  
this.loadUsers();  
this.cancelEdit();  
},  
error: (err) => {  
console.error('Erreur updateUser', err);  
alert('Erreur lors de la mise à jour (voir la console).');  
}
});  
} else {  
// Mode ajout  
this.userService.addUser(name, email).subscribe({  
next: () => {  
this.loadUsers();  
this.form.reset();  
},  
error: (err) => {  
console.error('Erreur addUser', err);  
alert("Erreur lors de l'ajout (voir la console).");  
}
});  
}
}
// Suppression d’un utilisateur  
supprimerUser(id: number) {  
if (confirm('Voulez-vous vraiment supprimer cet utilisateur ?')) {  
this.userService.deleteUser(id).subscribe({  
next: () => {  
alert('Utilisateur supprimé avec succès ');  
this.loadUsers();  
},  
error: (err) => {  
console.error('Erreur deleteUser', err);  
alert('Erreur lors de la suppression (voir la console).');  
}
});  
}
}
}
Créer un fichier CSS  
Dans le dossier :  
src/app/components/user-form/user-form.component.css  
/* ------------ FORMULAIRE ------- */  
.form-title {  
text-align: center;  
margin-bottom: 15px;  
color: #333;  
}
.user-form {  
max-width: 700px;  
margin: 0 auto 30px;  
background: #fff;  
padding: 20px;  
border-radius: 10px;  
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);  
}
.form-row {  
display: flex;  
align-items: center;  
gap: 10px;  
}
.user-form input {  
flex: 1;  
padding: 8px 12px;  
border: 1px solid #ccc;  
border-radius: 6px;  
font-size: 14px;  
}
.btn-submit {  
background-color: #007bff;  
color: white;  
border: none;  
border-radius: 6px;  
padding: 8px 16px;  
cursor: pointer;  
transition: background-color 0.2s;  
}
.btn-submit:hover {  
background-color: #0056b3;  
}
.btn-cancel {  
margin-top: 10px;  
background-color: #6c757d;  
color: white;  
border: none;  
border-radius: 6px;  
padding: 8px 16px;  
cursor: pointer;  
}
.btn-cancel:hover {  
background-color: #5a6268;  
}
/* ===== TITRE DE LISTE ===== */  
.list-title {  
text-align: center;  
margin-bottom: 15px;  
color: #333;  
}
/* ===== LISTE DES UTILISATEURS ===== */  
.user-list {  
max-width: 700px;  
margin: 0 auto;  
display: flex;  
flex-direction: column;  
gap: 10px;  
}
.user-card {  
background-color: #fff;  
border: 1px solid #ddd;  
border-radius: 10px;  
padding: 12px 18px;  
display: flex;  
justify-content: space-between;  
align-items: center;  
transition: transform 0.2s ease, box-shadow 0.2s ease;  
}
.user-card:hover {  
transform: scale(1.01);  
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);  
}
.user-info {  
display: flex;  
flex-direction: column;  
}
.user-info strong {  
font-size: 16px;  
color: #333;  
}
.user-info span {  
font-size: 14px;  
color: #555;  
}
.user-info small {  
font-size: 12px;  
color: #888;  
}
/* Boutons actions */  
.actions {  
display: flex;  
gap: 10px;  
}
.btn-edit {  
background-color: #28a745;  
color: white;  
padding: 6px 12px;  
border: none;  
border-radius: 6px;  
cursor: pointer;  
}
.btn-edit:hover {  
background-color: #218838;  
}
.btn-delete {  
background-color: #dc3545;  
color: white;  
padding: 6px 12px;  
border: none;  
border-radius: 6px;  
cursor: pointer;  
}
.btn-delete:hover {  
background-color: #b02a37;  
}
Vérifie que le composant utilise bien le CSS  
Dans le fichier :  
user-form.component.ts :  
Modifier HTML  
user-form.component.html :  
<h2 class="form-title">  
{{ editingUser ? 'Modifier un utilisateur' : 'Ajouter un utilisateur' }}  
</h2>  
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="user-form">  
<div class="form-row">  
<input type="text" formControlName="name" placeholder="Nom" />  
<input type="email" formControlName="email" placeholder="Email" />  
<button type="submit" class="btn-submit">  
{{ editingUser ? 'Mettre à jour' : 'Ajouter' }}  
</button>  
</div>  
<button  
*ngIf="editingUser"  
type="button"  
(click)="cancelEdit()"  
class="btn-cancel"  
>
Annuler  
</button>  
</form>  
<hr />  
<h2 class="list-title">Liste des utilisateurs</h2>  
<div class="user-list">  
<div *ngFor="let user of users" class="user-card">  
<div class="user-info">  
<strong>{{ user.name }}</strong>  
<span>{{ user.email }}</span>  
<small>{{ user.registeredAt }}</small>  
</div>  
<div class="actions">  
<button class="btn-edit" (click)="startEdit(user)">Modifier</button>  
<button class="btn-delete" (click)="supprimerUser(user.id!)">  
Supprimer  
</button>  
</div>  
</div>  
</div>