Signals
What are Signals?
Section intitulée « What are Signals? »Les Signals sont une primitive réactive pour gérer l’état dans Angular (v16+).
import { Component, signal, computed, effect } from '@angular/core';
@Component({ selector: 'app-counter', standalone: true, template: ` <p>Count: {{ count() }}</p> <p>Double: {{ double() }}</p> <button (click)="increment()">+</button> `})export class CounterComponent { // Signal count = signal(0);
// Computed signal double = computed(() => this.count() * 2);
// Effect constructor() { effect(() => { console.log('Count changed:', this.count()); }); }
increment() { this.count.set(this.count() + 1); // Ou this.count.update(value => value + 1); }}Signal Operations
Section intitulée « Signal Operations »// Create signalcount = signal(0);user = signal<User | null>(null);items = signal<string[]>([]);
// Read valueconsole.log(this.count()); // 0
// Set valuethis.count.set(10);
// Update valuethis.count.update(value => value + 1);
// Muter les tableaux/objetsthis.items.mutate(arr => arr.push('new item'));
// Ou better: immutable updatethis.items.update(arr => [...arr, 'new item']);Computed Signals
Section intitulée « Computed Signals »export class ShoppingCartComponent { items = signal([ { name: 'Item 1', price: 10, quantity: 2 }, { name: 'Item 2', price: 20, quantity: 1 } ]);
// Automatiquement recalculé lorsque les items changent total = computed(() => { return this.items().reduce((sum, item) => { return sum + (item.price * item.quantity); }, 0); });
itemCount = computed(() => this.items().length);
isEmpty = computed(() => this.itemCount() === 0);
addItem(item: CartItem) { this.items.update(items => [...items, item]); }}export class UserComponent { userId = signal(1);
constructor() { // S'exécute lorsque userId change effect(() => { console.log('Loading user:', this.userId()); this.loadUser(this.userId()); });
// Nettoyage effect((onCleanup) => { const subscription = this.someObservable$.subscribe();
onCleanup(() => { subscription.unsubscribe(); }); }); }}Signal Inputs
Section intitulée « Signal Inputs »import { Component, input } from '@angular/core';
@Component({ selector: 'app-user-card', standalone: true, template: ` <h2>{{ user().name }}</h2> <p>{{ user().email }}</p> `})export class UserCardComponent { // Signal input (Angular 17.1+) user = input.required<User>();
// Optionnel avec valeur par défaut showAvatar = input(true);
// Derived value displayName = computed(() => { const user = this.user(); return `${user.firstName} ${user.lastName}`; });}
// Usage<app-user-card [user]="currentUser" [showAvatar]="false" />Signal vs Observable
Section intitulée « Signal vs Observable »// Observable (RxJS)count$ = new BehaviorSubject(0);
this.count$.subscribe(value => { console.log(value);});
this.count$.next(1);
// Signalcount = signal(0);
effect(() => { console.log(this.count());});
this.count.set(1);Interop with Observables
Section intitulée « Interop with Observables »import { toSignal, toObservable } from '@angular/core/rxjs-interop';
export class DataComponent { userService = inject(UserService);
// Observable -> Signal users = toSignal(this.userService.getUsers(), { initialValue: [] });
// Signal -> Observable searchTerm = signal(''); searchTerm$ = toObservable(this.searchTerm);
constructor() { this.searchTerm$.pipe( debounceTime(300), switchMap(term => this.userService.search(term)) ).subscribe(results => { // Handle results }); }}Complex State with Signals
Section intitulée « Complex State with Signals »interface TodoState { todos: Todo[]; filter: 'all' | 'active' | 'completed';}
export class TodoService { private state = signal<TodoState>({ todos: [], filter: 'all' });
// Sélecteurs todos = computed(() => this.state().todos); filter = computed(() => this.state().filter);
filteredTodos = computed(() => { const todos = this.todos(); const filter = this.filter();
switch (filter) { case 'active': return todos.filter(t => !t.completed); case 'completed': return todos.filter(t => t.completed); default: return todos; } });
// Actions addTodo(title: string) { this.state.update(state => ({ ...state, todos: [...state.todos, { id: Date.now(), title, completed: false }] })); }
setFilter(filter: TodoState['filter']) { this.state.update(state => ({ ...state, filter })); }
toggleTodo(id: number) { this.state.update(state => ({ ...state, todos: state.todos.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) })); }}Bonnes Pratiques
Section intitulée « Bonnes Pratiques »- Utiliser les signals pour l’état local des composants
- Utiliser computed pour les valeurs dérivées
- Utiliser les effects avec parcimonie (effets secondaires uniquement)
- Préférer les mises à jour immutables plutôt que
mutate() - Utiliser toSignal pour faire le pont avec les observables
- Les Signals activent OnPush par défaut
- Plus simple que RxJS pour un état simple

