Step-By-Step Guide for integrating NgRx State Management with Angular 16!
Tags: angular, frontend, redux, ngrx
Published: 2023-06-08 10:15:36


NGRX is a popular library for state management in Angular applications. It helps to manage the state of an application in a predictable and scalable way. In this guide, we will go through the step-by-step process of implementing NGRX by building a small Todo application.

> **You can refer to the full code** [here](https://stackblitz.com/edit/stackblitz-starters-squhtc?file=src%2Ftodo-list.component.ts,src%2Fstore%2Feffects.ts,src%2Fstore%2Fservice.ts,src%2Fstore%2Fselectors.ts,src%2Fmain.ts,src%2Ftodo-list.component.html,src%2Fstore%2Freducers.ts,src%2Fstore%2Fstore.ts,src%2Fstore%2Factions.ts)

## Step 1: Install NGRX
To install NGRX, run the following command in your terminal:
```
npm install @ngrx/store @ngrx/effects --save
```
The `@ngrx/store` package provides the core state management functionality for NGRX. The `@ngrx/effects` package provides a way to handle side-effects in your application.

Our application folder structure will look like this-
```
src/
   | store/
     | actions.ts
     | reducers.ts
     | selectors.ts
     | store.ts
     | todo.model.ts 
   | main.ts
   | todo.component.ts
   | todo.component.html
```

## Step 2: Define the Model Todo
The first step is to define the model of your data in `store/todo.model.ts` -

```typescript
export interface Todo {
  id: number | string;
  description: string;
  completed: boolean;
}
```
In this example, we define the `Todo` interface with some properties.

## Step 3: Define the Service and Actions
Service will contain our todo api service with the `getAll` function. This will act as a fake backend service. We will be going to use the Observable and imitate the latency to show the loader.
Define the todo service in `store/service.ts`

```typescript
@Injectable()
export class ToDoService {
 // fake backend
  getAll(): Observable<Todo[]> {
    return of(
      [{
        id: 1,
        description: 'description 1',
        completed: false
      },
      {
        id: 2,
        description: 'description 2',
        completed: false
      }]
    ).pipe(delay(2000))
  }
}
```

Actions are messages that describe a state change in your application. They are plain JavaScript objects that have a type property and an optional payload. Define the actions in `store/actions.ts`

```typescript
export const loadTodos = createAction('[Todo] Load Todos');
export const loadTodosSuccess = createAction('[Todo] Load Todos Success', props<{ todos: Todo[] }>());
export const loadTodosFailure = createAction('[Todo] Load Todos Failure', props<{ error: string }>());
export const addTodo = createAction('[Todo] Add Todo', props<{ todo: Todo }>());
export const updateTodo = createAction('[Todo] Update Todo', props<{ todo: Todo }>());
export const deleteTodo = createAction('[Todo] Delete Todo', props<{ id: string }>());
```
In this example, we define several actions for managing the `Todo` state. The `loadTodos` action is used to load the list of todos from the server. The `loadTodosSuccess` action is dispatched when the todos are loaded successfully. The `loadTodosFailure` action is dispatched when there is an error loading the todos. The `addTodo`, `updateTodo`, and `deleteTodo` actions are used to add, update, and delete todos respectively.

## Step 4: Define the Reducers
Reducers are pure functions that take the current state and an action and return a new state. They are responsible for handling the state changes in your application. Define the actions in `store/reducers.ts`

```typescript
export interface TodoState {
todos: Todo[];
loading: boolean;
error: string;
}
export const initialState: TodoState = {
todos: [],
loading: false,
error: ''
};
export const todoReducer = createReducer(
initialState,

on(TodoActions.loadTodos, state => ({ ...state, loading: true })),

on(TodoActions.loadTodosSuccess, (state, { todos }) =>({ ...state, todos, loading: false })),

on(TodoActions.loadTodosFailure, (state, { error }) => ({ ...state, error, loading: false })),

on(TodoActions.addTodo, (state, { todo }) => ({ ...state, todos: [...state.todos, todo] })),

on(TodoActions.updateTodo, (state, { todo }) => ({ ...state, todos: state.todos.map(t => t.id === todo.id ? todo : t) })),

on(TodoActions.deleteTodo, (state, { id }) => ({ ...state, todos: state.todos.filter(t => t.id !== id) })),
);
```
In this example, we define a reducer for managing the `Todo` state. The `todoReducer` function takes the `initialState` and a set of reducer functions defined using the `on` function from `@ngrx/store`. Each reducer function handles a specific action and returns a new state.

## Step 5: Define the Effects
Effects are services that listen for actions and perform side-effects such as HTTP requests or interacting with the browser's APIs.
```typescript

@Injectable()
export class TodoEffects {
  
  loadTodos$ = createEffect(() =>
    this.actions$.pipe(
      ofType(TodoActions.loadTodos),
      mergeMap(() =>
        this.todoService.getAll().pipe(
          map((todos) => TodoActions.loadTodosSuccess({ todos })),
          catchError((error) =>
            of(TodoActions.loadTodosFailure({ error: error.message }))
          )
        )
      )
    )
  );
  constructor(private actions$: Actions, private todoService: ToDoService) {}
}
```
In this example, we define an effect for handling the `loadTodos` action. The `loadTodos$` effect listens for the `loadTodos` action using the `ofType` operator from `@ngrx/effects`. When the action is dispatched, the effect calls the `getAll` method from the `TodoService` and dispatches either the `loadTodosSuccess` or `loadTodosFailure` action depending on the result.

## Step 6: Define interface for the Store
This will be defined in the `store/sotre.ts` file -

```typescript
export interface AppState {
  todo: TodoState
}

export interface AppStore {
  todo: ActionReducer<TodoState, Action>;
}

export const appStore: AppStore = {
  todo: todoReducer
}

export const appEffects = [TodoEffects];
```
`AppState` interface will define all the feature properties of the application. Here we have a single feature called as `todo` which is of type `TodoState`. We can have multiple features inside our application like this.

`AppStore` interface will define all the reducer types used in our app. In this case we have a single todo reducer so we will map the `todoReducer` to the `todo` property. `appStore` will be used to config our Store Module.

`appEffects` will have the array of defined effects classes. This will be used to register the effects in the application.


## Step 7: Register the Store and Effects in the main standalone component

To use the NGRX store in your application, you need to register the `StoreModule` in your `AppModule`. We will be using a standalone components here -

```typescript
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [
    CommonModule
  ],
  template: ``,
})
export class App {
}

bootstrapApplication(App, {
  // register the store providers here
  providers: [
    provideStore(appStore),
    provideEffects(appEffects),
    ToDoService
  ]
});
```
In this example, we register the store using the `appStore`. We also register the Effects with `appEffects`.

## Step 8: Use the Store in ToDo List Component

To use the store in your components, you need to inject the `Store` service and dispatch actions.We will create a standalone `TodoListComponent` in `src/todo-list.component.ts` with the html file in `src/todo-list.component.html`

```typescript
@Component({
standalone: true,
selector: 'app-todo-list',
imports: [NgFor, NgIf, AsyncPipe, JsonPipe],
templateUrl: './todo-list.component.html'
})
export class TodoListComponent {
todos$: Observable<Todo[]>;
isLoading$: Observable<boolean>;

constructor(private store: Store<AppState>) {
  this.todos$ = this.store.select(todoSelector);
  this.isLoading$ = this.store.select(state => state.todo.loading);
  this.loadTodos();
}

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

addTodo(index: number) {

const todo: Todo = {id: index, description: 'New Todo', completed: false };
this.store.dispatch(TodoActions.addTodo({ todo }));
}

complete(todo: Todo) {
  this.store.dispatch(TodoActions.updateTodo({todo : {...todo, completed: true}}));
}
}
```

**_todo-list.component.html_**
```html
<ng-container *ngIf="todos$ | async as todos;">

  <ul *ngIf="todos.length; else empty">
    <li *ngFor="let todo of todos$ | async; let i = index;">
    {{todo.id}}  {{ todo.description }} - {{ todo.completed ? 'Done' : 'Pending' }}
      <button [disabled]="todo.completed" (click)="complete(todo)">
      complete
    </button>
    </li>

  </ul>

  <ng-template #empty>
    <div>No Data</div>
  </ng-template>


<button (click)="loadTodos()">Refresh</button>
<button (click)="addTodo(todos.length + 1)">Add</button>

<div *ngIf="isLoading$ | async">Loading...</div>

</ng-container>
```

In this example, we define a component that uses the `Store` service to load and display todos. We inject the `Store` service and select the `todos` property from the state using the `select` method. We also define three methods for dispatching the `loadTodos`, `addTodo` and `updateTodo` actions. The `async` pipe is used to subscribe to the `todos$` observable and display the list of todos.

## Step 9: Register the ToDo List Component in main component

```typescript
@Component({
  selector: 'my-app',
  standalone: true,
  imports: [
    CommonModule,
    TodoListComponent
  ],
  template: `
    <app-todo-list/>
  `,
})
export class App {
}
```
Add `TodoListComponent` in the imports array. Add the `<app-todo-list/>` tag to display the TodoListComponent.

## Conclusion
In this guide, we have gone through the step-by-step process of implementing NGRX in an Angular application. We have seen how to define the state, actions, reducers, effects, and how to use the store in components. By following these steps, you can easily manage the state of your application in a scalable and predictable way using NGRX.