Aller au contenu principal

Gestion d'État

Component State (Simple)

@Component({
selector: 'app-counter'
})
export class CounterComponent {
count = 0; // Local state

increment() {
this.count++;
}
}

Signals (Modern)

import { Component, signal, computed } from '@angular/core';

@Component({
selector: 'app-todo-list'
})
export class TodoListComponent {
todos = signal<Todo[]>([]);
filter = signal<'all' | 'active' | 'completed'>('all');

filteredTodos = computed(() => {
const filter = this.filter();
const todos = this.todos();

switch (filter) {
case 'active': return todos.filter(t => !t.completed);
case 'completed': return todos.filter(t => t.completed);
default: return todos;
}
});

addTodo(title: string) {
this.todos.update(todos => [
...todos,
{ id: Date.now(), title, completed: false }
]);
}

toggleTodo(id: number) {
this.todos.update(todos =>
todos.map(t => t.id === id ? { ...t, completed: !t.completed } : t)
);
}
}

Service-based State

@Injectable({ providedIn: 'root' })
export class UserService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();

private loadingSubject = new BehaviorSubject<boolean>(false);
loading$ = this.loadingSubject.asObservable();

setUser(user: User) {
this.userSubject.next(user);
}

getUser(): Observable<User | null> {
return this.user$;
}

clearUser() {
this.userSubject.next(null);
}
}

// Component usage
@Component({})
export class ProfileComponent {
userService = inject(UserService);
user$ = this.userService.user$;
}

Signal-based Service

@Injectable({ providedIn: 'root' })
export class TodoStore {
private state = signal<TodoState>({
todos: [],
filter: 'all',
loading: false
});

// Selecaurs
todos = computed(() => this.state().todos);
filter = computed(() => this.state().filter);
loading = computed(() => this.state().loading);

filteredTodos = computed(() => {
const todos = this.todos();
const filter = this.filter();
// Filter logic...
return todos;
});

// Actions
addTodo(title: string) {
this.state.update(state => ({
...state,
todos: [...state.todos, { id: Date.now(), title, completed: false }]
}));
}

setFilter(filter: 'all' | 'active' | 'completed') {
this.state.update(state => ({ ...state, filter }));
}

async loadTodos() {
this.state.update(state => ({ ...state, loading: true }));
try {
const todos = await this.http.get<Todo[]>('/api/todos').toPromise();
this.state.update(state => ({ ...state, todos, loading: false }));
} catch (error) {
this.state.update(state => ({ ...state, loading: false }));
}
}
}

NgRx (Enterprise)

Store Configuration

// app.config.ts
import { provideStore } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';

export const appConfig: ApplicationConfig = {
providers: [
provideStore({ todos: todoReducer }),
provideEffects([TodoEffects])
]
};

Actions

import { createAction, props } from '@ngrx/store';

export const loadTodos = createAction('[Todo] Load Todos');
export const loadTodosSuccess = createAction(
'[Todo] Load Todos Success',
props<{ todos: Todo[] }>()
);
export const addTodo = createAction(
'[Todo] Add Todo',
props<{ title: string }>()
);

Reducer

import { createReducer, on } from '@ngrx/store';

export interface TodoState {
todos: Todo[];
loading: boolean;
}

const initialState: TodoState = {
todos: [],
loading: false
};

export const todoReducer = createReducer(
initialState,
on(loadTodos, state => ({ ...state, loading: true })),
on(loadTodosSuccess, (state, { todos }) => ({
...state,
todos,
loading: false
})),
on(addTodo, (state, { title }) => ({
...state,
todos: [...state.todos, { id: Date.now(), title, completed: false }]
}))
);

Selectors

import { createSelector, createFeatureSelector } from '@ngrx/store';

export const selectTodoState = createFeatureSelector<TodoState>('todos');

export const selectAllTodos = createSelector(
selectTodoState,
state => state.todos
);

export const selectActiveTodos = createSelector(
selectAllTodos,
todos => todos.filter(t => !t.completed)
);

export const selectLoading = createSelector(
selectTodoState,
state => state.loading
);

Effects

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map, mergeMap, catchError } from 'rxjs/operators';

@Injectable()
export class TodoEffects {
actions$ = inject(Actions);
todoService = inject(TodoService);

loadTodos$ = createEffect(() =>
this.actions$.pipe(
ofType(loadTodos),
mergeMap(() =>
this.todoService.getTodos().pipe(
map(todos => loadTodosSuccess({ todos })),
catchError(() => of(loadTodosFailure()))
)
)
)
);
}

Component Utilisation

import { Store } from '@ngrx/store';

@Component({})
export class TodoListComponent {
store = inject(Store);

todos$ = this.store.select(selectAllTodos);
loading$ = this.store.select(selectLoading);

ngOnInit() {
this.store.dispatch(loadTodos());
}

addTodo(title: string) {
this.store.dispatch(addTodo({ title }));
}
}

Comparison

ApproachBest ForComplexityPerformance
Component StateSimple, isolated stateLowGood
SignalsLocal reactive stateLowExcellent
Service + BehaviorSubjectShsontd stateMediumGood
Signal ServiceShsontd reactive stateMediumExcellent
NgRxEnterprise, complex appsHighExcellent

Bonnes Pratiques

@Injectable({ providedIn: 'root' })
export class AppStore {
// Private state
private state = signal<AppState>(initialState);

// Public selecaurs
user = computed(() => this.state().user);
isLoggedIn = computed(() => !!this.user());

// Actions (mutate state)
login(user: User) {
this.state.update(s => ({ ...s, user }));
}

logout() {
this.state.update(s => ({ ...s, user: null }));
}
}

RxJS-based (Legacy apps)

@Injectable({ providedIn: 'root' })
export class AppStore {
private stateSubject = new BehaviorSubject<AppState>(initialState);
private state$ = this.stateSubject.asObservable();

user$ = this.state$.pipe(map(s => s.user));
isLoggedIn$ = this.user$.pipe(map(u => !!u));

login(user: User) {
const current = this.stateSubject.value;
this.stateSubject.next({ ...current, user });
}
}

When to Use What

  • Component state: Simple, isolated features
  • Signals: Moderne reactive local/shsontd state
  • Service + BehaviorSubject: Legacy shsontd state
  • NgRx: Large apps, complex state, team standards
  • Component store (NgRx): Feature-specific state