Using ngrx store freeze

  • The problem with mutability is that if we do it in the reducer, there won’t be any error, but the store subscribers and selectors won’t be triggered. Hence it’s imp. to enforce immutability of store, so that mutating throws compile time error allowing us to fix it and make reducers pure functions and store immutable.
  • This is not recommended in prod, as it uses Object.freeze, which is not supported by all browsers.

Slides:

code changes:

import { storeFreeze } from 'ngrx-store-freeze';
StoreModule.provideStore(compose(storeFreeze, reducer))

ref: https://angular-university.io/lesson/angular-ngrx-immutability-in-reducer-functions-how-to-use-the-ngrx-store-freeze-library

Types of State in Redux Store

There are 3 types of State in any App/Component:

  • Application State
    • Peristant Stuff that multiple components care about
  • UI State
    • Ephemeral Stuff that is local to the container component and dies with it.
  • Router State
    • Routing Info of the App

Application and Routing states should Compulsorily be put in the Redux Store.

As for the UI State, it’s optional. The rule of thumb is to put it into the Store unless it’s Awkward

read more:

Reselect for Components

Slides: https://goo.gl/photos/3DWETLDtTM8j13mF6

To deal with derived data, Redux recommends to use Reselect. With ngrx/store you can use RxJS CombineLatest operator.

Let’s imagine that the following object is your application state:

{
  "users": {
    "293580923": { "username": "shprink"},
    "423948745": { "username": "byjc"},
    "435435799": { "username": "myagoo"},
    "027859645": { "username": "whoknows"},
    ...
  },
  "trendingUsers": [293580923, 435435799, ...],
  "topUsers": [423948745, 027859645]
}

To get the trendingUsers or topUsers list of users you need to merge them with the actual users objects from the users key.

CombineLatest emits an item whenever any of the source Observables emits a new item.

rx_combinelatest_operator

To get the list of trendingUsers with just CombineLatest operator, here is what you need to do:

you can easily displayed the list of trendingUsers with the async pipe:

import { Store } from '@ngrx/store';
import { Observable } from "rxjs/Observable";
import 'rxjs/add/operator/combineLatest';
@Component({
    template: `
      <div *ngFor="let user of (usersStream$ | async)">
        {{user.username}}
      </div>
    `
})
export class TrendingUsersComponent {
  usersStream$: Observable<Array<IUserState>>;
  constructor(
    public store: Store<AppState>
  ) {
    this.usersStream$ = Observable.combineLatest(
      store.select('users'),
      store.select('trendingUsers'),
      (users: IUsersState, trendingUsers: Array<number>)
        => trendingUsers.map(id => users[id]))
  }
}

You don’t need reselect

The select() method will give you an observable that calls distinctUntilChanged() internally, meaning they will only fire when the state actually changes. ( it means when there is a new reference ).

Redux Principles

  • Store is the Single source of truth
  • State is read-only
  • Mutations to the State are made with pure functions using Reducers
    • A reucer is like a property accessor (getter/settor), that sets the value of the property depending on the action, so if there is a boolean value in the store, called visibility, then the visibilityReducer will have a switch case for all the 20 different ACTIONS that it has to react to, and then will update the visibility property as per the logic.
  • Action is the representation of the change to the State
  • UI Should be a Pure Function of the Application State
  • Use Reducer Composition to maintain separation of concerns when updating State. For Eg. An ADD_TODO action is composed of Creating a new TODO item and adding it to TODO Array. These 2 things should be done by a different Reducers, the first which doesn’t need any state and simply returns new TODO object and the second reducer that actually adds this new TODO to the State.
  • The State of the App is formed by composing the states managed/returned by different reducers. This composition is managed by the Root Reducer
    • Eg. The State of ToDo App with Visibility Filters (ShowCompleted, ShowAll etc.) is formed by the root App reducer that returns the state created when the Action is passed to both todos reducer (further being composed of todo reducer) and the visibilityFilter Reducer that it comprises of:
    • Capture.JPG
    • This whole thing above can be replaced by helper function combineReducers() and the shorthand ES6 notation (if the reducer name and the state prop they manage have the same name):
    • Capture.JPG
    • Here “filter” is a property in the state that this action wants to set.
    • Capture.JPG
    • So basically the “SET_VISIBILITY_FILTER” Action is passed to both ToDos and VisibilityFilter reducer, which handle it separately, coz they manage different parts of the State:
      • VisibilityFilter reducer handles just the filter property of the Store and hence just updates that.
      • ToDos Reducer uses the payload to figure out if All ToDos are to be returned or just completed ones…
    • Q: If state’s ToDos array has been updated to have just completed tasks, then aren’t other tasks lost?
      • The Ans to this is key to local state vs Store State. Here, the visible todos are derived from all todos that come down from the Store.
      • This derivation happens locally in the container component, and is stored as local state, and is NEVER stored in the Store. In fact there isn’t even any action or reducer associated with this logic, coz it doesn’t touch the store at all.
    • Note that Actions need to be high level, for instance a TODO app with filters (Completed, All etc.) will have only these actions:
      • Add Todo
      • Toggle Todo
      • Filter Todos (Note that every filter is not an action in itself, instead,the filter type is passed in the payload of this action)

 

Ref: https://egghead.io/courses/getting-started-with-redux

Slides: https://goo.gl/photos/w6YpgsqNeEx9Ev7n6