1 of 54

Getting to grips with state

with NgRx and RxJS

Ferdinand Malcher and Johannes Hoppe

Prepare for the workshop

ac18.angular.schule

2 of 54

Getting to grips with state

with NgRx and RxJS

Ferdinand Malcher and Johannes Hoppe

Prepare for the workshop

ac18.angular.schule

3 of 54

  • cache server-fetched data
  • share data between components
  • react to things that happen throughout the application
  • maintain data that is spread over many components

How can I …

@fmalcher01 | @johanneshoppe

4 of 54

CENTRALIZE

ALL YOUR DATA

@fmalcher01 | @johanneshoppe

5 of 54

State Management

@fmalcher01 | @johanneshoppe

6 of 54

“Redux is a predictable state container for JavaScript apps.” — redux.js.org

@fmalcher01 | @johanneshoppe

7 of 54

Reactive Extensions

for Angular (NgRx)

“RxJS powered state management for Angular applications, inspired by Redux”

— ngrx.github.io

@fmalcher01 | @johanneshoppe

8 of 54

Store

State

immutable

State

immutable

State

immutable

read-only

write: new state

Event

Single Source of Truth

@fmalcher01 | @johanneshoppe

9 of 54

NgRx provides robust state management for small and large projects. It enforces proper separation of concerns. Using it from the start reduces the risk of spaghetti when the project evolves.

@fmalcher01 | @johanneshoppe

10 of 54

Ferdinand Malcher

@fmalcher01

Johannes Hoppe

@JohannesHoppe

@fmalcher01 | @johanneshoppe

11 of 54

Building Blocks

Action

Reducer

Selector

Effect

Domain event

can trigger state changes

Read data from the state tree

for usage in components

Compute actions

to new state

Trigger side effects

and dispatch new actions

@fmalcher01 | @johanneshoppe

12 of 54

Data Flow in NgRx (basic)

Selector

State

Reducer

dispatch

Action

Component

Store

@fmalcher01 | @johanneshoppe

13 of 54

@fmalcher01 | @johanneshoppe

14 of 54

Prerequisites

Node.js and NPM

Visual Studio Code

Git

@fmalcher01 | @johanneshoppe

15 of 54

Installation

@fmalcher01 | @johanneshoppe

16 of 54

From Zero to NgRx

@fmalcher01 | @johanneshoppe

17 of 54

Setup store and feature reducer

ng g @ngrx/schematics:store State --root --module app

ng g @ngrx/schematics:reducer Book

--group --reducers reducers/index.ts

@fmalcher01 | @johanneshoppe

18 of 54

Structure of the feature state

export interface State {

books: Book[];

loading: boolean;

}

We need to define an initial state in the same file.

book.reducer.ts

@fmalcher01 | @johanneshoppe

19 of 54

Programming with NgRx is essentially programming with messages

@fmalcher01 | @johanneshoppe

20 of 54

Action

Domain event or command

can trigger state change

User actions/commands

asynchronous events (API calls, timers, …)

@fmalcher01 | @johanneshoppe

21 of 54

Action

Action Object

Type and optional payload

Action Creator Class

{

type: 'LOAD_SUCCESS',

payload: /* ... */

}

export class LoadSuccess implements Action {

readonly type = 'LOAD_SUCCESS';

constructor(public payload: Book[]) {}

}

@fmalcher01 | @johanneshoppe

22 of 54

Action Enum

Avoid magic strings

Group action types in an enum

export enum BookActionTypes {

LoadSuccess = 'LOAD_SUCCESS'

}

Use the enum values

Instead of the magic string

export class LoadSuccess {

readonly type = BookActionTypes.LoadSuccess;

constructor(public payload: Book[]) {}

}

@fmalcher01 | @johanneshoppe

23 of 54

Actions for our application

LoadBooksFail

“An error occured during loading of the book list”

LoadBooksSuccess

“Book list arrived back from the server”

LoadBooks

“Start loading of book list”

@fmalcher01 | @johanneshoppe

24 of 54

Create actions

ng g @ngrx/schematics:action Book --group

…and then build all other actions manually.

see also #1016

@fmalcher01 | @johanneshoppe

25 of 54

Dispatch action

constructor(private store: Store<State>) { }

// ...

this.store.dispatch(new LoadBooks());

dashboard.component.ts

@fmalcher01 | @johanneshoppe

26 of 54

Redux Dev Tools

@fmalcher01 | @johanneshoppe

27 of 54

State

Reducer

dispatch

Action

Component

Store

@fmalcher01 | @johanneshoppe

28 of 54

Reducer

take current state and new action

create new state (immutable)

Staten-1

Reducer

Action

Staten

@fmalcher01 | @johanneshoppe

29 of 54

Reducers are Pure Functions!

  • NO data other than own arguments
  • NO side effects
  • NO direct state manipulation

Always return a new, cloned state

Spread operator is our best friend

{ ...state }

@fmalcher01 | @johanneshoppe

30 of 54

What are we going to do?

Change the state!

{

books: [/***/],

loading: false

}

{

loading: true

}

LoadBooks

LoadBooksSuccess

