NgRx can be tricky

Starting with Angular and NgRx Store

Alain Boudard

--

Update: the source code is updated in Angular 13. In this version, there is no -c option available, since the creators ar used by default.

Attention : This article is updated with NgRx version 8 and higher and as much as possible with “new” syntax for creators like createAction and createReducer. The option is listed as : -c.

When you want to work with NgRx for your Angular application, you have many resources, to say the least. My attempt here is to go from scratch to a decent state of your App that could look like a production-type one.

I don’t want to bring tons of code here, I want to be as simple and demonstrative as possible. Also, we want to use schematics as much as possible, and see what benefits they bring. Don’t forget to use a lot of “dry-run” option with your commands, that really helps !

ng g c something --dry-run

I won’t cover the basics of NgRx, core concepts and stuff like that, I assume you, reading this, start having a decent idea of what we are talking about.

The NgRx.io website is quite new as I’m writing these lines, and provides a cleaner documentation than the good old github pages.

Of course you have many informations on how to start on the official website, I will simply try to fill the gaps that could slow you down in the process.

Let’s Schematics NgRx !

Well no, let’s install first ! Ok, I assume you already have an Angular project right ?

npm install @ngrx/schematics --save-devnpm install @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools --save

We have all important libs and the very important devtools. Run your app and we can start to work on our Store.

As stated in the official doc, use schematics for the default ng commands :

// deprecated
ng config cli.defaultCollection @ngrx/schematics
use instead
ng add @ngrx/schematics
the result is in angular.json:
"cli": {
"schematicCollections": [
"@ngrx/schematics"
]
}

Generate the initial Store in a global AppState (yeah, remove the dry-run option) :

ng generate @ngrx/schematics:store State --root --module app.module.ts --dry-run

Now you have the Store initialized ! It’s empty but still with no line of code that’s nice, see in the devtools tab :

Redux Devtools Chrome extension

Check the Redux extension here : https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en

Note that I will not touch the app.module file, it’s already configured to handle a State that will handle sub states, that’s convenient compared to a solution like the one described here : https://ngrx.io/guide/store where you need to give a name in the StoreModule.forRoot method. What we have instead is a metaReducers :

StoreModule.forRoot(reducers, { metaReducers }),

https://netbasal.com/implementing-a-meta-reducer-in-ngrx-store-4379d7e1020a on what are meta reducers. And https://ngrx.io/guide/store/metareducers for updated official doc.

Generate a simple Action, with the -c option that tells schematics to use the latest syntax (refer to the v13 update where this option is no longer available), I will use the simple counter example of the official doc with increment and decrement actions, https://ngrx.io/guide/store :

ng g action actions/Counter --dry-run
Create 2 simple actions with no parameter

With the v13 of NgRx, you can use the createActionGroup as follows. Be careful, with this syntax, the action will be incrementCounterand not increment :

Ok, now, this is where we want to generate a reducer that will be part of our global metaReducer :

ng g reducer reducers/Counter --reducers index.ts --skip-tests --dry-run

This is what you should have now in the reducers folder :

And in your devtools, you now have a counter sub-state object :

Ok, this is it for the schematics, feel free to organise your folders as you want, why not a root/state folder to put folders like actions, reducers and following selectors.

Populating the Store

Let’s refactor the counter state, maybe change their names since they all are “State”, I will use AppState for the global one and CounterState … and so on.

Initial names of States after schematics (pre version 8)
New names altered by hand for States interfaces with dynamic syntax for the key

CounterState will handle a simple number attribute, let’s populate the reducer (I know increment is not supposed to add 2) :

export interface CounterState {
count: number;
}
export const initialState: CounterState = {
count: 0
};
const counterReducer = createReducer(
initialState,
on(increment, state => ({ ...state, count: state.count + 2 })),
on(decrement, state => ({ ...state, count: state.count - 1 }))
);
export function reducer(state: CounterState | undefined, action: Action) {
return counterReducer(state, action);
}

Note that the original switch case is replaced with ngrx on() method.

