Quick Start

Show me the code

You find a demo using ngrx-ducks at ngrx-ducks-12. More explanations in another format here Api Discussion

Start with Ducks

Follow this document up along with this demo ngrx-ducks-12. There you will find the entire code that is shown in this article.💎

Implement State Mutations

First create a file where the mutation logic for a certain state slice should live. Put selectors logic here as well.

Add a class that implements the needed Case Reducers, builds required selectors and triggers actions.

createDuck supports to create both triggering actions (for Effects) and mutating actions. Second parameter of createDuck is a Case Reducer. It has a similar style of creating a reducer which actually makes state mutations.

import { createDuck } from '@ngrx-ducks/core';

@StoreFacade()
export class CounterFacade {
  counter = bindSelectors({ count: selectors.currentCount });
  
  loadCount = createDuck('[Counter] Set initial value',
    (state: CounterState, payload: number) => {
      return { ...state, count: payload };
  });
}

The decorator StoreFacade introduces a new mechanism how the Facade is connected with the Store.🚀

Create Reducer automatically

If you use Ducks there is no need to maintain switch-case statements. Now you can create the Reducer based on the Ducks you have created inside the Facade class.

import { createDuck, getReducer, StoreFacade } from '@ngrx-ducks/core';

@StoreFacade()
export class CounterFacade {
  duck1 = createDuck('Duck 1', /* reducer function */);
  duck2 = createDuck('Duck 2', /* reducer function */);
  duck3 = createDuck('Duck 3', /* reducer function */);
}

// declare initial state and export counterReducer
const initialState = { count: 0 };
export const counterReducer = getReducer(initialState, CounterFacade);

Just created counterReducer is a variable which can be used in ngrx method combineReducers doc. From that point our implementation of a reducer is finished. Example below shows us basic implementation.

import { Action, combineReducers, MetaReducer } from '@ngrx/store';
import { CounterState, counterReducer } from './counter';

export interface State {
  simple: CounterState;
}

export function reducers(state: State, action: Action) {
  return combineReducers<State>({
    simple: counterReducer
  })(state, action);
}

export const metaReducers: MetaReducer<State>[] = [];

Inject the StoreFacade Service

To use your Facade you need to inject it into your component.

@Component({/* ... */})
export class CounterComponent {
  constructor(private counter: CounterFacade) {}
}

Since the whole logic is implemented in a Facade (CounterFacade in our case) we only need to import that Facade.

🎉 The API allows you to create dynamic facades and your components do not even know that Redux is working behind the scenes. We only rely on the contracts that we use messaging and streams.

👓Thanks to TypeScript each method is strictly typed and the whole process of creating or dispatching Actions is transparent.

@Component({/* ... */})
export class CounterComponent implements OnInit {
  /* ... */

  ngOnInit() {
    this.counter.loadCount.dispatch(5000);
    //               ^ only type number is allowed 🎉
    //               - dispatches action: type: '[Counter] Set initial value'
    //                                    payload: 5000
  }
}

Use NgRx's selectors

Creating selectors is the same way as it's in NgRx.

import { bindSelectors, StoreFacade } from '@ngrx-ducks/core';

import * as selectors from './counter.selectors';

@StoreFacade()
export class CounterFacade {
  select = bindSelectors(selectors);
}

What if I don't want to dispatch the action directly?

No worries, ngrx-ducks gets you covered in this question. Each dispatching method provides an Action Creator.

  ngOnInit() {
    // Yields Action
    this.counter.loadCount(5000);
    // {
    //   type: '[Counter] Set initial value',
    //   payload: 5000
    // }

    // Dispatches an action
    this.counter.loadCount.dispatch(5000);

  }

You see .loadCount(5000); is a method as well. 🤯 This addition is very useful if you need to produce actions being returned by an Effect.

Effects

You may wonder if @ngrx-ducks can help you with actions being dispatched to an Effect. Short answer: Yes, it can.

Setup

Let's use createDuck again! createDuck is the only entry point you need to know to create and dispatch actions. It works with calling Effects as well. It works with Effects just like actions work with Effects in ngrx.

import { createDuck } from '@ngrx-ducks/core';

@StoreFacade()
export class CounterFacade {

  readonly loadCount = createDuck('[Counter] Load Count', dispatch<number>());

  // another action (called in Effect below)
  override = createDuck('[Counter] Set value',
    (state: CounterState, payload: number) => {
      return { ...state, count: payload };
    }
  );
}

export const counterActions = getActions(CounterFacade);

Triggering an Effect

import { currentCount } from './counter.selectors';

@Component({
  /* ... */
})
export class CounterComponent {
  counter$: Observable<number>;

  constructor(private counter: CounterFacade) {
    // let's dispatch an action which is handled by an Effect
    this.counter.loadCount.dispatch(10);
  }
}

Inside Effects

The last question is how an Effect itself handles actions with ngrx-ducks. An Effect filters the action type first. Let NgRx do it!😁

import { Actions, createEffect, ofType } from '@ngrx/effects';
import { counterActions } from './counter.facade';

@Injectable()
export class CounterEffects {
  setCounter = createEffect(() => this.actions$.pipe(
    ofType(counterActions.loadCount),
    delay(2000),
    map(({ payload }) => counterActions.override(payload))
  ));

  constructor(private actions$: Actions) {}
}

As mentioned before, you can make use of the generated, typed action creators to create an action that is handled by the Effect.

You see the only API you have to work with is the Ducks-API. It does not change anything in [ngrx] but automates the process dealing with actions.

Last updated