NgRx

November 28, 2018 · View on GitHub

La librería NgRx nos ayuda a utilizar Redux en nuestro proyecto de Angular.

Instalación

Para utilizar NgRx, necesitamos isntalar las propias librerías más algunas herramientas de desarrollo que añaden potencia al CLI de Angular.

npm install @angular-devkit/schematics --save-dev

npm install @ngrx/schematics --save-dev

ng config cli.defaultCollection @ngrx/schematics

npm install @ngrx/store --save

npm install @ngrx/store-devtools --save

npm install @ngrx/router-store --save

Comandos

Ahora con el CLI de Angular podemos hacer más cosas:

  • Generar el store raíz:

ng g st RootState ‐‐root ‐m app.module.ts ‐‐spec false

  • Generar el reductor y las acciones y el interface para una funcionalidad concreta:

cd .\src\app\reducers\

ng g r Hero ‐‐spec false ‐r index.ts

ng g a Hero ‐‐spec false

Adaptación a la librería

### En el módulo

import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store‐devtools';
import { metaReducers, reducers } from './reducers';
@NgModule({
  imports: [
    StoreModule.forRoot(rootReducers, { metaReducers }),
    StoreRouterConnectingModule.forRoot({ stateKey: 'router' }),
    !environment.production ? StoreDevtoolsModule.instrument() : []
  ]
})

### State

Lo primero que haremos será definir un interfaz para nuestro state. Se suele llamar State pero lo llamaré RootState en este documento.

export interface RootState {
  router: any;
  global: GlobalState;
  cars: CarsState;
}

### reducers

La librería necesita un mapeo entre cada propiedad del state y la función reductora asociada:

export const rootReducers: ActionReducerMap<RootState> = {
  router: routerReducer,
  global: globalreducer,
  cars: carsReducer
};

Las funciones reductoras estarán en un fichero a parte. No deben estar incluidas en ninguna clase.

export function globalReducer(state = initialState, action: GlobalActions): GlobalState {
  switch (action.type) {
    case GlobalActionTypes.SendUserMessage:
      return { ...state, userMessage: action.payload };
    case GlobalActionTypes.IsLoginNeeded:
      return { ...state, loginNeeded: action.payload };
    case GlobalActionTypes.StoreToken:
      return { ...state, token: action.payload };
    default:
      return state;
  }
}

Actions

export enum GlobalActionTypes {
  SendUserMessage = '[Global] Show Message',
  IsLoginNeeded = '[Auth] Is Login Needed',
  StoreToken = '[Auth] Store Token'
}
export class SendUserMesage implements Action {
  readonly type = GlobalActionTypes.SendUserMessage;
  constructor(public payload: string) {}
}
export type GlobalActions = SendUserMesage | IsLoginNeeded | StoreToken;

Index y reducers

import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import * as fromCar from './car.reducer';
export interface State {
  car: fromCar.State;
}
export const reducers: ActionReducerMap<State> = {
  car: fromCar.reducer
};
export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];

Feature

import { CarActions } from './car.actions';
export interface State {
  speed: number;
}
export const initialState: State = {
  speed: 0
};
export function reducer(state = initialState, action:CarActions ):State {
  switch (action.type) {
    case CarActionTypes.Brake:
      state.speed‐‐;
      return { ...state };
    case CarActionTypes.Throttle:
      state.speed++;
      return { ...state };
    default:
      return state;
    }
  }
}

dispatch y select

constructor(private store: Store<RootState>) {
  this.store.dispatch(new SendUserMesage('Tutorial Angular en Español'));
}
this.store
  .select('global', 'userMessage')
  .subscribe(userMessage: string => console.log(userMessage));

Acciones

import { Action } from '@ngrx/store';
export enum CarActionTypes {
  CarAction= '[Car] CarAction'
}
export class CarAction implements Action {
  readonly type = CarActionTypes.CarAction;
}
export type CarActions = CarAction;

Dispatch y select

export class CarContainerComponent implements OnInit {

  public motor = { speed: 5 };

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

  ngOnInit = () => this.store.select('car').subscribe(value => (this.motor = value));

  onBrake = () => this.store.dispatch(new Brake());

  onThrottle = () => this.store.dispatch(new Throttle());
}

Redux DevTools

http://extension.remotedev.io/

StoreDevtoolsModule.instrument()

Ejercicio

Realizar el ejercicio anterior utilizando la librería NgRx

Ejercicio resuelto en el módulo redux.