NgRx Ducks | Dynamic Facades improving coding experience

Gregor Woiwode
Mar 20 · 6 min read
Photo by Banter Snaps on Unsplash, Logo by Sascha Nuissl

While chatting with colleagues from different companies I have learned that a lot of developer teams ❤️ NgRx. During the discussions, one wish meets me again and again.

🗣 “It would be great if we had less maintenance but more guidance to write code that scales better.”

This motivated me to learn more about different refactoring techniques regarding Redux. I went through videos, articles and the Redux docs. I learned a lot from studying these sources. Finally, I came up with a plug-in for NgRx called NgRx Ducks. 🎉

The goals

Basically, ngrx-ducks is built to simplify the work with NgRx.

  1. ⚡️ Simpler Action handling through a Decorator API that reduces the amount of code you have to write to set up both reducer and action creator. Yes, NgRx Ducks generates action creators and reducer functions automatically.
  2. 👷‍♂️ Less maintenance work since you do not have to enhance enums or union types.
  3. 👓 More clarity enabled by an intuitive, typed API that you can use as an injectable Service that set up the interaction with the Store for you.

How does it work?

This library acts as a thin layer on top of NgRx. It automatically creates action creators and reducer functions for you. Furthermore, you get a service that you can use in your components. This Service provides a strictly typed API allowing both dispatching actions and selecting data from the Store.

The NgRx Ducks library does not modify the existing behaviour of NgRx. It only instruments the Observable provided by the Store and the Store’s method dispatch.

Since NgRx Ducks only depends on dispatch and the fact that the Store exposes an Observable it is pretty safe that NgRx Ducks remains compatible with NgRx even if a release contains breaking changes. 🙏

Set up

NgRx Ducks integrates seamlessly with your existing NgRx project. You only need to install the npm package and you are ready to go.

npm install @co-it/ngrx-ducks# oryarn add @co-it/ngrx-ducks

Decorator API

NgRx Ducks’ core concept is to bring action types and mutation logic (also called case reducers) back together.

Therefore a decorator is introduced called @Ducksify that annotates a simple class.

import { Ducksify } from '@co-it/ngrx-ducks';@Ducksify<number>({
initialState: 0
})

export class Counter {}

The decorator allows you to specify the initialState of your state slice. Passing the initialState allows NgRx Ducks to automatically generate the reducer function later on.

Now you place the mutation logic right inside the class. There is no need to write switch-case-Statements anymore. Instead of this, we create a mapping between action type and mutation logic using the decorator @Action.

import { Action, Ducksify } from '@co-it/ngrx-ducks';@Ducksify<number>({ /* ... */ })
export class Counter {
@Action('[Counter] Increase by passed value')
increaseBy(state: number, payload: number): number {
return state + payload;
}
}

NgRx Ducks maps each Action to the corresponding mutation logic. A Duck contains all information that is needed to generate the reducer function.

The reducer function

Reducer functions are generated automatically by NgRx Ducks. The factory reducerFrom creates a look-up table to match each action type with the corresponding mutation logic.

import { 
reducerFrom,
DucksifiedAction,
...
} from '@co-it/ngrx-ducks';
export function reducer(
state: number,
action: DucksifiedAction): number {
return reducerFrom(Counter)(state, action);
}

⚠️ You still need to wrap reducerFrom inside an exported function in order to work correctly with AoT compiler.

ℹ️ The generated reducer needs to be added to the provided ActionReducerMap by NgRx (see sample below).

The real power of the Ducks 💪

The best is yet to come! NgRx Ducks goal is to simplify the whole interaction with the Store. Generating a reducer function automatically is fine and now we will have a look at the dynamic facade that is created, too.

The @Ducksify decorator also takes care about registering your “Duck” as Service in Angular’s IoC Container. This means you can inject your Duck in your component!

Dispatching Actions to the Store

NgRx Ducks adds some patches to the Duck which allow you to use plain function calls instead of dispatching actions manually. You will get a typed API inside your components.

import { Duck } from '@co-it/ngrx-ducks';@Component({ /* ... */ })
export class CounterComponent {
constructor(@Inject(Counter) private _counter: Duck<Counter>) {
this.counter.incrementBy(42);
}
}

You will inject Duck<Counter> but not the Counter itself. The Duck automatically sets up an action creator for incrementBy that dispatches the action with the passed payload. Thanks to TypeScript’s dynamic types you will get nice auto-completion right away as you type.

In short NgRx Ducks automates the whole process handling Actions. You configure an action one time in your Duck and after it, you just use a typed, dynamic facade generated & updated automatically for you.

Selecting Data from the Store

A Duck instruments NgRx’s selectors to read data from the store. Each Duck offers a helper called 🍒 pick that accepts a selector.

Let’s imagine our Counter is registered as NgRx feature with the key `’counter'`. This would allow us the following selector setup.

import { createFeatureSelector, createSelector} from '@ngrx/store';const visitCounter = createFeatureSelector<number>('counter');
const count = createFeatureSelector<number>(count => count);
@Component({ /* ... */ })
export class CounterComponent {
count$: Observable<number>;
constructor(@Inject(Counter) private _counter: Duck<Counter>) {
this.count$ = this.counter.pick(count);
this.counter.incrementBy(42);
}
}

You can have a look at the full demo hosted at ⚡️ stackblitz.io.

A Duck offers both triggering state mutations and querying data from the Store. You only need one “ducksified” Service that sets up a comfortable API to interact with the Store. 🐥

One last thing

NgRx Ducks also seamlessly integrates with ☁️ Effects!

We will discuss this in detail in the next article.

🏃‍♂️ If you want to learn about it right now, you can refer to the complex example that is also available at ⚡️ stackblitz.io.

Recapitulation

  • NgRx Ducks acts as a thin layer on top of NgRx.
  • You can seamlessly integrate NgRx Ducks in existing projects.
  • A Duck 🐥
  • … automatically generates action creators & reducer functions.
  • … makes action enums and action union types unnecessary.
  • … is a dynamic facade that can be injected in a Component.
  • … provides self-dispatching actions that are dynamically typed
  • … allows reading data from the Store using the 🍒pick-API.
  • … can be used by Effects (see Demo)

That’s it ✨

Hopefully, I was able to convince you to give NgRx Ducks a try. Furthermore, I am interested in what you think about this library. If you have any ideas you just need to file an issue on GitHub. 👍

Rock On And Code
Gregor

Don’t hesitate to ping me if you have any thoughts or questions about this topic! You can find me at Twitter → @GregOnNet.

Related articles

The next article discusses how NgRx Ducks improve writing @Effect-Services.

Thomas Burleson wrote an article about how facades can lead to better state management with NgRx. Refer to this article if you want to learn about the facade pattern itself.

Gregor Woiwode

Written by

Gregor loves to build tools 🛠 that enable developers to be more productive. He also enjoys 🏃‍♂️ or trying his hand at hobby cooking 👨‍🍳.