Friday, March 31, 2017

Angular recipe: Get State updates from Redux

This article is about the possible ways to retrieve your data State in Angular (read 2 or 4) from Redux Store.


It's assumed that you are already familiar with Redux ecosystem. If not click an image below:


Angular-redux/store

We have installed @angular-redux/store which integrates our redux store in Ng2 applications.

Bash
npm install redux @angular-redux/store --save

Store:

Here we create the simple possible store with typed state and one field marker, that will be updated with each action.

TypeScript
import { combineReducers } from 'redux';
import {ItemsState, itemsReducer} from './items/items.reducer';

/**
 * Our Application state
 *
 * @interface IAppState
 */
export interface IAppState {
  readonly marker?: String
}

/**
 * Root reducer of our app
 */
export const rootReducer = combineReducers<IAppState>({
  marker: marker
});

/**
 * Our inlined sub-reducer for updating the marker.
 */
function marker(state:String = "InitialEmptyState", action: any): String {
  return "MarkedByAction " + action.type;
}

Component:

Our Component in which we will display our marker field changes.

TypeScript
import { Component } from '@angular/core';
import {NgRedux, select} from '@angular-redux/store';
import {Observable} from 'rxjs';
import {IAppState} from '../store/store';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private ngRedux: NgRedux<IAppState>, private actions: AppActions) { }

  ngOnInit() { }

  public clickHandler(): void {
    this.ngRedux.dispatch(this.actions.create());
  }
}

So, whenever the clickHandler is invoked we dispatch some action which updates our marker state.


Get updates from Store

To update the UI we need to subscribe on our state updates and here are the various ways to do so.

*Note: To focus on the receiving state updates, we will just output values into the log.
Also for an explicit difference between variables, all variable names are different. Otherwise I would stick to just marker$ for streams.


Subscribe for all State changes

This is the most primitive way. We subscribe for the all changes and retrieve needed field from the whole State:

TypeScript
constructor(private ngRedux: NgRedux<IAppState>, private actions: ItemsActions) {

  ngRedux.subscribe(() => {
      console.log("State Changed! m: " + this.ngRedux.getState().marker);
  });
}

Select property from NgRedux store as an Observable

Below is three different ways which do exact the same thing.

  • Select with Fat Arrow

    TypeScript
    ngOnInit() {
      const stream1$ = this.ngRedux.select(state => state.marker);
      stream1$.subscribe(m => {
        console.log("#1 Marker updated! m: " + m);
      });
    }
  • Select with Selector.
    Separating your selector migh become handy if you have a deep structure and need to be able to reuse selector in different places.

    * marker.selector.ts

    TypeScript
    import {IAppState} from '../store';
    /**
     * Selector for 'marker' in application store
     */
    export const markerSelector = (state: IAppState) => state.marker;

    * app.component.ts

    TypeScript
    import {markerSelector} from '../store/marker.selector';
    ...
    ngOnInit() {
      const stream2$ = this.ngRedux.select(markerSelector);
      stream2$.subscribe(m => {
        console.log("#2 Marker updated! m: " + m);
      });
    }
  • Select by property Name.

    TypeScript
    ngOnInit() {
      const stream3$ = this.ngRedux.select('marker');
      stream3$.subscribe(m => {
        console.log("#3 Marker updated! m: " + m);
      });
    }

    If you have a deeper structure pass it as an array

    TypeScript
    const bar$ = this.ngRedux.select(['foo', 'bar']);
  • Select via @select annotation.

    TypeScript
    @select(['marker'])
    readonly stream4$: Observable<string>;
    ...
    ngOnInit() {
     this.stream4$.subscribe(m => {
        console.log("#4 Marker updated! m: " + m);
      });
    }

Display Updates in UI

Here are also few ways to display your updates in UI:

  • Update property in subscribe handler

    * app.component.html

    <h2>Marker: {{marker}}</h2>
    

    * app.component.ts

    TypeScript
    public marker: string;
    ...
    ngOnInit() {
      const marker$ = this.ngRedux.select<string>('marker');
      marker$.subscribe(newMarker => this.marker = newMarker);
    }
  • Use | async pipe in template.
    It will automatically subscribe to your Observale stream and retrieve the values.

    * app.component.html

    <h2>Marker: {{marker$ | async}}</h2>
    

    * app.component.ts

    TypeScript
    @select(['marker'])
    readonly marker$: Observable<string>;

Conclusions

  • Use the @selector annotation with readonly access modifier
  • Bind UI directly with Observable property through | async pipe
  • Use Types everywhere.

see Also


2 comments: