Angular Signals et Standalone Components : Guide complet sans RxJS
AngularIntroduction
Aujourd’hui, nous allons explorer les nouveautés d’Angular qui ont révolutionné le développement avec ce framework.
Si vous avez déjà été frustré par la complexité de RxJS dans Angular, préparez-vous à être captivé par les Signals et les Standalone Components.
Ces fonctionnalités simplifient grandement le développement d’applications web modernes et réactives.
Dans cet article, nous allons :
- Comprendre ce que sont les Signals.
- Découvrir les Standalone Components.
- Créer une application de gestion de tâches (TODO) avec Angular et Angular Material.
- Voir comment ces nouvelles fonctionnalités améliorent l’expérience de développement.
Qu’est-ce que les Signals ?
Un Signal est une valeur qui change au fil du temps et qui peut notifier d’autres parties de l’application lorsqu’elle est mise à jour. C’est un mécanisme réactif qui permet de suivre les changements d’état de manière efficace.
Dans le diagramme ci-dessus :
Le Signal est au centre. Deux Consommateurs (Consumer 1 et Consumer 2) dépendent de ce Signal. Lorsque le Signal est mis à jour (via les fonctions set ou update), il notifie automatiquement tous les consommateurs qui en dépendent. Ce mécanisme est similaire au patron de conception Observer, où plusieurs observateurs peuvent réagir aux changements d’un sujet.
Avantages des Signals :
- Réactivité : Mise à jour automatique des composants dépendants.
- Simplicité : Moins de code boilerplate comparé à RxJS.
- Performance : Optimisation des changements d’état sans nécessité de subscriptions manuelles.
Les Standalone Components
Les Standalone Components sont une autre nouveauté d’Angular qui permet de créer des composants sans avoir besoin de les déclarer dans un module NgModule. Cela simplifie l’organisation du code et réduit la complexité des applications Angular.
Avantages des Standalone Components :
- Simplicité : Plus besoin de déclarer les composants dans un module.
- Modularité : Les composants peuvent être facilement réutilisés et importés.
- Rapidité : Réduction du temps de configuration initiale.
Création d’une Application de Gestion de Tâches
Nous allons mettre en pratique ces nouveautés en créant une application TODO simple avec Angular et Angular Material.
Mise en Place du Projet
Création du projet Angular :
ng new task-signal-app
Ajout d’Angular Material :
ng add @angular/material
Choisissez le thème indigo-pink
et acceptez les options par défaut.
Définition du Modèle de Données
Créez une interface TypeScript pour représenter une tâche :
// src/app/models/task.model.ts
export interface Task {
title: string;
}
Création du Service TaskService
Le service gérera la liste des tâches en utilisant un Signal.
// src/app/services/task.service.ts
import { Injectable, signal } from "@angular/core";
import { Task } from "../models/task.model";
@Injectable({
providedIn: "root",
})
export class TaskService {
tasks = signal<Task[]>([]);
addTask(task: Task) {
this.tasks.update((tasks) => [...tasks, task]);
}
removeTask(task: Task) {
this.tasks.update((tasks) => tasks.filter((t) => t !== task));
}
}
- La variable
tasks
: Signal contenant la liste des tâches. - La méthode
addTask
: Ajoute une nouvelle tâche à la liste. - La méthode
removeTask
: Supprime une tâche de la liste.
Création des Composants
Nous allons créer 2 composants pour cette application :
TaskListComponent
qui affichera la liste des tâches et permettra d’en ajouter de nouvelles.TaskItemComponent
qui affichera une tâche.
Composant TaskListComponent
Ce composant affichera la liste des tâches et permettra d’en ajouter de nouvelles.
// src/app/components/task-list/task-list.component.ts
import { Component, computed, inject, signal } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { MatButtonModule } from "@angular/material/button";
import { MatCardModule } from "@angular/material/card";
import { MatInputModule } from "@angular/material/input";
import { Task } from "../../models/task.model";
import { TaskService } from "../../services/task.service";
import { TaskItemComponent } from "../task-item/task-item.component";
@Component({
selector: "app-task-list",
standalone: true,
imports: [
MatInputModule,
MatButtonModule,
MatCardModule,
FormsModule,
TaskItemComponent,
],
templateUrl: "./task-list.component.html",
styleUrl: "./task-list.component.css",
})
export class TaskListComponent {
readonly searchTerm = signal("");
readonly taskTitle = signal("");
private readonly taskService = inject(TaskService);
readonly filteredTasks = computed(() => {
return this.taskService
.tasks()
.filter((task) =>
task.title.toLowerCase().includes(this.searchTerm().toLowerCase())
);
});
addTask() {
const title = this.taskTitle()?.trim();
if (title) {
this.taskService.addTask({ title });
this.taskTitle.set("");
}
}
removeTask(task: Task) {
this.taskService.removeTask(task);
}
}
taskTitle
: Signal pour le titre de la nouvelle tâche.searchTerm
: Signal pour le terme de recherche.filteredTasks
: Signal dérivé qui filtre les tâches en fonction du searchTerm.
Template du Composant TaskListComponent
<!-- src/app/components/task-list/task-list.component.html -->
<
<div class="container">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Rechercher une tâche</mat-label>
<input matInput [(ngModel)]="searchTerm" />
</mat-form-field>
<form (ngSubmit)="addTask()">
<mat-form-field appearance="outline" class="full-width">
<mat-label>Saisir une tâche</mat-label>
<input matInput name="title" [(ngModel)]="taskTitle" />
</mat-form-field>
<button mat-raised-button color="primary" type="submit">Ajouter</button>
</form>
<div class="task-list">
@for (task of filteredTasks(); track task.title) {
<app-task-item [task]="task" (removeTask)="removeTask($event)" />
}
</div>
</div>
- Utilisation d’Angular Material pour les champs de saisis et boutons.
- Two-way binding avec [(ngModel)] pour les champs de saisie.
Qu’est-ce que le two way binding ?
Et bien, c’est le fait de lier la valeur du champ de saisie quand l’utilisateur renseigne le champ et aussi quand le composant met à jour le signal.
Composant TaskItemComponent
Ce composant représente une tâche individuelle.
Il contiendra un input
qui reçoit l’objet tâche et un output
pour émettre un évènement lors qu’une tâche est supprimée.
// src/app/components/task-item/task-item.component.ts
import { Component, input, output } from "@angular/core";
import { MatCardModule } from "@angular/material/card";
import { Task } from "../../models/task.model";
import { MatButtonModule } from "@angular/material/button";
@Component({
selector: "app-task-item",
standalone: true,
imports: [MatCardModule, MatButtonModule],
templateUrl: "./task-item.component.html",
styleUrl: "./task-item.component.css",
})
export class TaskItemComponent {
task = input.required<Task>();
removeTask = output<Task>();
}
Template du Composant TaskItemComponent
La template est relativement simple, elle affiche le titre de la tâche et un bouton pour supprimer la tâche.
<!-- src/app/components/task-item/task-item.component.html -->
<mat-card>
<mat-card-content>
{{ task().title }}
<button mat-raised-button color="warn" (click)="removeTask.emit(task())">
Supprimer
</button>
</mat-card-content>
</mat-card>
Configuration des Routes
Déclarez une route pour afficher le composant TaskListComponent
.
// src/app/app.routes.ts
import { Routes } from "@angular/router";
import { TaskListComponent } from "./components/task-list/task-list.component";
export const routes: Routes = [{ path: "", component: TaskListComponent }];
Assurez-vous que votre fichier app.component.html
contient l’instruction <router-outlet>
:
<!-- src/app/app.component.html -->
<router-outlet></router-outlet>
Test de l’Application
Lancez le serveur de développement :
ng serve
Ouvrez votre navigateur à l’adresse http://localhost:4200
et testez les fonctionnalités :
- Ajout de tâches : Saisissez une tâche et cliquez sur “Ajouter”. La tâche apparaît dans la liste.
- Suppression de tâches : Cliquez sur “Supprimer” pour retirer une tâche de la liste.
- Recherche de tâches : Utilisez le champ de recherche pour filtrer les tâches en temps réel.
Conclusion
Nous avons créé une application de gestion de tâches en utilisant les Signals et les Standalone Components d’Angular. Ces fonctionnalités offrent une nouvelle manière de gérer l’état et la structure de vos applications, en les rendant plus réactives et plus simples à développer.
Points clés à retenir :
- Les Signals permettent une gestion réactive de l’état sans la complexité de RxJS.
- Les Standalone Components simplifient la structure des applications en éliminant la nécessité de modules pour chaque composant.
Ressources Supplémentaires
- Code source du projet : https://github.com/codingtechacademy/angular-signal-todo-app
- Documentation Angular : https://angular.dev/guide/signals