NgRx Ducks | Handle side effects with ease

Hello again 👋, in the previous article we discussed the basic features of NgRx Ducks. If you haven’t read it yet you may want to check it out.

Show me the code!

You will find the code discussed in this article at ⚡️ StackBlitz.

The benefit of @Effects in general

I love how NgRx encapsulates side effects. It automatically makes you separate the responsibilities of your Angular application handling different state concerns. Of cause, you need to maintain @Effect-Services and local UI logic in multiple files. I found out that this little “trade-off” helps to find bugs faster than doing service composition.

Improve Effects with NgRx Ducks

As I started the development of NgRx Ducks I also thought of a way to simplify the usage of an Effect without doing an own implementation that would introduce a new API. Since a Duck contains all information about actions I decided to expose an API that can do both filtering actions and trigger asynchronous operations in the Effect-Service.

Filter actions

NgRx provides a stream containing all actions that are dispatched. You can listen to that stream to react to certain actions. The operator ofType allows filtering the actions. You need to pass at least the action type to filter an action. Since version 7 of NgRx, you can automatically infer the type of the payload as well by providing the action union type in the constructor of the Effect-Service.

Comparison filtering Actions: NgRx 6 vs NgRx 7

Since a Duck gets rid of the action-union-type as well as the action-type-enum it provides it’s own API to handle actions in Effects. Instead of using the ofType operator NgRx Ducks introduces whereType.

The operator expects a member of the Duck-Service. It automatically extracts the action-type to filter the stream of actions.

import { Inject } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { tap } from 'rxjs/operators';
import { Duck, whereType } from '@co-it/ngrx-ducks';
import { Counter } from './counter.duck';
export class CounterEffects {
@Effect({ dispatch: false })
logIncrement$ = this._actions$.pipe(
whereType(this._counter.incrementBy),
tap(({type, payload}) =>
console.log(`'${type}' dispatched: `,payload)
)
);
constructor(
private _actions$: Actions,
@Inject(Counter) private _counter: Duck<Counter>) { }
}

The Effect that is shown above logs a message to the console each time when an action is dispatched calling incrementBy.

ℹ️ Like said in the previous article NgRx Ducks handles the whole infrastructure of Actions. That’s why a Duck is your single point of contact interacting with Actions or the Store.

Filter collection of actions

The operator whereType accepts both a single action-dispatcher or a collection containing multiple action dispatchers.

@Effect({ dispatch: false })
logIncrement$ = this._actions$.pipe(
whereType([this._counter.incrementBy, this._counter.decrementBy]),
tap(({type, payload}) =>
console.log(`'${type}' dispatched: `,payload)
)
);

🙋‍♀️ Type Inference for a single action

The payload of each action is inferred automatically.

I cannot thank enough the TypeScript team for introducing Dynamic Types that make this possible. 🙏
Inferring the type of action payload automatically

👨‍👩‍👧‍👧 Type Inference for a collection of actions

If you filter a collection of actions having different payloads TypeScript will automatically infer the corresponding union-type. ❤️

Inferring the type of action payloads coming from different action dispatchers

In other words, if one action has a payload of type number and another has a payload of type string, the following type the action is passed to the next operator.

{
type: string;
payload: string|number;
}

A Duck plays well with an Effect when it comes to filtering the stream of actions. Now, let’s see how a Duck can be used to trigger an Effect aka asynchronous operation.

Trigger an asynchronous operation

Sometimes you need an action to start an asynchronous operation without transforming the state directly. NgRx Ducks treats this as a special case.

You are able to add properties to a Duck that trigger Effects.

import { Action, Ducksify, effect } from '@co-it/ngrx-ducks';
@Ducksify<number>({
initialState: 0
})
export class Counter {
incrementCounter = effect<number>('[Counter] Set value');

@Action('[Counter] Increment value')
incrementBy(state: number, payload: number): number {
return state + payload;
}
// ...
}

The helper effect<TPayload> generates a self-dispatching action. If you want to transport a payload you need to specify the generic type parameter TPayload. Otherwise, you are free to omit the parameter.

Now you are able to dispatch an action using the “effect dispatcher”.

export class CounterComponent {
// ...
constructor(@Inject(Counter) private _counter: Duck<Counter>) {
this._counter.incrementCounter.dispatch(42);
// ...
}
}

Back to the @Effect-Service, you will notice that you also can use whereType to handle our dispatched action.

@Effect()
setCounter$ = this._actions$.pipe(
whereType(this._counter.incrementCounter),
map(({ payload }) => /* */ )
);

Now, only one last bit is missing to master @Effects with NgRx Ducks. How do you create actions in an Effect to mutate state in your Store?

🛠 Let’s have a look at action creators!

Use pure action creators

Every self-dispatching action produced by NgRx Ducks provides an action creator that you can use if you only need the pure plain action object.

Therefore you simply need to call action on the respective member of the Duck. The following example shows how the payload from incrementCounter is taken to produce a new action that is dispatched to the Store to increment the value of our counter.

@Effect()
setCounter$ = this._actions$.pipe(
whereType(this._counter.incrementCounter),
map(({ payload }) => this._counter.incrementBy.action(payload))
);
🎛 NgRx Ducks still gives you full control dealing with actions.

Comprehension question 🤔

Why do we need to call action? Well, this._counter.incrementBy(payload) would immediately dispatch an action to the Store. This breaks the flow of the Effect. That’s why action comes into play giving you the plain action object containing the type and the given payload.

Recapitulation

  • NgRx Ducks provides seamless integration with NgRx’s Effects.
  • Instead of using the operator ofType, whereType is used.
  • whereType automatically infers the payload of an action. 🎉
  • effect<TPayload> helps you to trigger asynchronous operations.
  • .action() is attached to each self-dispatching action allowing the creation of a plain action object.

💻 You will find the code discussed in this article at ⚡️ StackBlitz.

That’s it ✨

Thanks for reading the second article about NgRx Ducks. I hope you enjoyed it. If you have any further questions please leave me a comment below.

You also can write me a message on Twitter: @GregOnNet

Rock On And Code
Gregor

Related Articles