In any Angular application, managing state effectively is crucial for ensuring that your app behaves predictively and remains maintainable as it scales. State management involves tracking and managing the state of your application — the data your app relies upon to function. When multiple components need to interact with the same state, managing this data can get complicated. That’s where a state management library like NgRx comes into play.
NgRx is a powerful library inspired by Redux. It provides a structured approach to manage the state of your application in a reactive and predictable way. NgRx leverages RxJS, the reactive programming library for JavaScript, allowing developers to manage state using Observables. This makes state updates more predictable and easier to track.
To understand NgRx fully, it's essential to familiarize ourselves with its core concepts:
Actions
Actions are payloads of information that signal that something has occurred in your application, usually in response to a user event such as button clicks or API requests. Actions must have a type, and they can optionally contain a payload.
export const ADD_TODO = '[Todo] Add Todo'; export class AddTodo { static readonly type = ADD_TODO; constructor(public payload: { title: string }) {} }
Reducers
Reducers are pure functions that take the current state of the application and an action as arguments and return a new state. Reducers determine how the state changes in response to the actions dispatched.
import { Todo } from './todo.model'; export interface State { todos: Todo[]; } const initialState: State = { todos: [] }; export function todoReducer(state: State = initialState, action: AddTodo): State { switch (action.type) { case ADD_TODO: return { ...state, todos: [...state.todos, { title: action.payload.title, completed: false }] }; default: return state; } }
Selectors
Selectors are pure functions used to obtain slices of your store's state. They allow components to obtain the data they need in a clean way.
import { createSelector } from '@ngrx/store'; export const selectTodos = (state: State) => state.todos; export const selectCompletedTodos = createSelector( selectTodos, (todos: Todo[]) => todos.filter(todo => todo.completed) );
Effects
Effects handle side effects, such as API calls or interactions with external services. They listen for actions dispatched from your components, perform the asynchronous operations, and then dispatch new actions based on the results.
import { Actions, ofType, createEffect } from '@ngrx/effects'; import { Injectable } from '@angular/core'; import { of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { DataService } from './data.service'; @Injectable() export class TodoEffects { constructor(private actions$: Actions, private dataService: DataService) {} loadTodos$ = createEffect(() => this.actions$.pipe( ofType('[Todo] Load Todos'), switchMap(() => this.dataService.getTodos() .pipe( map(todos => ({ type: '[Todo] Load Todos Success', payload: todos })), catchError(() => of({ type: '[Todo] Load Todos Failure' })) )) )); }
To set up NgRx in your Angular application, follow these steps:
Install NgRx packages:
ng add @ngrx/store @ngrx/effects @ngrx/store-devtools
Create your store module in your AppModule
:
import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { todoReducer } from './store/todo.reducer'; import { TodoEffects } from './store/todo.effects'; @NgModule({ declarations: [ /* components */ ], imports: [ BrowserModule, StoreModule.forRoot({ todos: todoReducer }), EffectsModule.forRoot([TodoEffects]), ], bootstrap: [AppComponent] }) export class AppModule { }
Dispatch actions from your components:
import { Store } from '@ngrx/store'; @Component({...}) export class TodoComponent { constructor(private store: Store<State>) {} addTodo() { this.store.dispatch(new AddTodo({ title: 'New Todo Item' })); } }
Use selectors to obtain data in your components:
import { Store } from '@ngrx/store'; @Component({...}) export class TodoListComponent { todos$: Observable<Todo[]>; constructor(private store: Store<State>) { this.todos$ = this.store.select(selectTodos); } }
When working with NgRx, it's essential to adopt some best practices that will keep your code sustainable and easy to understand:
By following the patterns of NgRx, you facilitate better organization and management of state within your Angular applications, transforming complex, unpredictable behaviors into clear, maintainable code structures.
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular
24/10/2024 | Angular