Tuesday, May 28, 2019

boilerplate bakeout of the NgRx store stuff for a new object in an Angular 7 application

Alright, at the store there already was a user and we are adding businessUnitRecords with the following files:

  1. state-management/actions/business-unit-record.actions.ts

    import { Action } from '@ngrx/store';
    import { BusinessUnitRecordModel } from '../../models/business-unit-
          record.model';
    import { BusinessUnitRecordUpdatePayload } from 'src/app/models/business-unit-record-update-
          payload.model';
     
    export enum BusinessUnitRecordActionTypes {
       LoadBusinessUnitRecords = '[BusinessUnitRecord] Load Business Unit
             Records',
       LoadBusinessUnitRecordsSuccess = '[BusinessUnitRecord] Load Business Unit
             Records Success',
       LoadBusinessUnitRecordsFail = '[BusinessUnitRecord] Load Business Unit
             Records Failure',
       UpdateBusinessUnitRecord = '[BusinessUnitRecord] Update Business Unit
             Record'
    }
     
    export class LoadBusinessUnitRecords implements Action {
       readonly type = BusinessUnitRecordActionTypes.LoadBusinessUnitRecords;
       constructor() {}
    }
     
    export class LoadBusinessUnitRecordsSuccess implements Action {
       readonly type = BusinessUnitRecordActionTypes
             .LoadBusinessUnitRecordsSuccess;
       constructor(public payload: Array<BusinessUnitRecordModel>) {
          
       }
    }
     
    export class LoadBusinessUnitRecordsFail implements Action {
       readonly type = BusinessUnitRecordActionTypes
             .LoadBusinessUnitRecordsFail;
       constructor(public payload: string) {}
    }
     
    export class UpdateBusinessUnitRecord implements Action {
       readonly type = BusinessUnitRecordActionTypes
             .UpdateBusinessUnitRecord;
       constructor(public payload: BusinessUnitRecordUpdatePayload) {
          
       }
    }
     
    export type BusinessUnitRecordActions = LoadBusinessUnitRecords |
          LoadBusinessUnitRecordsSuccess | LoadBusinessUnitRecordsFail |
          UpdateBusinessUnitRecord;
     
     
  2. state-management/effects/business-unit-record.effect.ts

    import { Injectable } from '@angular/core';
    import { Actions, Effect, ofType } from '@ngrx/effects';
    import { Store, Action, select } from '@ngrx/store';
    import { Observable, of } from 'rxjs';
    import { mergeMap, map, catchError, tap, switchMap } from 'rxjs/operators';
    import { IMainStore } from '../store/store';
    import * as fromBusinessUnitRecords from '../actions/business-unit-
          record.actions';
    import { BusinessUnitRecordsContract } from '../../contracts/business-unit-
          records.contract';
    import { BusinessUnitRecordModel } from '../../models/business-unit-
          record.model';
     
    @Injectable()
    export class BusinessUnitRecordEffects {
       constructor(
          private businessUnitRecordsContract: BusinessUnitRecordsContract,
          private actions$: Actions<fromBusinessUnitRecords
                .BusinessUnitRecordActions>,
          private store: Store<IMainStore>
       ) {
       
       }
     
       @Effect()
       loadBusinessUnitRecords$: Observable<Action> = this.actions$.pipe(
          ofType(fromBusinessUnitRecords.BusinessUnitRecordActionTypes
                .LoadBusinessUnitRecords),
          mergeMap((a, i) => {
          return this.businessUnitRecordsContract.GetBusinessUnitRecordsObj().pipe(
             map(businessUnitRecords => new fromBusinessUnitRecords
                   .LoadBusinessUnitRecordsSuccess(businessUnitRecords)
             ),
             catchError((err, ii) => {
             return of(new
                   fromBusinessUnitRecords.LoadBusinessUnitRecordsFail(err.message));
             })
          )
          })
       );
    }
     
     
  3. state-management/reducers/business-unit-record.reducer.ts

    import { BusinessUnitRecordStore } from '../store/store';
    import { BusinessUnitRecordActions, BusinessUnitRecordActionTypes } from
          '../actions/business-unit-record.actions';
    import { IBusinessUnitRecordStore } from '../../models/business-unit-
          record.model';
    export function BusinessUnitRecordReducer(
       state = BusinessUnitRecordStore,
       action: BusinessUnitRecordActions
    ) : IBusinessUnitRecordStore {
       switch(action.type){
          case BusinessUnitRecordActionTypes.LoadBusinessUnitRecordsSuccess:
             return {
                ...state,
                businessUnitRecords: action.payload
             };
     
          case BusinessUnitRecordActionTypes.UpdateBusinessUnitRecord:
             action.payload.businessUnitRecordsContract.UpdateBusinessUnitRecord(
                action.payload.businessUnitRecordModel,
                action.payload.whatToDo,
                action.payload.error
             );
             return {
                ...state,
                businessUnitRecords: state.businessUnitRecords.map(bu => {
                   if (bu.BusinessUnitID == action.payload
                         .businessUnitRecordModel.BusinessUnitID) {
                      bu.isProcessing = action.payload
                         .businessUnitRecordModel.isProcessing;
                   }
                   return bu;
                })
             };
     
          default: {
             return state;
          }
       }
    }
     
     
  4. state-management/selectors/business-unit-record.selector.ts

    import { createFeatureSelector, createSelector } from '@ngrx/store';
    import { IBusinessUnitRecordStore } from '../../models/business-unit-
          record.model';
    import { IMainStore } from '../store/store';
     
    export const getBusinessUnitFeatureState = createFeatureSelector<IMainStore,
          IBusinessUnitRecordStore>('businessUnitRecords');
     
    export const getAllBusinessUnits = createSelector(
       getBusinessUnitFeatureState,
       state => state.businessUnitRecords
    );
     
     
  5. state-management/store/store.ts

    import { IUserStore } from '../../models';
    import { IBusinessUnitRecordStore } from '../../models/business-unit-
          record.model';
     
    export interface IMainStore {
       businessUnitRecords: IBusinessUnitRecordStore,
       user: IUserStore
    }
     
    export const UserStore: IUserStore = {
       user: null
    }
     
    export const BusinessUnitRecordStore: IBusinessUnitRecordStore = {
       businessUnitRecords: null
    }
     
     

