10 - NgRx Introduction

1. Presentation

Presentation: NgRx

https://docs.google.com/presentation/d/1MknPww1MdwzreFvDh086TzqaB5yyQga0PaoRR9bgCXs/edit?usp=sharing

2. Install redux dev tools chrome extension

https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=en

3. Simple Counter Demo

a) Make a new empty Angular CLI Project

We will make a simple NgRx demo app first before getting deep into it with Nx which will scaffold out an opinionated abstracted version.

cd workshop
ng new ngrx-counter-demo
cd ngrx-counter-demo
code .

b) Add NgRx to the app

npm install @ngrx/store @ngrx/store-devtools

c) Add a counter.reducer.ts fill to a new state folder in the app

src/app/state/counter.reducer.ts
export function counterReducer(state, action) {
  on('INCREMENT', state => state + 1),
  on('DECREMENT', state => state - 1),
  on(reset, state => 0),
);

d) Register the reducer and NgRx in the App module

src/app/app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { counterReducer } from './state/counter.reducer';
import { StoreDevtoolsModule} from '@ngrx/store-devtools';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    StoreModule.forRoot({ counter: counterReducer }),
    StoreDevtoolsModule.instrument({})
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

e) Add the HTML to the App component

src/app/app.component.html
<button (click)="increment()">Increment</button>
<div>Current Count: {{ count$ | async }}</div>
<button (click)="decrement()">Decrement</button>

f) Add App component logic

Note: changeDetection: ChangeDetectionStrategy.OnPush to avoid issue with change detection.

src/app/app.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  count$: Observable<number>;

  constructor(private store: Store<any>) {
    this.count$ = store.pipe(select('counter'));
  }

  increment() {
    this.store.dispatch({ type: 'INCREMENT' });
  }

  decrement() {
    this.store.dispatch({ type: 'DECREMENT' });
  }

}

g) Adding action creators

At this point we have no strong types for our actions or our state tree, let's fix that.

  • Add a file to the state folder called counter.actions.ts

src/app/state/counter.actions.ts
import { Action } from '@ngrx/store';

export enum CounterActionTypes {
    Increment = '[Counter Page] Increment',
    Decrement = '[Counter Page] Decrement'
}

export const increment = createAction(CounterActionTypes.Increment);

export const decrement = createAction(CounterActionTypes.Decrement);

h) Adding action State interfaces

  • In the top of the reducer file add a CounterState interface and use it as our new action types in the reducer.

src/app/state/counter.reducer.ts
import { CounterActions, CounterActionTypes } from 'src/app/state/counter.actions';

export interface CounterState {
  count: number;
}

export const initialState: CounterState = {
  count: 0
};

export function counterReducer(state = initialState, action: CounterActions) {
  switch (action.type) {
    case CounterActionTypes.Increment:
      return {
        count: state.count + 1
      };

    case CounterActionTypes.Decrement:
      return {
        count: state.count - 1
      };

    default:
      return state;
  }
}

i) Add global State interface

This is useful to be able to type your injected store and select state.

src/app/state/appState.ts
import { CounterState } from 'src/app/state/counter.reducer';

export interface AppState {
    counter: CounterState;
}

j) Use state interface in app component

src/app/app.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { Increment, Decrement } from 'src/app/state/counter.actions';
import { AppState } from 'src/app/state/appState';                 // Added

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
  count$: Observable<number>;

  constructor(private store: Store<AppState>) {                      // Added
    this.count$ = store.pipe(select(state => state.counter.count));  // Added
  }

  increment() {
    this.store.dispatch(new Increment());
  }

  decrement() {
    this.store.dispatch(new Decrement());
  }

}

Last updated