@fmalcher01 | @johanneshoppe

31 of 54

Reducer

export function reducer(state, action: BookActions): State {

switch (action.type) {

case BookActionTypes.LoadBooks: {

return { ...state, loading: true }; // return new state

}

default: { return state; }

}

}

book.reducer.ts

@fmalcher01 | @johanneshoppe

32 of 54

Selector

State

Reducer

dispatch

Action

Component

Store

@fmalcher01 | @johanneshoppe

33 of 54

Selector

read part of the state tree

for usage in components

“A selector is a pure function that takes the state as an argument and returns a slice of the store state.”

— https://blog.angularindepth.com/c50b1dc556bc

false

{

books: [],

loading: false

}

@fmalcher01 | @johanneshoppe

34 of 54

Feature Selector

Feature Selector

Root selector for one feature – our selector journey always starts here

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

import { State as BookState } from './book.reducer';

// ...

export const getBookState = createFeatureSelector<BookState>('book');

book.selectors.ts

@fmalcher01 | @johanneshoppe

35 of 54

Create Selector

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

// ...

const getBooksLoading = createSelector(

getBookState, // other selectors

state => state.loading // projector function

);

Map state tree to the desired data

Function createSelector()

Projector function as last argument

book.selectors.ts

@fmalcher01 | @johanneshoppe

36 of 54

Use Selector

{

books: [],

loading: false

}

{

books: [],

loading: true

}

{

books: [/***/],

loading: true

}

{

books: [/***/],

loading: false

}

select(getBooksLoading)

true

false

false

Memoization

@fmalcher01 | @johanneshoppe

37 of 54

Use Selectors in component

loading$ = this.store.pipe(

select(getBooksLoading)

);

books$ = this.store.pipe(

select(getAllBooks)

);

dashboard.component.ts

@fmalcher01 | @johanneshoppe

38 of 54

Data flow in NgRx (complete)

HTTP

Web Server

Effect

dispatch

Selector

Action

dispatch

State

Reducer

Action

Component

Store

@fmalcher01 | @johanneshoppe

39 of 54

Effect

Reactive data stream (Observable of Actions)

React to actions and other events

Trigger side effects (everything is allowed)

Automatically dispatch new action

Action

Action

@fmalcher01 | @johanneshoppe

40 of 54

Create effects class

ng g @ngrx/schematics:effect Book --group --root --module app

@fmalcher01 | @johanneshoppe

41 of 54

What are we going to do?

React to the LoadBooks action

Fetch books from the server

Dispatch LoadBooksSuccess with the book list inside

@fmalcher01 | @johanneshoppe

42 of 54

Effect

@Effect()

loadBooks$ = this.actions$.pipe(

ofType(BookActionTypes.LoadBooks), // filter actions

concatMap(action => this.service.getAll()), // Side Effect HTTP

tap(action => console.log('Logging')), // Side Effect Logging

map(books => new LoadBooksSuccess(books)) // map to action

);

book.effects.ts

The resulting Action will be dispatched automatically!

@fmalcher01 | @johanneshoppe

43 of 54

Data flow in NgRx (complete)

HTTP

Web Server

Effect

dispatch

Selector

Action

dispatch

State

Reducer

Action

Component

Store

@fmalcher01 | @johanneshoppe

44 of 54

SHARI: What belongs in the Store?

State that is accessed by many components and services

State that is persisted and [later on] hydrated from storage

State that needs to be available when re-entering routes

State that needs to be retrieved with a side effect

State that is impacted by actions from other sources

Shared

Hydrated

Available

Retrieved

Impacted

@fmalcher01 | @johanneshoppe

45 of 54

How can I do…

Routing

@ngrx/router-store

Bindings to connect the Angular Router with @ngrx/store

ngrx-router

NGRX Router – Router Bindings and Helpers for NgRx Effects

@fmalcher01 | @johanneshoppe

46 of 54

How can I do…

Testing

Testing Effects

@ngrx/effects/testing provides utilities for effect(ive) testing

Marble Testing

Testing RxJS Code with Marble Diagrams

@fmalcher01 | @johanneshoppe

47 of 54

How can I do…

Entity Management

@ngrx/entity

Entity State adapter for managing record collections

ngrx-data

Radically reduce the amount of "boilerplate" necessary to manage entities with NgRx

@fmalcher01 | @johanneshoppe

48 of 54

What can you do?

@fmalcher01 | @johanneshoppe

49 of 54

Read blogs

Watch talks

Resources list in our guide document

ac18.angular.schule

@fmalcher01 | @johanneshoppe

50 of 54

Do try this at home

We prepared some homework for you.

@fmalcher01 | @johanneshoppe

51 of 54

Discuss with friends

Ask questions

Stay curious

@fmalcher01 | @johanneshoppe

52 of 54

@angular_schule

@fmalcher01 | @johanneshoppe

53 of 54

Code on GitHub

https://github.com/

angular-schule/book-rating-ngrx

@fmalcher01 | @johanneshoppe

54 of 54

Credits

Network and Database icons�Web Development icons

�designed by Smashicons from flaticon.com�

@fmalcher01 | @johanneshoppe