Angular NgRx

NgRx is a powerful library for managing state in Angular applications using the Redux pattern. In this blog post, we will explore what NgRx is, why it's useful, how it works with RxJS, and how to use its core concepts like Store and Effects effectively.

NgRx 1

Introduction to NgRx in Angular

State management is a crucial aspect of modern web applications, especially when dealing with complex and dynamic data. NgRx is a powerful library for managing state in Angular applications using the Redux pattern. In this blog post, we will explore what NgRx is, why it's useful, how it works with RxJS, and how to use its core concepts like Store and Effects effectively.

NgRx 2

What is NgRx and Why is it Useful?

NgRx is a state management library for Angular applications that follows the Redux architecture. It provides a unidirectional data flow and centralizes the application's state, making it easier to manage, debug, and scale. Some key benefits of using NgRx include:

  • Predictable State Management: By using a single source of truth, applications become more predictable.
  • Time Travel Debugging: NgRx DevTools allow developers to inspect and replay state changes.
  • Improved Performance: With immutability and efficient change detection, the application becomes more performant.
  • Simplifies Data Flow: NgRx enforces a structured way of handling application state.

NgRx 0

Why Do We Need Both NgRx and RxJS?

NgRx relies heavily on RxJS, which is a reactive programming library for handling asynchronous operations. Here’s why they complement each other:

  • Reactive Streams: RxJS provides powerful stream-based operations that allow real-time data handling.
  • Asynchronous Data Handling: NgRx Effects use RxJS to manage side effects like API calls efficiently.
  • State Synchronization: RxJS operators like map, filter, and switchMap help transform and control state changes.

Using RxJS in combination with NgRx allows for better scalability and maintainability, ensuring smooth data flow in the application.

NgRx Store Management System

NgRx Store is at the core of the state management system. It works by keeping the application state in a centralized store, following the Redux principles:

  1. Actions – Represent the events in the application (e.g., [User] Load Users).
  2. Reducers – Pure functions that specify how the state changes in response to actions.
  3. Selectors – Functions that retrieve and format the state data efficiently.

Example of a basic reducer:

import { createReducer, on } from '@ngrx/store';
import { loadUsersSuccess } from './user.actions';
import { User } from '../models/user.model';
 
export interface UserState {
  users: User[];
}
 
export const initialState: UserState = {
  users: []
};
 
export const userReducer = createReducer(
  initialState,
  on(loadUsersSuccess, (state, { users }) => ({ ...state, users }))
);

Introducing NgRx Effects and How to Use Them Well

NgRx Effects handle side effects like HTTP requests, logging, and routing outside of the store. They use RxJS observables to listen to actions and perform asynchronous tasks.

Example of an Effect for Fetching Users

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { UserService } from '../services/user.service';
import { loadUsers, loadUsersSuccess } from './user.actions';
import { mergeMap, map } from 'rxjs/operators';
 
@Injectable()
export class UserEffects {
  constructor(private actions$: Actions, private userService: UserService) {}
 
  loadUsers$ = createEffect(() => this.actions$.pipe(
    ofType(loadUsers),
    mergeMap(() => this.userService.getUsers()
      .pipe(map(users => loadUsersSuccess({ users }))))
  ));
}

Best Practices for Using NgRx Effects

  • Keep Effects simple and focus on handling side effects.
  • Use proper error handling mechanisms to catch errors in API calls.
  • Use mergeMap or switchMap based on the use case to optimize performance.
  • Keep the store clean by ensuring unnecessary actions are not dispatched.

Basic Usage Example in an Angular Demo

Step 1: Installing NgRx

ng add @ngrx/store @ngrx/effects @ngrx/store-devtools

Step 2: Defining Actions

import { createAction, props } from '@ngrx/store';
import { User } from '../models/user.model';
 
export const loadUsers = createAction('[User] Load Users');
export const loadUsersSuccess = createAction('[User] Load Users Success', props<{ users: User[] }>());

Step 3: Setting Up Store and Effects in AppModule

import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { userReducer } from './state/user.reducer';
import { UserEffects } from './state/user.effects';
 
@NgModule({
  imports: [
    StoreModule.forRoot({ users: userReducer }),
    EffectsModule.forRoot([UserEffects])
  ]
})
export class AppModule {}

Step 4: Dispatching Actions and Selecting Data in a Component

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { loadUsers } from './state/user.actions';
import { Observable } from 'rxjs';
import { User } from './models/user.model';
 
@Component({
  selector: 'app-user-list',
  template: `
    <div *ngFor="let user of users$ | async">
      {{ user.name }}
    </div>
  `
})
export class UserListComponent implements OnInit {
  users$: Observable<User[]>;
 
  constructor(private store: Store<{ users: User[] }>) {
    this.users$ = this.store.select('users');
  }
 
  ngOnInit() {
    this.store.dispatch(loadUsers());
  }
}

Conclusion

NgRx is a powerful tool for managing state in Angular applications. By leveraging the combination of Actions, Reducers, Selectors, and Effects, developers can create scalable, maintainable, and reactive applications. With this foundational knowledge, you can now dive deeper into optimizing your Angular projects using NgRx!