Ngrx Love

What?

  • "RxJS powered state management for Angular applications, inspired by Redux"
    • Focus on Redux part today
    • RxJS will have to be a separate talk

Terms

  • State - A single immutable data structure
  • Actions - Objects that describe state changes
  • Reducers - Pure functions that take the previous state and the next action to compute the new state
  • Store - An observable of state and an observer of actions
    • Can dispatch actions
    • Can select state

Redux

Redux

Why?

  • Single source of truth
    • One place to trigger actions (store.dispatch)
      • easy to track
    • One place to wire actions together (ngrx/effects)
    • One place to modify state (reducers)
      • easy to test
    • One place to listen to state changes (store.select)
  • Like a database for the browser
  • More later...

How?

Make Way for Ducklings

  • Apologies to Robert McCloskey

Mr. Mallard Alone

mister.component calls store.dispatch(quack)

export class MisterComponent implements OnInit {
  constructor(private store: Store<fromRoot.State>) { }

  onClick() {
    this.store.dispatch({ type: actionTypes.QUACK });
  }
}

Mr. Mallard Alone

mister.reducer updates the state

export const reducer = handleActions<State>(
  {
    [actionTypes.QUACK]: state => {
      return Object.assign({}, state, { says: 'Quack!' });
    },
  },
  initialState);

Mr. Mallard Alone

mister.component renders new state

export class MisterComponent implements OnInit {
  says$: Observable<string>;

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

  ngOnInit() {
    this.says$ = this.store.select(s => s.mister.says);
  }
}
<figure>
  <img src="assets/mister.png">
  <figcaption>
    <h2>{{says$ | async}}</h2>
  </figcaption>
</figure>

Mrs. Mallard

missus.reducer responds to mister's QUACK action

export const reducer = handleActions<State>(
  {
    [misterActionTypes.QUACK]: state => {
      return Object.assign({}, state, { says: 'Quack!' });
    },
  },
  initialState);

Ducklings!

duckling.effects connects actions together

  @Effect()
  misterQuack$: Observable<Action> = this.actions$
    .ofType(misterActionTypes.QUACK)
    .map(() => ducklingQuackAction(0));

  @Effect()
  ducklingQuack$: Observable<Action> = this.actions$
    .ofType(actionTypes.QUACK).map(toPayload)
    .withLatestFrom(
      this.store.select(fromRoot.getDucklingState)
    )
    .filter(([ducklingIndex, ducklings])
      => ducklingIndex < ducklings.length - 1)
    .map(([ducklingIndex])
      => ducklingQuackAction(ducklingIndex + 1))
    .delay(400);

Ducklings!

missus.effects is overwhelmed

  @Effect()
  missusSquawk$: Observable<Action> = this.actions$
    .ofType(misterActionTypes.QUACK)
    .delay(4000)
    .map(() => squawkAction());

Undo/Redo

Small changes for big wins

const reducers = {
  duckling: fromDuckling.reducer,
  missus: fromMissus.reducer,
  mister: fromMister.reducer,
};

const reducer
 = compose(undoable, combineReducers)(reducers);

@NgModule({
  imports: [StoreModule.provideStore(reducer)],
})
export class AppModule { }

Why? (part 2)

Easy(er) Features

Redux Dev Tools Extension

  • Benefit from the work of Redux/React community
  • Easily view state
  • Easily generate tests for reducers with real data
  • Time travel
  • Export/Import to reproduce bugs, etc.

More Questions

Ngrx Love

By Isaac Mann

Ngrx Love

  • 585