Now the initial state is displayed in the devtools, default value for count attribute is 0, perfect :

Call the Store with dispatch Actions

We can add some buttons in our component and see in the devtools if it works — again, note the version 8 syntax with simple functions calls :

// app.component.html
<div><button (click)="decrement()">decrement</button></div>
<div><button (click)="increment()">increment</button></div>
// app.component.ts
constructor(private store: Store<AppState>) {
// futur selector here
}
decrement(): void {
this.store.dispatch(decrement());
}
increment(): void {
this.store.dispatch(increment());
}
ngOnInit(): void {
this.increment();
}

Now, I see the init Action, I see the onInit hook method of my component, and I can click and call a third one, and see how the State evolves :

Display Store data with NgRx selector

Now the last part : display data by calling selectors. Unfortunately, selectors don’t come with a schematic (it’s a feature request somewhere, at nrwl I think … anyway). Here is the official doc : https://ngrx.io/guide/store/selectors. I prefer a dedicated sub folder like /selectors/counter.selector.ts :

export const selectCounterState = (state: AppState) => state.counter;
export const getCount = createSelector(
selectCounterState,
counter => counter.count
);

Okay, now we just need to call the selector and display some data with an async pipe since it’s the best way to handle Observables in the view (refer to articles about auto unsubscribe your Observables in Angular). See how to use “else” and aliases, very convenient.

// app.component.html
<div>
whatever you want
<ng-container *ngIf="(count$ | async) as num">
<span [innerText]="num" id="num"></span>
</ng-container>
</div>
// app.component.ts
count$: Observable<number>;
constructor(private store: Store<AppState>) {
this.count$ = this.store.pipe(select(getCount));
}

And voilà, the value of the counter is displayed ! Keep in mind that this is a very simple situation, you might have to handle a situation where you will combine selectors, deal with empty inital values etc …

Another useful quick schematics : container

With a simple command, scaffold a component that imports your desired State interface from the store, and inject the mandatory tools in the spec file for the unit tests :

ng g co hello --state reducers/index.ts --stateInterface AppState

One last thing, we want to make the unit test pass, since we have a unique component, that shouldn’t be too complicated.

Unit Test Angular component with NgRx Store

Ok, if your app was made from scratch, the generated component spec file is not compliant with the test example in the official NgRx doc : https://ngrx.io/guide/store/testing. You need to refactor a little bit to instantiate the component and fixture in the beforeEach hook method.

Also, we don’t have a feature Module, so the app.component.spec.ts file will be like this :

import { TestBed, waitForAsync } from "@angular/core/testing";
import { MockStore, provideMockStore } from "@ngrx/store/testing";
import * as counterActions from "./actions/counter.actions";
import { AppComponent } from "./app.component";
import * as fromRoot from "./reducers";
import { ObserverSpy, subscribeSpyTo } from "@hirez_io/observer-spy";
describe("AppComponent", () => {let component: AppComponent;
let store: MockStore<fromRoot.AppState>;
let storeSpy: ObserverSpy<any>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
providers: [AppComponent, provideMockStore()],
});
store = TestBed.inject(MockStore);
storeSpy = subscribeSpyTo(store.scannedActions$);
component = TestBed.inject(AppComponent);
}));
[...]it("should dispatch an action onInit - method 1", () => {
const action = counterActions.increment();
component.ngOnInit();
expect(storeSpy.getLastValue()).toEqual(action);
});
it("should dispatch an action onInit - method 2", () => {
const spyStore = spyOn(store, "dispatch").and.callThrough();
const action = counterActions.increment();
component.ngOnInit();
expect(spyStore).toHaveBeenCalledWith(action);
});
});

Run the usual

"test-headless:watch": "ng test --watch=true --browsers=ChromeHeadless"

This is it, I hope this can be of any help, You see now that adding another sub state will be easy, and this way you won’t confuse the different parts of your store. You also see that you can plug a feature Module with its own State without touching the app Module, exploring the options of the “feature” shematic.

Don’t forget to add Effects and you’re good to go !

Stackblitz starter project with Angular and NgRx Store

--

--