Alright, in order to use this stuff at a component, you'll want a constructor that starts out like this.

constructor(public store: Store<IMainStore>

 
 

Put records in the store if they are not yet there as follows (or otherwise, just update the store from the database).

this.store.dispatch(new LoadBusinessUnitRecords());

 
 

Pull records from the store like so:

this.store.select(getAllBusinessUnits).subscribe(businessUnitRecords => {
   this.businessUnitRecords = businessUnitRecords;
})

 
 

Write back a record:

this.store.dispatch(new
      UpdateBusinessUnitRecord(businessUnitRecordUpdatePayload));

 
 

My write back is kinda bad as it assumes that the call to the API will succeed and then immediately calls out to the store.

 
 

Addendum 6/13/2019: Something that is missing is the loop in of the effects at a module. See: this

 
 

Addendum 12/26/2019: In looking at what is above I realize there is no spec for IBusinessUnitRecordStore. It would have to look something like so:

export interface IBusinessUnitRecordStore {
   businessUnitRecords: Array<BusinessUnitRecord>;
}

 
 

StoreModule.forRoot({ models: BusinessUnitRecordReducer }), probably also needs to go in the imports in the module before the EffectsModule import. Import that up top from @ngrx/store to get it afloat. BusinessUnitRecordUpdatePayload is going to look like so:

export class BusinessUnitRecordUpdatePayload {
   model: BusinessUnitRecord;
   whatToDo: string;
   error: string;
}

No comments:

Post a Comment