NgRx Tips

ref:  https://gist.github.com/btroncone/a6e4347326749f938510#expanding-state

  • if store is to be thought of as your client side database, reducers can be considered the tables in said database.
  • Reducers represent sections, or slices of state within your application and should be structured and composed accordingly.
  • Store encompasses our application state and reducers output sections of that state, but how do we communicate to our reducers when state needs to be updated? That is the role of 4 actions.
  • Within a store application, all user interaction that would cause a state update must be expressed in the form of actions.
  • Finally, we need to extract, combine, and project data from store for display in our views. Because store itself is an observable, we have access to the typical JS collection operations you are accustom to (map, filter, reduce, etc.) along with a wide-array of extremely powerful RxJS based observable operators. This makes slicing up store data into any projection you wish quite easy.
    State Projection
    //most basic example, get people from state
    store.select('people')
      
    //combine multiple state slices
    Observable.combineLatest(
      store.select('people'),
      store.select('events'),
      (people, events) => {
        //projection here
    })
  • The two pillars of @ngrx/store, the store and dispatcher, both extend RxJS Subjects. Subjects are both Observables and Observers, meaning you can subscribe to a Subject, but can also subscribe a Subject to a source. At a high-level Subjects can be thought of as messengers, or proxies.Because Subjects are Observers, you can ‘next’, or pass values into the stream directly. Subscribers of that Subject will then be notified of emitted values. In the context of Store, these subscribers could be a Angular 2 service, component, or anything requiring access to application state.
  • In Store (and Redux), it is convention to dispatch actions to the application store. To maintain this API, the Dispatcherextends Subject, adding a dispatch method as a passthrough to the classic next method. This is used to pass values into the Subject before emitting these values to subcribers:
  • class Dispatcher extends Rx.Subject{
      dispatch(value : any) : void {
        this.next(value);
      }
    }
    
    //create a dispatcher (just a Subject with wrapped next method)
    const dispatcher = new Dispatcher();
  • While vanilla Subjects work perfectly as a ‘dispatcher’, they have one issue that prevents them from being a good fit for store. When subscribing to a Subject, only values emitted after the subscription are received. This is unacceptable in an environment where components will be consistently added and removed, requiring the latest, on-demand sections of state from the application store at the time of subscription.
  • Luckily, RxJS offers a convienant extension of Subject to handle this problem, the BehaviorSubject. BehaviorSubject’s encapsulate all of the functionality of Subject, but also return the last emitted value to subscribers upon subscription. This means components and services will always have access to the latest (or initial) application state and all future updates.
  • In order to function correctly in the context of store, the dispatcher still needs some work. In a Store application, all dispatched actions must be passed through a specific pipeline before a new representation of state is passed into store, to be emitted to all observers. You can think of this as a factory assembly line, in this case the stations on the line are pre-middleware -> reducers -> post-middleware -> store.

 

  • The creation of this pipeline is handled by passing the dispatcher into store when it is created. The store next method is then overridden in order to delegate all actions first to the dispatcher pipeline before passing the new representation of state into store. This also allows the store to be subscribed directly to action streams, funneling received actions first through the dispatcher.
Associating the Dispatcher with Store
/*
All actions should pass through pipeline before newly calculated state is passed to store. 
1.) Dispatched Action
2.) Pre-Middleware
3.) Reducers (return new state)
4.) Post-Middleware
5.) store.next(newState)
*/

class Dispatcher extends Rx.Subject{
  dispatch(value : any) : void {
    this.next(value);
  }
}

class Store extends Rx.BehaviorSubject{
  constructor(
    private dispatcher,
    initialState
  ){
    super(initialState);
    /* 
    all dispatched actions pass through action pipeline before new state is passed
    to store
    */
    this.dispatcher 
       //pre-middleware
       //reducers
       //post-middleware 
       .subscribe(state => super.next(state)); 

  }
  //delegate store.dispatch first through dispatched action pipeline
  dispatch(value){
    this.dispatcher.dispatch(value);  
  }
  //override store next to allow direct subscription to action streams by store
  next(value){
    this.dispatcher.dispatch(value);
  }
}

const dispatcher = new Dispatcher();
const store = new Store(dispatcher, 'INITIAL STATE');

const subscriber = store.subscribe(val => console.log(`VALUE FROM STORE: ${val}`));
/*
All dispatched actions first flow through action pipeline, calculating new state which is
then passed to store. To recap, our ideal behavior:
dispatched action -> pre-middleware -> reducers -> post-middleware -> store.next(newState)
*/

//both methods are same behind the scenes
dispatcher.dispatch('DISPATCHED VALUE!');
store.dispatch('ANOTHER DISPATCHED VALUE!');

