NgRx Effects is a powerful library that allows you to handle side effects in your Angular application. Side effects are operations that interact with the outside world, such as HTTP requests, logging, or other asynchronous operations. NgRx Effects helps you isolate these side effects from your components and state management logic, making your application more predictable and easier to test.
Key Concepts
- Effects: Functions that listen for specific actions dispatched to the store and perform side effects in response.
- Actions: Objects that represent an event or intention to change the state.
- Reducers: Functions that handle state transitions based on actions.
- Selectors: Functions that select a piece of state from the store.
Setting Up NgRx Effects
Step 1: Install NgRx Effects
First, you need to install the NgRx Effects library:
Step 2: Create an Effect
Create a new file for your effects, 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
or a feature module:
import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; import { AuthEffects } from './effects/auth.effects'; @NgModule({ imports: [ EffectsModule.forRoot([AuthEffects]) ] }) export class AppModule {}
Practical Example
Let's create a simple example where we handle user login using NgRx Effects.
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 a Service
Create a file auth.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private http: HttpClient) {} login(credentials: { username: string; password: string }): Observable<any> { return this.http.post('/api/login', 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 }))) ) ) ) ); }
Step 4: Register the Effect
Register the effect in your AppModule
:
import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; import { AuthEffects } from './effects/auth.effects'; @NgModule({ imports: [ EffectsModule.forRoot([AuthEffects]) ] }) export class AppModule {}
Practical Exercise
Exercise
- Create a new Angular service called
ProductService
that fetches a list of products from an API. - Define actions for loading products:
loadProducts
,loadProductsSuccess
, andloadProductsFailure
. - Create an effect that listens for the
loadProducts
action, calls theProductService
to fetch products, and dispatches eitherloadProductsSuccess
orloadProductsFailure
based on the result.
Solution
Step 1: Define Actions
Create a file product.actions.ts
:
import { createAction, props } from '@ngrx/store'; export const loadProducts = createAction('[Product] Load Products'); export const loadProductsSuccess = createAction( '[Product] Load Products Success', props<{ products: any[] }>() ); export const loadProductsFailure = createAction( '[Product] Load Products Failure', props<{ error: any }>() );
Step 2: Create a Service
Create a file product.service.ts
:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ProductService { constructor(private http: HttpClient) {} getProducts(): Observable<any[]> { return this.http.get<any[]>('/api/products'); } }
Step 3: Create the Effect
Create a file product.effects.ts
:
import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { ProductService } from '../services/product.service'; import { loadProducts, loadProductsSuccess, loadProductsFailure } from '../actions/product.actions'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { of } from 'rxjs'; @Injectable() export class ProductEffects { constructor( private actions$: Actions, private productService: ProductService ) {} loadProducts$ = createEffect(() => this.actions$.pipe( ofType(loadProducts), mergeMap(() => this.productService.getProducts().pipe( map(products => loadProductsSuccess({ products })), catchError(error => of(loadProductsFailure({ error }))) ) ) ) ); }
Step 4: Register the Effect
Register the effect in your AppModule
:
import { NgModule } from '@angular/core'; import { EffectsModule } from '@ngrx/effects'; import { ProductEffects } from './effects/product.effects'; @NgModule({ imports: [ EffectsModule.forRoot([ProductEffects]) ] }) export class AppModule {}
Conclusion
NgRx Effects is a powerful tool for handling side effects in your Angular application. By isolating side effects from your components and state management logic, you can create more predictable and testable applications. In this section, you learned how to set up NgRx Effects, create effects, and handle side effects in a practical example. You also completed an exercise to reinforce your understanding of NgRx Effects. In the next module, we will explore state management in more detail.
Angular 2+ Course
Module 1: Introduction to Angular
Module 2: TypeScript Basics
- Introduction to TypeScript
- TypeScript Variables and Data Types
- Functions and Arrow Functions
- Classes and Interfaces