Nouveau : Datasets open source gratuits disponibles !Decouvrir →
🔺
Intermediaire 25 min Angular

NgRx avec Angular : guide complet

Pourquoi NgRx avec Angular : guide complet ?

NgRx est une bibliothèque state management très puissante et flexible pour les applications Angular. En tant qu'application front-end moderne, le state management devient rapidement un encombrement lorsque vous gérez des données complexes et réparties sur plusieurs composants. NgRx fournit une structure claire et prévisible pour gérer l'état de votre application, ce qui améliore la maintenabilité et la scalabilité.

Un cas d'usage concret est un e-commerce en ligne où le panier doit être partagé entre différents composants de l'application. Avec NgRx, vous pouvez centraliser le state du panier dans un seul store, facilitant ainsi la gestion des interactions entre les différents composants et garantissant que les données sont cohérentes.

Prerequis

  • Connaissances en Angular (version 10+)
  • Compréhension de RxJS
  • Environnement de développement installé avec Node.js (v14+), npm (v6+), et Angular CLI (v10+)

Pour installer les outils nécessaires :

## Installer Node.js et npm
curl -fsSL https://deb.nodesource.com/setup_14.x | sudo -E bash -
sudo apt-get install -y nodejs

## Installer Angular CLI
npm install -g @angular/cli@10+

Concepts fondamentaux

1. Store

Le store est le seul endroit où l'état de votre application se trouve.

// src/app/store/index.ts
import { createStore, ActionReducerMap } from '@ngrx/store';
import * as fromCounter from './counter.reducer';

export interface AppState {
  counter: fromCounter.State;
}

export const reducers: ActionReducerMap<AppState> = {
  counter: fromCounter.reducer,
};

export function createAppStore() {
  return createStore(reducers);
}

2. Reducer

Un reducer prend le state actuel et une action, puis retourne un nouveau state.

// src/app/store/counter.reducer.ts
import { Action } from '@ngrx/store';

export interface State {
  count: number;
}

const initialState: State = {
  count: 0,
};

export function reducer(state = initialState, action: Action): State {
  switch (action.type) {
    case '[Counter] Increment':
      return {
        ...state,
        count: state.count + 1,
      };
    case '[Counter] Decrement':
      return {
        ...state,
        count: state.count - 1,
      };
    default:
      return state;
  }
}

3. Action

Une action est un objet qui décrit ce qui a changé dans l'état.

// src/app/store/counter.actions.ts
export const increment = () => ({
  type: '[Counter] Increment',
});

export const decrement = () => ({
  type: '[Counter] Decrement',
});

4. Selector

Un selector permet d'extraire une partie spécifique du state.

// src/app/store/counter.selectors.ts
import { createSelector } from '@ngrx/store';
import * as fromCounter from './counter.reducer';

export const selectCounterState = (state: fromCounter.State) => state.counter;

export const selectCount = createSelector(
  selectCounterState,
  (counterState: fromCounter.State) => counterState.count
);

Mise en pratique : projet fil rouge

Étape 1 : Création du projet Angular

ng new ngRx-todo-app --routing
cd ngRx-todo-app

Étape 2 : Installation de NgRx

npm install @ngrx/store @ngrx/effects @ngrx/router-store @ngrx/entity --save

Étape 3 : Configuration du store

Créez un fichier store/index.ts pour configurer le store.

// src/app/store/index.ts
import { createStore, ActionReducerMap } from '@ngrx/store';
import * as fromTodos from './todos.reducer';

export interface AppState {
  todos: fromTodos.State;
}

export const reducers: ActionReducerMap<AppState> = {
  todos: fromTodos.reducer,
};

export function createAppStore() {
  return createStore(reducers);
}

Étape 4 : Création du reducer

Créez un fichier todos.reducer.ts pour le reducer.

// src/app/store/todos.reducer.ts
import { Action } from '@ngrx/store';

export interface State {
  ids: string[];
  entities: { [id: string]: Todo };
}

export const initialState: State = {
  ids: [],
  entities: {},
};

export function reducer(state = initialState, action: Action): State {
  switch (action.type) {
    case '[Todos] Add Todo':
      return {
        ...state,
        ids: [...state.ids, action.payload.id],
        entities: { ...state.entities, [action.payload.id]: action.payload },
      };
    default:
      return state;
  }
}

Étape 5 : Création des actions

Créez un fichier todos.actions.ts pour les actions.

// src/app/store/todos.actions.ts
import { Action } from '@ngrx/store';

export const addTodo = (id: string, title: string) => ({
  type: '[Todos] Add Todo',
  payload: { id, title },
});

Étape 6 : Création des selectors

Créez un fichier todos.selectors.ts pour les selectors.

// src/app/store/todos.selectors.ts
import { createSelector } from '@ngrx/store';
import * as fromTodos from './todos.reducer';

export const selectTodosState = (state: fromTodos.State) => state.todos;

