Skip to content

BrianJVarley/Angular-NgRx-Prototyping

Repository files navigation

Angular-ngrx-GettingStarted

Forked from Angular & NgRx course

Some examples of using @ngrx in Angular and how to strongly type the state, reducer, actions within an application

Strongly Typing Stuff:

Pretty much assign a type to your state and actions to enforce typing..See usage-with-typescript

import { Product } from '../product';

/* NgRx */
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { ProductActions, ProductActionTypes } from './product.actions';
import * as fromRoot from '../../state/app.state';

// Extends the app state to include the product feature.
// This is required because products are lazy loaded.
// So the reference to ProductState cannot be added to app.state.ts directly.
export interface State extends fromRoot.State {
  products: ProductState;
}

// State for this feature (Product)
export interface ProductState {
  showProductCode: boolean;
  currentProduct: Product;
  products: Product[];
}

const initialState: ProductState = {
  showProductCode: true,
  currentProduct: null,
  products: []
};

export function reducer(state = initialState, action: ProductActions): ProductState {

  switch (action.type) {
    case ProductActionTypes.ToggleProductCode:
      return {
        ...state,
        showProductCode: action.payload
      };

    default:
      return state;
  }
}

Creating Actions:

Define an enum to type the actions available for your state. This gives good feedback when dispatching actions so that you supply the correct payload types and call the correct actions.

Each action implements a standard type from @ngrx/store called an Action which enforces the type checking on the actions. At the end of the actions file we export the actions as a union export type ProductActions = ToggleProductCode | SetCurrentProduct; so that the action types show in intellisense on the editor.

import { Product } from '../product';

/* NgRx */
import { Action } from '@ngrx/store';

export enum ProductActionTypes {
  ToggleProductCode = '[Product] Toggle Product Code',
  SetCurrentProduct = '[Product] Set Current Product'
}

// Action Creators
export class ToggleProductCode implements Action {
  readonly type = ProductActionTypes.ToggleProductCode;

  constructor(public payload: boolean) { }
}

export class SetCurrentProduct implements Action {
  readonly type = ProductActionTypes.SetCurrentProduct;

  constructor(public payload: Product) { }
}

export type ProductActions = ToggleProductCode
  | SetCurrentProduct;

Dispatching Actions:

The actions are dispatched via the store and created from the exported actions object. Since we typed the actions we get type checking on the action payload and actions types.

/* NgRx */
import { Store, select } from '@ngrx/store';
import * as fromProduct from '../state/product.reducer';
import * as productActions from '../state/product.actions';

....

this.store.dispatch(new productActions.ToggleProductCode(value));

Using @ngrx/effects:

The NgRx effects decorator handles side effects from a service. For example a service that makes async HTTP request. This really helps to keep components pure and decrease the responsibility of a component.

The @ngrx/effects are similar to redux-saga library commonly used in React applications to handle side effects.

This example ProductEffects class demonstrates listening for the loadProducts action and dispatches a side effect action LoadSuccess or LoadFail depending on the result of the loadProducts action.

import { Injectable } from '@angular/core';

import { Observable, of } from 'rxjs';
import { mergeMap, map, catchError } from 'rxjs/operators';

import { ProductService } from '../product.service';

/* NgRx */
import { Action } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects';
import * as productActions from './product.actions';

@Injectable()
export class ProductEffects {

  constructor(private productService: ProductService,
              private actions$: Actions) { }

  @Effect()
  loadProducts$: Observable<Action> = this.actions$.pipe(
    ofType(productActions.ProductActionTypes.Load),
    mergeMap(action =>
      this.productService.getProducts().pipe(
        map(products => (new productActions.LoadSuccess(products))),
        catchError(err => of(new productActions.LoadFail(err)))
      )
    )
  );

}

Defining State Selectors:

Selectors are pure functions used for obtaining slices of store state. @ngrx/store provides a few helper functions for optimizing this selection. Selectors provide many features when selecting slices of state.

  • Portable
  • Memoization
  • Composition
  • Testable
  • Type-safe
// Selector functions
const getProductFeatureState = createFeatureSelector<ProductState>('products');

export const getShowProductCode = createSelector(
  getProductFeatureState,
  state => state.showProductCode
);

Debugging with Redux Dev Tools: Install the Chrome Devtools extension F12 in browser and open Redux tab, where you can view sequence of each action dispatched and the state tree changes.