State management in Angular with NgRx

Adrian Fâciu

NgRx

NgRx

  • What is NgRx (and Redux)
  • Store
  • Actions, Reducers, Selectors
  • Effects
  • NgRx Router module
  • Redux dev tools
  • Tests
  • Q&A

NgRx

github.com/

ng-training/

ngrx-workshop

Version 8!

NgRx

Boilerplate

Reactive State for Angular

NgRx

what is NgRx?

+

+

State

  • data from a server
  • URL and router state
  • local UI state

what state?

Redux

Actions

Actions

const incrementAction = { type: 'INCREMENT' }
const addAction = { 
  type: 'ADD',
  value: 4,
}

Actions in NgRx

 

interface Action {
  type: string;
}
import { createAction } from '@ngrx/store';

export const increment = createAction('Increment');

Actions in NgRx

 

import { createAction, props } from '@ngrx/store';

export const add = createAction(
  '[Calculator Page] Add',
  props<{ value: number }>()
);

Reducers

Reducers

export function reducer(
    state = initialState,
    action: Action
) {
    if (action.type === 'Increment') {
        return { count: state.count++ }
    }

    return state;
}

Reducers

export function reducer(
  state = initialState,
  action: Action
) {
  switch (action.type) {
    case 'Increment': {
      return { count: state.count++ }
    }

    case 'Decrement': {
      return { count: state.count-- }
    }

    default: {
      return state;
    }
  }
}

Reducers

import { createAction, createReducer, on }
  from '@ngrx/store';

export const increment = createAction('Increment');
export const decrement = createAction('Decrement');

export const reducer = createReducer(
    initialState,
    on(increment, (state) => ({ count: state.count++ })),
    on(decrement, (state) => ({ count: state.count-- })),
);

Store

Store

  • holds application state
  • send actions through dispatch
  • read state through select

Store

import { StoreModule } from '@ngrx/store';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot({
       user: userReducer,
       count: countReducer,
    }),
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Code practice

Redux dev tools

  • view state object
  • view actions (list)
  • export/import state
  • time travel
  • pause/lock actions
  • many other features

Redux dev tools

import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule }
  from '@ngrx/store-devtools';

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    StoreModule.forRoot(reducers),
    StoreDevtoolsModule.instrument({
      maxAge: 25, 
      logOnly: environment.production, 
    }),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Demo

Selectors

Pure functions, used to get slices of state

Selectors

  • Memoization
  • Composition
  • Type-safe

Selectors

import { createSelector } from '@ngrx/store';

interface AppState {
  user: UserState;
}

interface UserState {
  username?: string;
}

const getUserState = (state: GlobalState) 
  => state.user;

const getUserName = createSelector(
  getUserState,
  state => state.username
);

Code practice

Back to actions

What kind of actions do we have?

Action types:

  • commands
  • documents
  • events

NgRx Effects

NgRx Effects

  • isolate side effects from components
  • listen to an observable of every action
  • perform tasks, which are synchronous or asynchronous

NgRx Effects

  • effects are a class (service)
  • @Effect() decorator
  • createEffect() method

Code practice

Exercise

NgRx Router Store

Router Store

Connects the Angular router to the Store

Router Store

import { StoreRouterConnectingModule, routerReducer }
   from '@ngrx/router-store';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    StoreModule.forRoot({
      router: routerReducer,
    }),
    StoreRouterConnectingModule.forRoot(),
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Code practice

Runtime checks

Runtime checks

Runtime checks

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      runtimeChecks: {
        strictStateImmutability: true,
        strictActionImmutability: true,
        strictStateSerializability: true,
        strictActionSerializability: true,
      },
    }),
  ],
})
export class AppModule {}

Code practice

Schematics

Scaffolding library for Angular applications using NgRx libraries

npm install @ngrx/schematics -D

Schematics

ng generate @ngrx/schematics:store State
  --root --module app.module.ts
ng generate @ngrx/schematics:effect App
  --root --module app.module.ts
ng generate @ngrx/schematics:action user

Schematics

ng config cli.defaultCollection @ngrx/schematics
ng generate action user

Demo

Meta reducers

Meta reducers

export function debug(reducer: ActionReducer<any>){
  return function(state, action) {
    console.log('state', state);
    console.log('action', action);
 
    return reducer(state, action);
  };
}
 
export const metaReducers: MetaReducer<any>[] = [debug];
 
@NgModule({
  imports: [
    StoreModule.forRoot(reducers, { metaReducers })
  ],
})
export class AppModule {}

Code practice

NgRx Entity

NgRx Entity

  • Reduces boilerplate for managing a collection of entities
  • Built-in CRUD operations
  • Built-in selectors

NgRx Entity

interface EntityState<V> {
  ids: string[] | number[];
  entities: { [id: string | id: number]: V };
}

interface State extends EntityState<User> {
  selectedUserId: number | null;
}


const adapter: EntityAdapter<User>
  = createEntityAdapter<User>();

Code practice

Testing

Testing

  • provideMockStore
  • overrideSelector
  • jasmine-marbles

Integration testing

Use the 'real' Store

Selectors

export const selectTotal = createSelector(
  selectSumEvenNums,
  selectSumOddNums,
  (evenSum, oddSum) => evenSum + oddSum
);

describe('My Selectors', () => {
  it('should calc selectTotal', () => {
    expect(selectTotal.projector(2, 3)).toBe(5);
  });
});

Code practice

Simple state management

Testing effects

NgRx Data

NgRx Data

  • manage collections of entities (domain models) without the code
  • based on metadata config
  • provides all the NgRx parts, HTTP methods, and extension points

Q&A

Thanks!