What is @ngrx/effects?
@ngrx/effects is a library for managing side effects in Angular applications using the Redux pattern. It allows you to isolate side effects from your components and services, making your code more predictable and easier to test.
What are @ngrx/effects's main functionalities?
Handling Side Effects
This code demonstrates how to handle side effects using @ngrx/effects. The `loadItems$` effect listens for the `loadItems` action, calls a service to fetch data, and then dispatches either a success or failure action based on the result.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { MyService } from './my-service';
import * as MyActions from './my-actions';
@Injectable()
export class MyEffects {
loadItems$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.loadItems),
mergeMap(() => this.myService.getAll().pipe(
map(items => MyActions.loadItemsSuccess({ items })),
catchError(() => of(MyActions.loadItemsFailure()))
))
));
constructor(
private actions$: Actions,
private myService: MyService
) {}
}
Combining Multiple Effects
This code demonstrates how to combine multiple effects in a single class. The `loadItems$` effect handles loading items, while the `updateItem$` effect handles updating an item. Both effects listen for their respective actions and call the appropriate service methods.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { MyService } from './my-service';
import * as MyActions from './my-actions';
@Injectable()
export class MyEffects {
loadItems$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.loadItems),
mergeMap(() => this.myService.getAll().pipe(
map(items => MyActions.loadItemsSuccess({ items })),
catchError(() => of(MyActions.loadItemsFailure()))
))
));
updateItem$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.updateItem),
mergeMap(action => this.myService.update(action.item).pipe(
map(() => MyActions.updateItemSuccess({ item: action.item })),
catchError(() => of(MyActions.updateItemFailure()))
))
));
constructor(
private actions$: Actions,
private myService: MyService
) {}
}
Error Handling
This code demonstrates how to handle errors in effects. The `loadItems$` effect catches any errors that occur during the service call and dispatches a `loadItemsFailure` action with the error information.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { MyService } from './my-service';
import * as MyActions from './my-actions';
@Injectable()
export class MyEffects {
loadItems$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.loadItems),
mergeMap(() => this.myService.getAll().pipe(
map(items => MyActions.loadItemsSuccess({ items })),
catchError(error => of(MyActions.loadItemsFailure({ error })))
))
));
constructor(
private actions$: Actions,
private myService: MyService
) {}
}
Other packages similar to @ngrx/effects
redux-saga
redux-saga is a library that aims to make side effects (e.g., asynchronous actions like data fetching) easier and more manageable in Redux applications. It uses generator functions to handle side effects, which can make the code more readable and easier to test compared to @ngrx/effects.
redux-thunk
redux-thunk is a middleware that allows you to write action creators that return a function instead of an action. This function can then perform asynchronous dispatches. While simpler than @ngrx/effects, it can become harder to manage as the complexity of side effects grows.
mobx
MobX is a state management library that makes it simple to connect the reactive data of your application with the UI. Unlike @ngrx/effects, MobX uses observables to manage state and side effects, which can be more intuitive for some developers.
@ngrx/effects
Side effect model for @ngrx/store
Installation
To install @ngrx/effects from npm:
npm install @ngrx/effects --save
Example Application
https://github.com/ngrx/example-app
Effects
In @ngrx/effects, effects are sources of actions. You use the @Effect()
decorator to hint which observables on a service are action sources, and @ngrx/effects automatically merges your action streams letting you subscribe them to store.
To help you compose new action sources, @ngrx/effects exports an Actions
observable service that emits every action dispatched in your application.
Example
- Create an AuthEffects service that describes a source of login actions:
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { Actions, Effect } from '@ngrx/effects';
@Injectable()
export class AuthEffects implements OnDestroy {
constructor(
private http: Http,
private actions$: Actions
) { }
@Effect() login$ = this.actions$
.ofType('LOGIN')
.map(action => JSON.stringify(action.payload))
.switchMap(payload => this.http.post('/auth', payload)
.map(res => ({ type: 'LOGIN_SUCCESS', payload: res.json() }))
.catch(() => Observable.of({ type: 'LOGIN_FAILED' }));
);
}
- Provide your service via
EffectsModule.run
to automatically start your effect:
import { AuthEffects } from './effects/auth';
import { EffectsModule } from '@ngrx/effects';
@NgModule({
imports: [
EffectsModule.run(AuthEffects)
]
})
export class AppModule { }
Note: For effects that depend on the application to be bootstrapped (i.e. effects that depend on the Router) use EffectsModule.runAfterBootstrap
. Be aware that runAfterBootstrap
will only work in the root module.
Testing Effects
WIP