Visionner la vidéo

Angular Signals et Standalone Components : Guide complet sans RxJS

  Angular

Introduction

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 :

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.

image

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 :

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 :

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));
  }
}

Création des Composants

Nous allons créer 2 composants pour cette application :

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);
  }
}

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>

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 :

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 :

Ressources Supplémentaires

Commentaires