export const selectAllTodos = createSelector(
  selectTodosState,
  (todosState: fromTodos.State) => Object.values(todosState.entities)
);

Étape 7 : Injection du store dans le composant

Créez un fichier app.component.ts pour le composant.

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Store, select } from '@ngrx/store';
import * as fromTodos from './store/todos.reducer';
import { addTodo } from './store/todos.actions';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  todos$ = this.store.pipe(select(fromTodos.selectAllTodos));

  constructor(private store: Store) {}

  ngOnInit() {}

  addTodo(title: string) {
    const id = Date.now().toString();
    this.store.dispatch(addTodo({ id, title }));
  }
}

Étape 8 : Ajout du template

Créez un fichier app.component.html pour le template.

<!-- src/app/app.component.html -->
<div>
  <h1>Todo List</h1>
  <ul>
    <li *ngFor="let todo of todos$ | async">
      todo.title
      <button (click)="removeTodo(todo.id)">Remove</button>
    </li>
  </ul>
  <input type="text" [(ngModel)]="newTodoTitle" placeholder="Add new todo" />
  <button (click)="addTodo(newTodoTitle)">Add Todo</button>
</div>

Erreurs frequentes et debugging

Erreur 1 : Action non dispatchée

// ❌ Mauvais
this.store.dispatch(addTodo({ id: '1', title: 'Test' }));

// ✅ Correct
this.store.dispatch(new AddTodo('1', 'Test'));

Erreur 2 : Selector non trouvé

// ❌ Mauvais
const todos$ = this.store.pipe(select(fromTodos.selectAllTodos));

// ✅ Correct
const todos$ = this.store.pipe(select(fromTodos.selectAllTodos));

Erreur 3 : Reducer non défini

// ❌ Mauvais
export const reducers: ActionReducerMap<AppState> = {
  counter: fromCounter.reducer,
};

// ✅ Correct
export const reducers: ActionReducerMap<AppState> = {
  todos: fromTodos.reducer,
};

Pour aller plus loin

1. Intégration avec Effects

NgRx Effects permet de gérer les effets secondaires (appels API, etc.) en séparant le flux d'actions et les actions déclenchées.

// src/app/store/todos.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import * as fromTodos from './todos.reducer';
import { addTodo } from './todos.actions';

@Injectable()
export class TodosEffects {
  constructor(private actions$: Actions) {}

  addTodo$ = createEffect(() =>
    this.actions$.pipe(
      ofType(fromTodos.addTodo),
      tap((action) => {
        // Effect code here
      })
    )
  );
}

2. Utilisation de Feature Modules

NgRx permet de structurer le state en utilisant des modules feature.

// src/app/store/todos/todo.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import * as fromTodos from './todos.reducer';
import { TodosEffects } from './todos.effects';

@NgModule({
  imports: [
    StoreModule.forFeature(fromTodos.todosFeatureKey, fromTodos.reducer),
    EffectsModule.forFeature([TodosEffects]),
  ],
})
export class TodosModule {}

3. Persisting State

NgRx persisting state permet de sauvegarder le state dans le local storage.

// src/app/store/persist-config.ts
import { StoreConfig } from '@ngrx/store';
import * as fromTodos from './todos.reducer';

export const todosFeatureKey = 'todos';

@StoreConfig({ name: todosFeatureKey, resetOnChanges: true })
export class TodosState {}

Défi pratique : Créer une application de gestion de contacts

Créez une application simple pour gérer des contacts. Utilisez NgRx pour gérer le state des contacts et ajoutez les fonctionnalités suivantes :

  • Ajouter un contact
  • Supprimer un contact
  • Mettre à jour un contact
  • Afficher la liste des contacts

Conseils :

  • Structurez votre store en utilisant des feature modules.
  • Utilisez NgRx Effects pour gérer les effets secondaires (appels API).
  • Implémentez des selectors pour extraire des parties spécifiques du state.

Besoin d'aide sur Angular ?

Besoin d'aide sur un projet technique ? Decrivez-le pour des conseils personnalises.

Recevoir des conseils

Questions frequentes

Qu'est-ce que NgRx ?
NgRx est une architecture d'état basée sur Redux pour les applications Angular, permettant une gestion prévisible et déterministe de l'état de l'application.
Comment installer NgRx dans un projet Angular existant ?
Pour installer NgRx, utilisez la commande 'ng add @ngrx/store' dans votre terminal. Cela ajoutera automatiquement les fichiers nécessaires et configure une structure de base pour l'utilisation d'NgRx.
Quelle est la différence entre actions et reducers en NgRx ?
En NgRx, les actions représentent des événements qui peuvent être dispatchés. Les reducers définissent comment ces actions modifient le state de l'application de manière déterministe.

Pages liees

Chaque semaine, le meilleur de la tech francaise

Tendances, salaires, outils et opportunites — directement dans votre boite mail.

Gratuit. Desabonnement en un clic. Pas de spam.