Before we get started with generating code, let’s make sure to install the necessary NgRx node modules from a prompt:
$ npm install @ngrx/{store,store-devtools,entity,effects}
step1:Suggested Implementation
1.GenerateRootStoreModule using the Angular CLI:$ ng g module root-store --flat false --module app.module.ts
2.Generate RootState interface to represent the entire state of your application using the Angular CLI:$ ng g interface root-store/root-state
step 2:Suggested Implementation — Entity Feature Module
1.GenerateMyFeatureStoreModule feature module using the Angular CLI:$ ng g module root-store/my-feature-store --flat false --module root-store/root-store.module.ts
actions.ts file in the app/root-store/my-feature-store directory:import { Action } from @ngrx/store;
import { MyModel } from '../../models';
export enum ActionTypes {
LOAD_REQUEST = '[My Feature] Load Request',
LOAD_FAILURE = '[My Feature] Load Failure',
LOAD_SUCCESS = '[My Feature] Load Success'
}
export class LoadRequestAction implements Action {
readonly type = ActionTypes.LOAD_REQUEST;
}
export class LoadFailureAction implements Action {
readonly type = ActionTypes.LOAD_FAILURE;
constructor(public payload: { error: string }) {}
}
export class LoadSuccessAction implements Action {
readonly type = ActionTypes.LOAD_SUCCESS;
constructor(public payload: { items: MyModel[] }) {}
}
export type Actions = LoadRequestAction | LoadFailureAction | LoadSuccessAction;
3.State — Create a
state.ts file in the app/root-store/my-feature-store directory:import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
import { MyModel } from '../../models';
export const featureAdapter: EntityAdapter<MyModel> = createEntityAdapter<
MyModel
>({
selectId: model => model.id,
sortComparer: (a: MyModel, b: MyModel): number =>
b.someDate.toString().localeCompare(a.someDate.toString())
});
export interface State extends EntityState<MyModel> {
isLoading?: boolean;
error?: any;
}
export const initialState: State = featureAdapter.getInitialState({
isLoading: false,
error: null
4.Reducer — Create a
reducer.ts file in the app/root-store/my-feature-store directory:import { Actions, ActionTypes } from './actions';
import { featureAdapter, initialState, State } from './state';
export function featureReducer(state = initialState, action: Actions): State {
switch (action.type) {
case ActionTypes.LOAD_REQUEST: {
return {
...state,
isLoading: true,
error: null
};
}
case ActionTypes.LOAD_SUCCESS: {
return featureAdapter.addAll(action.payload.items, {
...state,
isLoading: false,
error: null
});
}
case ActionTypes.LOAD_FAILURE: {
return {
...state,
isLoading: false,
error: action.payload.error
};
}
default: {
return state;
}
}
}
5.Selectors — Create a
selectors.ts file in the app/root-store/my-feature-store directory:import {
createFeatureSelector,
createSelector,
MemoizedSelector
} from '@ngrx/store';
import { MyModel } from '../models';
import { featureAdapter, State } from './state';
export const getError = (state: State): any => state.error;
export const getIsLoading = (state: State): boolean => state.isLoading;
export const selectMyFeatureState: MemoizedSelector<
object,
State
> = createFeatureSelector<State>('myFeature');
export const selectAllMyFeatureItems: (
state: object
) => MyModel[] = featureAdapter.getSelectors(selectMyFeatureState).selectAll;
export const selectMyFeatureById = (id: string) =>
createSelector(
this.selectAllMyFeatureItems,
(allMyFeatures: MyModel[]) => {
if (allMyFeatures) {
return allMyFeatures.find(p => p.id === id);
} else {
return null;
}
}
);
export const selectMyFeatureError: MemoizedSelector<
object,
any
> = createSelector(
selectMyFeatureState,
getError
);
export const selectMyFeatureIsLoading: MemoizedSelector<
object,
boolean
> = createSelector(
selectMyFeatureState,
getIsLoading
);
6.Effects — Create an
effects.ts file in the app/root-store/my-feature-store directory with the following:import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable, of as observableOf } from 'rxjs';
import { catchError, map, startWith, switchMap } from 'rxjs/operators';
import { DataService } from '../../services/data.service';
import * as featureActions from './actions';
@Injectable()
export class MyFeatureStoreEffects {
constructor(private dataService: DataService, private actions$: Actions) {}
@Effect()
loadRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType<featureActions.LoadRequestAction>(
featureActions.ActionTypes.LOAD_REQUEST
),
startWith(new featureActions.LoadRequestAction()),
switchMap(action =>
this.dataService.getItems().pipe(
map(
items =>
new featureActions.LoadSuccessAction({
items
})
),
catchError(error =>
observableOf(new featureActions.LoadFailureAction({ error }))
)
)
)
);
}
Wiring up the Root Store Module to your Application component
Now that we have built our Root Store Module, composed of Feature Store Modules, let’s add it to the main
1.Add app.module.ts and show just how neat and clean the wiring up process is.RootStoreModule to your application’s NgModule.imports array. Make sure that when you import the module to pull from the barrel export:import { RootStoreModule } from './root-store';
2.Here’s an example container component that is using the store:import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { MyModel } from '../../models';
import {
RootStoreState,
MyFeatureStoreActions,
MyFeatureStoreSelectors
} from '../../root-store';
@Component({
selector: 'app-my-feature',
styleUrls: ['my-feature.component.css'],
templateUrl: './my-feature.component.html'
})
export class MyFeatureComponent implements OnInit {
myFeatureItems$: Observable<MyModel[]>;
error$: Observable<string>;
isLoading$: Observable<boolean>;
constructor(private store$: Store<RootStoreState.State>) {}
ngOnInit() {
this.myFeatureItems$ = this.store$.select(
MyFeatureStoreSelectors.selectAllMyFeatureItems
);
this.error$ = this.store$.select(
MyFeatureStoreSelectors.selectUnProcessedDocumentError
);
this.isLoading$ = this.store$.select(
MyFeatureStoreSelectors.selectUnProcessedDocumentIsLoading
);
this.store$.dispatch(new MyFeatureStoreActions.LoadRequestAction());
}
}
Finished Application Structure
├── app
│ ├── app-routing.module.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── components
│ ├── containers
│ │ └── my-feature
│ │ ├── my-feature.component.css
│ │ ├── my-feature.component.html
│ │ └── my-feature.component.ts
│ ├── models
│ │ ├── index.ts
│ │ └── my-model.ts
│ │ └── user.ts
│ ├── root-store
│ │ ├── index.ts
│ │ ├── root-store.module.ts
│ │ ├── selectors.ts
│ │ ├── state.ts
│ │ └── my-feature-store
│ │ | ├── actions.ts
│ │ | ├── effects.ts
│ │ | ├── index.ts
│ │ | ├── reducer.ts
│ │ | ├── selectors.ts
│ │ | ├── state.ts
│ │ | └── my-feature-store.module.ts
│ │ └── my-other-feature-store
│ │ ├── actions.ts
│ │ ├── effects.ts
│ │ ├── index.ts
│ │ ├── reducer.ts
│ │ ├── selectors.ts
│ │ ├── state.ts
│ │ └── my-other-feature-store.module.ts
│ └── services
│ └── data.service.ts
├── assets
├── browserslist
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── tslint.json