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.
8.0.0-beta.0 (2019-04-17)
Bug Fixes
- effects: add the export of EffectMetadata (#1720) (214316f)
- example: handle possible undefined results from Dictionary (#1745) (861b0cb), closes #1735
- schematics: check for empty name when using store schematic for feature states (#1659) (#1666) (3b9b890)
- store: add the missing bracket in immutability meta-reducer (#1721) (56f8a59)
- Store: selector with only a projector (#1579) (da1ec80), closes #1558
- StoreDevTools: rename action list filters (#1589) (5581826), closes #1557
Code Refactoring
Features
BREAKING CHANGES
- entity: Dictionary could be producing undefined but previous typings were not explicit about it.
- Store: Internal functions and tokens are removed from the public API
- router-store: usage of forRoot is now required for StoreRouterConnectingModule
BEFORE:
@NgModule({
imports: [StoreRouterConnectingModule],
})
export class AppModule {}
AFTER:
@NgModule({
imports: [StoreRouterConnectingModule.forRoot()],
})
export class AppModule {}
- Store: Selectors with only a projector function aren't valid anymore.
This change will make the usage more consistent.
BEFORE:
const getTodosById = createSelector((state: TodoAppSchema, id: number) => state.todos.find((p) => p.id === id));
AFTER:
const getTodosById = createSelector(
(state: TodoAppSchema) => state.todos,
(todos: Todo[], id: number) => todos.find((p) => p.id === id)
);
- StoreDevTools:
actionsWhitelist
is renamed to actionsSafelist
actionsBlacklist
is renamed to actionsBlocklist
BEFORE:
StoreDevtoolsModule.instrument({
actionsWhitelist: ['...'],
actionsBlacklist: ['...'],
});
AFTER:
StoreDevtoolsModule.instrument({
actionsSafelist: ['...'],
actionsBlocklist: ['...'],
});
<a name="7.4.0"></a>