Application Architecture

with NgRx

WEB DEVELOPER

BRETT COFFIN

Angular

Node

FRP

Firebase

Neo4j

Kite-Surf

Speargun Fishing

Bitcoin

TECH STACK

FRP

DI

Decorator

Typescript

Immutability

Reducer

Store

Action (SFA)

Observable

Pattern

FRP

DI

Decorator

Typescript

Immutability

Reducer

Store

Action (SFA)

*

Resources

MVC

Managing the States

Pure MVC 

Implemented in many languages

Mediator,

Proxy,

Facade,

Cmd,

MVP,

MVW...

Functional Reactive Programing

Erik Meijer

Getters

READ / PULL

() => A

Lazy Producer

Consumer is in charge

Setters

WRITE / PUSH

A => ()

Lazy Consumer

Producer is in charge


const tradeMaxPrice$ = stratFeed$
      .pipe(
        skipUntil(stratState$.pipe(
          pluck('order'),
          filter((x) => x.type === 'buy'),
          takeUntil(stratState$.pipe(
            pluck('order'),
            filter((x) => x.type === 'sell'),
          )),
        )),
        scan((acc, curr: Feed) => {
          return (acc > curr.price) ? acc : curr.price;
        }, 0),
        tap(x => {
          this.programmaticCommandSubject.next({tradeMaxPrice: x});
        })
      );

Example

FRP

Orchestrate Application State Elegantly

Perfect for Highly Reative UI (ie: real time)

No Managing state in objects

Observable Chains

REDUX

CQRS EVENT SOURCING

REDUX

NgRx Concepts

View

Command

Effect

Event

Reducer

Snapshot

Store

Projection

(CLICK)

Command: queryBooks()

VIEW

COMMAND

type:  string

payload: any

error: bool

meta: any

FLUX STANDARD ACTION (FSA)

queryBooks = (payload?) => {
    return this.store.dispatch(
        {type: Commands.QUERY_BOOKS, payload}
     );
  }

queryBooks()

EFFECT

queryBooks()

Success

Failure

 @Effect()
  queryBooks$ = this.actions$
    .pipe(
      ofType(Commands.QUERY_BOOKS),
      switchMap(_ => this.service.queryBooks()
        .pipe(
          map((res) => this.events.queryBooksComplete(res)),
          catchError(err => this.events.queryBooksError(err))
        )));

EFFECT

EVENT

EVENT

EVENT

type: string

payload: any

error: bool

meta: any

FLUX STANDARD ACTION (FSA)

queryBooksComplete = (payload) => {
    return {type: Events.QUERY_BOOKS_COMPLETE, payload};
};

queryBooksError = (payload) => {
    return of({type: Events.QUERY_BOOKS_COMPLETE, payload, error: true});
};

queryBooksComplete

REDUCER

Event: queryBooksComplete

Snapshot: books

switch (event.type) {
    case Events.QUERY_BOOKS_COMPLETE: {
      return queryBooksSnapshot(state, event);
    }
     
    default: {
      return state;
    }
}

const queryBooksSnapshot = (state: IBook[], event): IBook[]  => {
   const newBooks = event.error ? [] : event.payload;
   return [...state, ...newBooks];
};

REDUCER

STORE

Snapshot: books

Projection


  queryBooks$(): Observable<IBook[]> {
    return this.store.select('books');
  }

export interface AppState {
  books: IBook[];
}

export const reducers: ActionReducerMap<AppState> = {
  books: fromBooks.getReducers
};

STORE

Books

Projection

VIEW


books$: Observable<IBook[]> = this.bookProj.queryAll$();

<li *ngFor="let book of books$ | async">{{book.name}}</li>

View

Command

Effect

Event

Reducer

Snapshot

Store

Projection

Input

Ouput

*

Unidirectional data flow

Command

Effect

Event

Command

Side Effect

Command

*

COMPLEXITY

FRP is a better solution to manage complex states

This pattern solve the same way every problem

CONCITENCY

But why ?

But why ?

View

Command

Effect

Event

Reducer

Snapshot

Store

Projection

*

One pattern to rule them all

NOTES

  • Don't trigger a Command from a store mutation
  • You are probably missing an Effect
  • Separate Commands from Events
  • Use the pattern within features modules
  • Build your own API around it

 

 

ng-conf 2020: Call for Papers

By bretto

ng-conf 2020: Call for Papers

  • 282