NgRx Effects is a powerful library that allows you to handle side effects in your Angular applications. Side effects are operations that interact with the outside world, such as HTTP requests, logging, or interacting with browser APIs. NgRx Effects helps you isolate these side effects from your components and services, making your application more predictable and easier to test.

Key Concepts

  1. Effects: Functions that listen for actions dispatched from the store and perform side effects.
  2. Actions: Objects that represent an event in the application.
  3. Reducers: Functions that handle state changes based on actions.
  4. Store: A single source of truth for the application state.

Setting Up NgRx Effects

Step 1: Install NgRx Effects

First, you need to install the NgRx Effects library:

npm install @ngrx/effects

Step 2: Create an Effect

Create a new file for your effect, for example, auth.effects.ts:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { AuthService } from '../services/auth.service';
import { login, loginSuccess, loginFailure } from '../actions/auth.actions';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class AuthEffects {
  constructor(
    private actions$: Actions,
    private authService: AuthService
  ) {}

  login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(login),
      mergeMap(action =>
        this.authService.login(action.credentials).pipe(
          map(user => loginSuccess({ user })),
          catchError(error => of(loginFailure({ error })))
        )
      )
    )
  );
}

Step 3: Register the Effect

Register the effect in your AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { AuthEffects } from './effects/auth.effects';
import { reducers } from './reducers';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([AuthEffects])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Practical Example

Let's create a simple login effect that handles user authentication.

Step 1: Define Actions

Create a file auth.actions.ts:

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

export const login = createAction(
  '[Auth] Login',
  props<{ credentials: { username: string; password: string } }>()
);

export const loginSuccess = createAction(
  '[Auth] Login Success',
  props<{ user: any }>()
);

export const loginFailure = createAction(
  '[Auth] Login Failure',
  props<{ error: any }>()
);

Step 2: Create the AuthService

Create a file auth.service.ts:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  login(credentials: { username: string; password: string }): Observable<any> {
    // Simulate an HTTP request
    if (credentials.username === 'admin' && credentials.password === 'admin') {
      return of({ id: 1, name: 'Admin User' });
    } else {
      throw new Error('Invalid credentials');
    }
  }
}

Step 3: Create the Effect

Create a file auth.effects.ts:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { AuthService } from '../services/auth.service';
import { login, loginSuccess, loginFailure } from '../actions/auth.actions';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class AuthEffects {
  constructor(
    private actions$: Actions,
    private authService: AuthService
  ) {}

  login$ = createEffect(() =>
    this.actions$.pipe(
      ofType(login),
      mergeMap(action =>
        this.authService.login(action.credentials).pipe(
          map(user => loginSuccess({ user })),
          catchError(error => of(loginFailure({ error: error.message })))
        )
      )
    )
  );
}

Step 4: Register the Effect

Update your AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { AuthEffects } from './effects/auth.effects';
import { reducers } from './reducers';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([AuthEffects])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Practical Exercise

Task

  1. Create a new effect that handles user registration.
  2. Define actions for register, registerSuccess, and registerFailure.
  3. Implement the register method in the AuthService.
  4. Create the RegisterEffects class and handle the registration logic.
  5. Register the new effect in the AppModule.

Solution

Step 1: Define Actions

Create a file auth.actions.ts:

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

export const register = createAction(
  '[Auth] Register',
  props<{ credentials: { username: string; password: string } }>()
);

export const registerSuccess = createAction(
  '[Auth] Register Success',
  props<{ user: any }>()
);

export const registerFailure = createAction(
  '[Auth] Register Failure',
  props<{ error: any }>()
);

Step 2: Update AuthService

Update auth.service.ts:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  login(credentials: { username: string; password: string }): Observable<any> {
    // Simulate an HTTP request
    if (credentials.username === 'admin' && credentials.password === 'admin') {
      return of({ id: 1, name: 'Admin User' });
    } else {
      throw new Error('Invalid credentials');
    }
  }

  register(credentials: { username: string; password: string }): Observable<any> {
    // Simulate an HTTP request
    return of({ id: 2, name: credentials.username });
  }
}

Step 3: Create the Register Effect

Create a file register.effects.ts:

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { AuthService } from '../services/auth.service';
import { register, registerSuccess, registerFailure } from '../actions/auth.actions';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { of } from 'rxjs';

@Injectable()
export class RegisterEffects {
  constructor(
    private actions$: Actions,
    private authService: AuthService
  ) {}

  register$ = createEffect(() =>
    this.actions$.pipe(
      ofType(register),
      mergeMap(action =>
        this.authService.register(action.credentials).pipe(
          map(user => registerSuccess({ user })),
          catchError(error => of(registerFailure({ error: error.message })))
        )
      )
    )
  );
}

Step 4: Register the Effect

Update your AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { AuthEffects } from './effects/auth.effects';
import { RegisterEffects } from './effects/register.effects';
import { reducers } from './reducers';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot(reducers),
    EffectsModule.forRoot([AuthEffects, RegisterEffects])
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}

Summary

In this section, you learned how to use NgRx Effects to handle side effects in your Angular application. You created an effect to handle user login and registration, defined actions, and registered the effects in your application module. NgRx Effects helps you manage side effects in a clean and predictable way, making your application easier to maintain and test.

Next, you will learn about state management using NgRx Store in the next module.

© Copyright 2024. All rights reserved