Introducció
NgRx Effects és una biblioteca que permet gestionar efectes secundaris en aplicacions Angular utilitzant el patró Redux. Els efectes són operacions que es desencadenen en resposta a accions i poden incloure operacions asíncrones com sol·licituds HTTP, accés a l'emmagatzematge local, navegació, etc. NgRx Effects ajuda a mantenir la lògica d'efectes secundaris fora dels reducers, mantenint-los purs i fàcils de provar.
Objectius
- Comprendre què són els efectes en NgRx.
- Aprendre a crear i gestionar efectes.
- Integrar efectes amb el flux d'accions i reducers.
- Veure exemples pràctics d'ús d'efectes.
Conceptes Clau
- Efectes: Són classes que contenen lògica per gestionar efectes secundaris.
- Actions: Les accions que desencadenen els efectes.
- Effects Decorator: Decorador
@Effect
per marcar observables com efectes. - Effects Module: Mòdul que registra els efectes en l'aplicació.
Creació d'Efectes
Instal·lació de NgRx Effects
Abans de començar, assegura't d'haver instal·lat NgRx Effects en el teu projecte Angular:
Configuració del Mòdul d'Efectes
Afegeix EffectsModule
al mòdul principal de la teva aplicació (app.module.ts
):
import { EffectsModule } from '@ngrx/effects'; import { MyEffects } from './effects/my-effects'; @NgModule({ imports: [ // altres imports EffectsModule.forRoot([MyEffects]) ], // altres configuracions }) export class AppModule { }
Creació d'una Classe d'Efectes
Crea una classe d'efectes (my-effects.ts
) per gestionar els efectes secundaris:
import { Injectable } from '@angular/core'; import { Actions, ofType, createEffect } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { MyService } from '../services/my-service'; import * as MyActions from '../actions/my-actions'; @Injectable() export class MyEffects { constructor( private actions$: Actions, private myService: MyService ) {} loadItems$ = createEffect(() => this.actions$.pipe( ofType(MyActions.loadItems), mergeMap(() => this.myService.getItems() .pipe( map(items => MyActions.loadItemsSuccess({ items })), catchError(error => of(MyActions.loadItemsFailure({ error }))) )) ) ); }
Explicació del Codi
- Actions: Flux d'accions que es poden escoltar.
- ofType: Filtra les accions per tipus.
- createEffect: Crea un efecte.
- mergeMap: Permet combinar múltiples observables.
- map: Transforma les dades emeses per l'observable.
- catchError: Gestiona errors i retorna una nova acció.
Exemple Pràctic
Suposem que tenim un servei que obté una llista d'articles des d'un servidor:
// my-service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class MyService { constructor(private http: HttpClient) {} getItems(): Observable<Item[]> { return this.http.get<Item[]>('https://api.example.com/items'); } }
I les accions corresponents:
// my-actions.ts import { createAction, props } from '@ngrx/store'; export const loadItems = createAction('[Items] Load Items'); export const loadItemsSuccess = createAction('[Items] Load Items Success', props<{ items: Item[] }>()); export const loadItemsFailure = createAction('[Items] Load Items Failure', props<{ error: any }>());
Exercici Pràctic
Objectiu
Crear un efecte que carregui una llista d'usuaris des d'un servei i gestioni els casos d'èxit i error.
Passos
- Crear el servei (
user.service.ts
):
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { User } from '../models/user.model'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) {} getUsers(): Observable<User[]> { return this.http.get<User[]>('https://api.example.com/users'); } }
- Definir les accions (
user.actions.ts
):
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[] }>()); export const loadUsersFailure = createAction('[User] Load Users Failure', props<{ error: any }>());
- Crear l'efecte (
user.effects.ts
):
import { Injectable } from '@angular/core'; import { Actions, ofType, createEffect } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { UserService } from '../services/user.service'; import * as UserActions from '../actions/user.actions'; @Injectable() export class UserEffects { constructor( private actions$: Actions, private userService: UserService ) {} loadUsers$ = createEffect(() => this.actions$.pipe( ofType(UserActions.loadUsers), mergeMap(() => this.userService.getUsers() .pipe( map(users => UserActions.loadUsersSuccess({ users })), catchError(error => of(UserActions.loadUsersFailure({ error }))) )) ) ); }
- Registrar l'efecte (
app.module.ts
):
import { EffectsModule } from '@ngrx/effects'; import { UserEffects } from './effects/user.effects'; @NgModule({ imports: [ // altres imports EffectsModule.forRoot([UserEffects]) ], // altres configuracions }) export class AppModule { }
Solució
// user.service.ts import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { User } from '../models/user.model'; @Injectable({ providedIn: 'root' }) export class UserService { constructor(private http: HttpClient) {} getUsers(): Observable<User[]> { return this.http.get<User[]>('https://api.example.com/users'); } } // user.actions.ts 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[] }>()); export const loadUsersFailure = createAction('[User] Load Users Failure', props<{ error: any }>()); // user.effects.ts import { Injectable } from '@angular/core'; import { Actions, ofType, createEffect } from '@ngrx/effects'; import { of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { UserService } from '../services/user.service'; import * as UserActions from '../actions/user.actions'; @Injectable() export class UserEffects { constructor( private actions$: Actions, private userService: UserService ) {} loadUsers$ = createEffect(() => this.actions$.pipe( ofType(UserActions.loadUsers), mergeMap(() => this.userService.getUsers() .pipe( map(users => UserActions.loadUsersSuccess({ users })), catchError(error => of(UserActions.loadUsersFailure({ error }))) )) ) ); } // app.module.ts import { EffectsModule } from '@ngrx/effects'; import { UserEffects } from './effects/user.effects'; @NgModule({ imports: [ // altres imports EffectsModule.forRoot([UserEffects]) ], // altres configuracions }) export class AppModule { }
Resum
En aquesta secció, hem après què són els efectes en NgRx i com utilitzar-los per gestionar efectes secundaris en aplicacions Angular. Hem vist com crear una classe d'efectes, definir accions i integrar els efectes amb el flux d'accions i reducers. També hem realitzat un exercici pràctic per consolidar els coneixements adquirits. Amb aquests coneixements, estàs preparat per gestionar efectes secundaris de manera eficient en les teves aplicacions Angular utilitzant NgRx Effects.
Curs d'Angular 2+
Mòdul 1: Introducció a Angular
- Què és Angular?
- Configuració de l'entorn de desenvolupament
- La teva primera aplicació Angular
- Arquitectura d'Angular
Mòdul 2: Conceptes bàsics de TypeScript
- Introducció a TypeScript
- Variables i tipus de dades en TypeScript
- Funcions i funcions fletxa
- Classes i interfícies
Mòdul 3: Components i plantilles
Mòdul 4: Directives i pipes
Mòdul 5: Serveis i injecció de dependències
Mòdul 6: Enrutament i navegació
Mòdul 7: Formularis en Angular
Mòdul 8: Client HTTP i observables
- Introducció al client HTTP
- Realització de sol·licituds HTTP
- Gestió de respostes HTTP
- Ús d'observables