const actionStream$ = new Rx.Subject();
/*
Overriding store next method allows us to subscribe store directly to action streams, providing same behavior as manually calling store.dispatch or dispatcher.dispatch
*/
actionStream$.subscribe(store);
actionStream$.next('NEW ACTION!');
  • Inspired by Redux, @ngrx/store has the concept of Reducer functions used to manipulate specific slices of state. Reducers accept a state and action parameter, exposing a switch statement (generally, although this could be handled multiple ways) defining action types in which the reducer is concerned. 9 Each time an action is dispatched, each reducer registered to store will be called (through the root reducer, created during provideStore at application bootstrap), passed the current state for that state slice (accumulator) and the dispatched action. If the reducer is registered to handle that action type, the appropriate state calculation will be performed and a new representation of state output. If not, the current state for that section will be returned. This is the core of Store and Redux state management.
  • Scan behaves in a similar fashion to reduce, except the accumulator value is maintained over time, or until the observable with scan applied has completed. For instance, as actions are dispatched and new state output, the accumulator in the scan function will always be the last output representation of state. This alleviates the need to maintain a copy of state in store to be passed to our reducers, the scan operator handles this functionality.
  • To utilize scan in the application store, it simply needs to be applied as an operator to the dispatcher. All dispatched actions will pass through scan, invoking the combined reducer with current state and action, outputting a new representation of state. The new application state is then next‘ed, or pushed to the store, and emitted to all subscribers.
  • Equipping Store with scan
    class Store extends Rx.BehaviorSubject{
      constructor(
        private dispatcher,
        private reducer,
        initialState = {}
      ){
        super(initialState);
        this.dispatcher 
           //pre-middleware?
    /*
    Scan is a reduce over time. In the previous lesson we compared reduce to a snowball rolling downhill, accumulating mass
    (or calculated value). Scan can be thought of similarly, except the hill has no certain end. The accumulator (in this
    case, state) will continue to collect until destroyed. This makes it the ideal operator for managing application state.
    */
           .scan((state, action) => this.reducer(state, action), initialState)
           //post-middleware? 
           .subscribe(state => super.next(state));
      }
      //...store implementation
    }
  • Managing Middleware with let

     

    Middleware has been removed in ngrx/store v2. It is still worth reading through this section to understand let as it can be used with selectors.\

  • Middleware has been remove in ngrx/store v2. Please see the meta-reducers section for how to create similar functionality.
  • While most operators are passed emitted values from the observable, let is handed the entire observable. This allows the opportunity to tack on extra operators and functionality, before returning the source observable. While this may seem like a small nuance, it fits perfectly into situations like middleware or selectors (to be discussed later), where the consumer would like to define a composable, reusable block of code to be inserted at a particular slot in an observable chain.
  • this.dispatcher 
           //The let operate accepts the source observable, returning a new observable
           //@ngrx/store composes middleware so you can supply more than 1 function,
           //for our simple example we will accept one pre and post middleware
           //Middleware signature: (obs) => obs
           .let(preMiddleware)
           .scan((state, action) => this.reducer(state, action), initialState)
           .let(postMiddleware) 
           .subscribe(state => super.next(state));
  • To recap up to this point:
    • Dispatched actions are next‘ed into the dispatcher (Subject)
    • The Dispatcher has 3 operators applied:
      • let – Passed an observable of actions
      • scan – Calls each reducer with current state and action, outputting new representation of state
      • let – Passed an observable of state
    • The new representation of state is then nexted into store (BehaviorSubject), to be emitted to subscribers

    This is the gist of the inner workings of store.

  • Slicing state with map

     

    The cornerstone function for projecting data from a collection is map. Map applies a specified function to each item, returning a new representation of that item. Because application state is a key/value object map of sections of state, it’s simple to provide a helper function to return the requested slice of state based on a string, or any other relevant selector.

  • //Map makes it easy to select slices of state that will be needed for your components
      //This is a simple helper function to make grabbing sections of state more concise
      select(key : string) {
        return this.map(state => state[key]);
  • Managing Updates with distinctUntilChanged

    Each view in our application generally is concerned with their own particular slices of state. For perfomance reasons, we would prefer not to emit new values from the selected state slices unless an update has been made. Luckily for us, RxJS has an operator for exactly this scenario (notice the trend). The distinctUntilChanged operator will only emit when the next value is unique, based on the previously emitted value. In cases of numbers and strings, this means equal numbers and strings, in the case of objects, if the object reference is the same a new object will not be emitted.

  •   distinctUntilChanged only emits new values when output is distinct, per last emitted value. 
        In the example below, the observable with the distinctUntilChanged operator will emit one less value then the other       with only the map operator applied
      */
      select(key : string) {
        return this
          .map(state => state[key])
          .distinctUntilChanged();
      }
  • Reducers are the foundation to your store application. As the application store maintains state, reducers are the workhorse behind the manipulation and output of new state representations as actions are dispatched. Each reducer should be focused on a specific section, or slice of state, similar to a table in a database.
  • Creating reducer’s is quite simple once you get used to one common idiom, never mutate previous state and always return a new representation of state when a relevant action is dispatched. If you are new to store or the Redux pattern this takes some practice to feel natural. Instead of using mutative methods like push, or reassigning values to previously existing objects, you will instead lean on none mutating methods like concat and Object.assign to return new values.
  • The AsyncPipe is a unique, stateful pipe in Angular 2 meant for handling both Observables and Promises. When using the AsyncPipe in a template expression with Observables, the supplied Observable is subscribed to, with emitted values being displayed within your view. This pipe also handles unsubscribing to the supplied observable, saving you the mental overhead of manually cleaning up subscriptions in ngOnDestroy. In a Store application, you will find yourself leaning on the AsyncPipeheavily in nearly all of your component views. For a more detailed explanation of exactly how the AsyncPipe works, check out my article Understand and Utilize the AsyncPipe in Angular 2 or free egghead.io video Using the Async Pipe in Angular 2.
  • /*
            Observable of people, utilzing the async pipe
            in our templates this will be subscribed to, with
            new values being dispayed in our template.
            Unsubscribe wil be called automatically when component
            is disposed.
          */
          this.people = _store.select('people');
  • <person-list
            [people]="people | async"
  • Taking Advantage of ChangeDetection.OnPush

    Utilizing a centralized state tree in Angular 2 can not only bring benefits in predictability and maintability, but also performance. To enable this performance benefit we can utilize the changeDetectionStrategy of OnPush.

    The concept behind OnPush is straightforward, when components rely solely on inputs, and those input references do not change, Angular can skip running change detection on that section of the component tree. As discussed previously, all delegating of state should be handled in smart, or top level components. This leaves the majority of components in our application relying solely on input, safely allowing us to set the ChangeDetectionStrategy to OnPush in the component definition. These components can now forgo change detection until necessary, giving us a free performance boost.

     

  • Projecting State for View with combineLatest and withLatestFrom

    While building out your @ngrx/store application, many of your components will require slices of state output from multiple reducers. One example of this would be a list that is adjusted by a filter, both managed through store. When the filter is changed, the list needs to be updated to reflect this change. So how do we facilitate this interaction? RxJS provides two operators that you will consistently utilize, combineLatest and withLatestFrom.

    The combineLatest operator accepts an unspecified number of observables, emitting the last emitted value from each when any of the provided observables emit. These values are passed to a projection function for you to form the appropriate projection.

  • //combineLatest also takes an optional projection function
    const combinedProject = Rx.Observable
    .combineLatest(
        timerOne,
        timerTwo,
        timerThree,
        (one, two, three) => {
          return `Timer One (Proj) Latest: ${one}, 
                  Timer Two (Proj) Latest: ${two}, 
                  Timer Three (Proj) Latest: ${three}`
        }
    );
    //log values
    const subscribe = combinedProject.subscribe(latestValuesProject => console.log(latestValuesProject));
  • The withLatestFrom operator is similar, except it only combines the last emitted values from the provided observables when the source observable emits. This is useful when your projection depends first on a single source, aided by multiple other sources.
  • Extracting Selectors for Reuse

    Through the course of building your application you will often utilize similar queries, or projections of state in your views. A common way to eliminate the duplication of this logic is to place popular selections into services, injecting these services where needed in your components or other services. While this certainly works, there is another more flexible, composable way to tackle this issue.

    Because nothing about these projections is Angular specific, we can export each small query, or 12 selector independantly, without the need for Angular service wrapping. Leveraging the let operator, these selectors can then be mixed and matched for the desired result, whether in components, services, or middleware. This toolbox of targeted, composable queries is called the selector pattern.

  • Applying selectors is easy, simply apply the let operator to the appropriate Observable, supplying the selector(s) of your choosing.
  • this.model = Observable.combineLatest(
                _store.select('people')
                _store.select('partyFilter')
              )
              //extracting party model to selector
              .let(partyModel());
          //for demonstration on combining selectors
          this.percentAttendance = _store.let(percentAttending());
  • Implementing a Meta-Reducer

    (Work Along | Completed Lesson)

    One of the many advantages to a single, immutable state tree is the ease of implementation for generally tricky features like undo/redo. Since the progression of application state is fully tracable through snapshots of store, the ability to walk back through these snap shots becomes trivial. A popular method for implementing this functionality is through meta-reducers.

    Despite the ominous sound, meta-reducers are actually quite simple in theory and implementation. To create a meta-reducer, you wrap a current reducer in a parent reducer, delegating the majority of actions through the wrapped reducer as normal, stepping in only when defined meta actions (such as undo/redo) are dispatched. How does this look in practice? Let’s see by creating a reset feature for our party planning application, allowing the user to start from scratch if they want to enter all new data.

  • To encapsulate this functionality we create a factory function, accepting any reducer to wrap, returning our reset reducer. When the reset reducer is initialize we grab the initial state of the wrapped reducer, saving it for later use. All that is left is to listen for a specific reset action to be dispatched. If RESET_STATE is not dispatched, the action is passed to the wrapped reducer and state returned as normal. When RESET_STATE is triggered the stored initial state is returned instead of the result of invoking the wrapped reducer. Undo/redo can be handled similarly, keeping track of previous actions in local state.
    Reset Meta-Reducer
    export const RESET_STATE = 'RESET_STATE';
    
    const INIT = '__NOT_A_REAL_ACTION__';
    
    export const reset = reducer => {
        let initialState = reducer(undefined, {type: INIT})
        return function (state, action) {
          //if reset action is fired, return initial state
          if(action.type === RESET_STATE){
            return initialState;
          }
          //calculate next state based on action
          let nextState = reducer(state, action);
          //return nextState as normal when not reset action
          return nextState;
      }
    }
    Wrap Reducer On Bootstrap
    bootstrap(App, [
      //wrap people in reset meta-reducer
      provideStore({people: reset(people), partyFilter})
    ]);

    It is worth noting that the store root reducer is itself a meta-reducer, created behind the scenes when calling provideStore(by 14 combineReducers). For each dispatched action, the root reducer invokes each application reducer with the previous state and current action, returning an object map of [reducer]: state[reducer].

    14 combineReducers
    export function combineReducers(reducers: any): Reducer<any> {
      const reducerKeys = Object.keys(reducers);
      const finalReducers = {};
    
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i];
        if (typeof reducers[key] === 'function') {
          finalReducers[key] = reducers[key];
        }
      }
    
      const finalReducerKeys = Object.keys(finalReducers);
    
      return function combination(state = {}, action) {
        let hasChanged = false;
        const nextState = {};
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i];
          const reducer = finalReducers[key];
          const previousStateForKey = state[key];
          const nextStateForKey = reducer(previousStateForKey, action);
    
          nextState[key] = nextStateForKey;
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        return hasChanged ? nextState : state;
      };
    }

    ALSO SEE: https://github.com/ngrx/effects/blob/master/docs/intro.md

    In @ngrx/effects, effects are sources of actions. You use the @Effect() decorator to hint which observables on a service are action sources, and @ngrx/effects automatically merges your action streams letting you subscribe them to store.

    To help you compose new action sources, @ngrx/effects exports an Actions observable service that emits every action dispatched in your application.

    Observables decorated with the @Effect() decorator are expected to be a stream of actions to be dispatched. Pass { dispatch: false } to the decorator to prevent actions from being dispatched.

    API: https://github.com/ngrx/effects/blob/master/docs/api.md

    Example

    1. Create an AuthEffects service that describes a source of login actions:
    import { Injectable } from '@angular/core';
    import { Http } from '@angular/http';
    import { Actions, Effect } from '@ngrx/effects';
    import { Observable } from 'rxjs/Observable';
    
    @Injectable()
    export class AuthEffects {
      constructor(
        private http: Http,
        private actions$: Actions
      ) { }
    
      @Effect() login$ = this.actions$
          // Listen for the 'LOGIN' action
          .ofType('LOGIN')
          // Map the payload into JSON to use as the request body
          .map(action => JSON.stringify(action.payload))
          .switchMap(payload => this.http.post('/auth', payload)
            // If successful, dispatch success action with result
            .map(res => ({ type: 'LOGIN_SUCCESS', payload: res.json() }))
            // If request fails, dispatch failed action
            .catch(() => Observable.of({ type: 'LOGIN_FAILED' }))
          );
    }
    1. Provide your service via EffectsModule.run to automatically start your effect:
    import { EffectsModule } from '@ngrx/effects';
    import { AuthEffects } from './effects/auth';
    
    @NgModule({
      imports: [
        EffectsModule.run(AuthEffects)
      ]
    })
    export class AppModule { }

    Note: For effects that depend on the application to be bootstrapped (i.e. effects that depend on the Router) use EffectsModule.runAfterBootstrap. Be aware that runAfterBootstrap will only work in the root module.

    Excellent RxJS and Redux related Egghead.io videos and courses:

    For additional examples, explanations, and resources for RxJS check out my new site at http://learnrxjs.io/!

     

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s