dev_to_angular_ngrxbest_practices_for_enterprise_angular_app
dev_to_angular_ngrxbest_practices_for_enterprise_angular_app
Posted on Feb 4, 2019 • Updated on Jun 12, 2019 • Originally published at wesleygrimes.com
16 3
Background
The following represents a pattern that I’ve developed at my day job after building several enterprise
Angular applications using the NgRx library. I have found that most online tutorials do a great job of
helping you to get your store up and running, but often fall short of illustrating best practices for clean
separation of concerns between your store feature slices, root store, and user interface.
With the following pattern, your root application state, and each slice (property) of that root application
state are separated into a RootStoreModule and per feature MyFeatureStoreModule.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Prerequisites
This article assumes that you are building an Angular v6 CLI generated application.
Suggested Implementation
1. Generate RootStoreModule using the Angular CLI:
2. Generate RootState interface to represent the entire state of your application using the Angular CLI:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
ng g interface root-store/root-state
This will create an interface named RootState but you will need to rename it to State inside the
generated .ts file as we want to, later on, utilize this as RootStoreState.State
PLEASE NOTE: You will come back later on and add to this interface each feature module as a
property.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
17 }
18
19 export class LoadSuccessAction implements Action {
20 readonly type = ActionTypes.LOAD_SUCCESS;
21 constructor(public payload: { items: MyModel[] }) {}
22 }
23
24 export type Actions = LoadRequestAction | LoadFailureAction | LoadSuccessAction;
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
17 error: null
18 });
19 }
20 case ActionTypes.LOAD_FAILURE: {
21 return {
22 ...state,
23 isLoading: false,
24 error: action.payload.error
25 };
26 }
27 default: {
28 return state;
29 }
30 }
31 }
1 import {
2 createFeatureSelector,
3 createSelector,
4 MemoizedSelector
5 } from '@ngrx/store';
6
7 import { MyModel } from '../models';
8
9 import { featureAdapter, State } from './state';
10
11 export const getError = (state: State): any => state.error;
12
13 export const getIsLoading = (state: State): boolean => state.isLoading;
14
15 export const selectMyFeatureState: MemoizedSelector<
16 object,
17 State
18 > = createFeatureSelector<State>('myFeature');
19
20 export const selectAllMyFeatureItems: (
21 state: object
22 ) => MyModel[] = featureAdapter.getSelectors(selectMyFeatureState).selectAll;
23
24 export const selectMyFeatureById = (id: string) =>
25 createSelector(this.selectAllMyFeatureItems, (allMyFeatures: MyModel[]) => {
26 if (allMyFeatures) {
27 return allMyFeatures.find(p => p.id === id);
28 } else {
29 return null;
30 }
31 });
32
33 export const selectMyFeatureError: MemoizedSelector<object, any> = createSelector(
34 selectMyFeatureState,
35 getError
36 );
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
37
38 export const selectMyFeatureIsLoading: MemoizedSelector<
39 object,
40 boolean
41 > = createSelector(selectMyFeatureState, getIsLoading);
6. Effects — Create an effects.ts file in the app/root-store/my-feature-store directory with the following:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
1 import { Action } from '@ngrx/store';
2 import { User } from '../../models';
3
4 export enum ActionTypes {
5 LOGIN_REQUEST = '[My Feature] Login Request',
6 LOGIN_FAILURE = '[My Feature] Login Failure',
7 LOGIN_SUCCESS = '[My Feature] Login Success'
8 }
9
10 export class LoginRequestAction implements Action {
11 readonly type = ActionTypes.LOGIN_REQUEST;
12 constructor(public payload: { userName: string; password: string }) {}
13 }
14
15 export class LoginFailureAction implements Action {
16 readonly type = ActionTypes.LOGIN_FAILURE;
17 constructor(public payload: { error: string }) {}
18 }
19
20 export class LoginSuccessAction implements Action {
21 readonly type = ActionTypes.LOGIN_SUCCESS;
22 constructor(public payload: { user: User }) {}
23 }
24
25 export type Actions = LoginRequestAction | LoginFailureAction | LoginSuccessAction;
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
7 return {
8 ...state,
9 error: null,
10 isLoading: true
11 };
12 case ActionTypes.LOGIN_SUCCESS:
13 return {
14 ...state,
15 user: action.payload.user,
16 error: null,
17 isLoading: false,
18
19 };
20 case ActionTypes.LOGIN_FAILURE:
21 return {
22 ...state,
23 error: action.payload.error,
24 isLoading: false
25 };
26 default: {
27 return state;
28 }
29 }
30 }
1 import {
2 createFeatureSelector,
3 createSelector,
4 MemoizedSelector
5 } from '@ngrx/store';
6
7 import { User } from '../../models';
8
9 import { State } from './state';
10
11 const getError = (state: State): any => state.error;
12
13 const getIsLoading = (state: State): boolean => state.isLoading;
14
15 const getUser = (state: State): any => state.user;
16
17 export const selectMyFeatureState: MemoizedSelector<
18 object,
19 State
20 > = createFeatureSelector<State>('myFeature');
21
22 export const selectMyFeatureError: MemoizedSelector<object, any> = createSelector(
23 selectMyFeatureState,
24 getError
25 );
26
27 export const selectMyFeatureIsLoading: MemoizedSelector<
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
28 object,
29 boolean
30 > = createSelector(selectMyFeatureState, getIsLoading);
31
32 export const selectMyFeatureUser: MemoizedSelector<
33 object,
34 User
35 > = createSelector(selectMyFeatureState, getUser);
6. Effects — Create an effects.ts file in the app/root-store/my-feature-store directory with the following:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
1. Update the app/root-store/my-feature-store/my-feature-store.module.ts with the following:
2. Create an app/root-store/my-feature-store/index.ts barrel export. You will notice that we import our
store components and alias them before re-exporting them. This, in essence, is “name-spacing” our
store components.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
4 export interface State {
5 myFeature: MyFeatureStoreState.State;
6 myOtherFeature: MyOtherFeatureStoreState.State;
7 }
4. Update your app/root-store/root-store.module.ts by importing all feature modules, and importing the
following NgRx modules: StoreModule.forRoot({}) and EffectsModule.forRoot([]):
5. Create an app/root-store/selectors.ts file. This will hold any root state level selectors, such as a
Loading property, or even an aggregate Error property:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
22 MyFeatureStoreSelectors.selectMyFeatureIsLoading,
23 MyOtherFeatureStoreSelectors.selectMyOtherFeatureIsLoading,
24 (myFeature: boolean, myOtherFeature: boolean) => {
25 return myFeature || myOtherFeature;
26 }
27 );
6. Create an app/root-store/index.ts barrel export for your store with the following:
1. Add RootStoreModule to your application’s NgModule.imports array. Make sure that when you import
the module to pull from the barrel export:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
24 this.myFeatureItems$ = this.store$.select(
25 MyFeatureStoreSelectors.selectAllMyFeatureItems
26 );
27
28 this.error$ = this.store$.select(
29 MyFeatureStoreSelectors.selectUnProcessedDocumentError
30 );
31
32 this.isLoading$ = this.store$.select(
33 MyFeatureStoreSelectors.selectUnProcessedDocumentIsLoading
34 );
35
36 this.store$.dispatch(
37 new MyFeatureStoreActions.LoadRequestAction()
38 );
39 }
40 }
├── 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
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
│ │ ├── 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
Github
wesleygrimes / angular-ngrx-chuck-norris
Chuck Norris Joke Generator w/ NgRx Store
Ultimate Courses
Development server
Run ng serve for a dev server. Navigate to http://localhost:4200/ . The app will automatically reload if you change any of the
source files.
Code scaffolding
Run ng generate component component-name to generate a new component. You can also use ng generate
directive|pipe|service|class|guard|interface|enum|module .
Build
View on GitHub
Stackblitz
You can see the live demo at https://angular-ngrx-chuck-norris.stackblitz.io and here is the Stackblitz
editor:
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
angular-ngrx-chuck-norris - StackBlitz
_NgRx _Best_Practices_Chuck_Norris_Example_stackblitz.com
Conclusion
It’s important to remember that I have implemented these best practices in several “real world”
applications. While I have found these best practices helpful, and maintainable, I do not believe they
are an end-all-be-all solution to organizing NgRx projects; it’s just what has worked for me. I am curious
as to what you all think? Please feel free to offer any suggestions, tips, or best practices you’ve learned
when building enterprise Angular applications with NgRx and I will update the article to reflect as such.
Happy Coding!
Additional Resources
I would highly recommend enrolling in the Ultimate Angular courses, especially the NgRx course. It is
well worth the money and I have used it as a training tool for new Angular developers. Follow the link
below to signup.
Ultimate Courses: Expert online courses in JavaScript, Angular, NGRX and TypeScript
_Expert online courses in JavaScript, Angular, NGRX and TypeScript. Join 50,000 others mastering new
technologies with…_ultimatecourses.com
👋 Before you go
Please leave your appreciation by commenting on this post!
Get started
• Feb 14 '19
Hello,
I have two features in my store: Auth and Itsm. After a successful connection with Auth, I want
to send the first action of Itsm. But there is a circular dependency.
I think I have built all the stores by following the tutorial. If I use the router directly in Auth-
Effects, it works well.
Do you have an example with two features and one calling the second?
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Thank you for this tutorial which is what I needed.
Daniel
• Feb 14 '19
Great Question Daniel! You should be able to dispatch actions in other features from effects.
Is this not working?
• Feb 14 '19
Hi
I have built AuthStoreModule and ItsmStoreModule with the same plan - from tutorial.
They are both injected in RootStoreModule.
I get "WARNING in Circular dependency detected:" when in auth-store/effects I want to
"this.store.dispatch(new ItsmStoreActions.ItsmBeginLoadingRequests())"
Complete error is
WARNING in Circular dependency detected:
src\app\root-store\root-store.module.ts -> src\app\root-store\auth-store\auth-
store.module.ts -> src\app\root-store\auth-store\effects.ts -> src\app\root-store\index.ts -
> src\app\root-store\root-store.module.ts
2 Thread
• Feb 14 '19
Can you show me how you're importing the ItsmStoreActions? Like what does you ts
import statement look like?
1 Thread
• Feb 14 '19
If you can, post a repo on stackblitz or github recreating the issue. Then we can resolve
together. Thanks!!
1 Thread
• Feb 14 '19
I have sent a screenshot with organization of files and the code of ItsmActions import.
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
The import is only in the itsm-store/index.ts ?
2 Thread
• Feb 14 '19
you may have to directly import the itsm-store/actions.ts to avoid the duplicate/circular
imports. When I get off work I will look into this more.
Thanks for the feedback Daniel! We will get this figured out. And I will add some notes to
the article so that others don't stumble on this issue.
1 Thread
• Feb 14 '19
I can't publish all the code to a github repo, it's too big. I will build a little appli with the
essential of the code.
It's a good idea, so I will test the Store without all the views and the backend.
2 Thread
• Feb 14 '19
1 Thread
• Feb 14 '19
I have forked your project and added a second feature store: hoax ;-)
joke-store/effects can only return action of type koke-actions/actions. So I have imported
the main hoax actions and that is running, even if the list of jokes is not proposed.
This is not practical nor clean because I will have about 10-12 main features in the menu
after login (AUTH).
With this technique, I need to import all the initial actions of all features into all feature
actions. 12 * 12 imports ...
2 Thread
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
• Feb 15 '19
I will go from one menu / item to another directly with the router. And the first step after
that will be to activate the feature store. It works well.
As we must be able to install this on-site application with all or only a few features, this
architecture will be suitable.
2 Thread
• Feb 15 '19
I plan on updating my article soon to accommodate for lazy loading feature stores, I will
also address dispatching actions between feature stores
3 Thread
• Feb 15 '19
Lazy loading is easy, we have just to move the featureModules from AppModule in their
corresponding module.
In DevRedux we can see a new line with 'update-reducers'
3 Thread
• Feb 15 '19
Agreed.
AWS PROMOTED
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF
Tackle challenges with the AWS security community
Join AWS Security LIVE! for an in-depth look at how to secure your AWS environment. Engage
with security pros, participate in live discussions, and enhance your knowledge.
Learn More
Read next
Introduction to Micro-frontend
Ratan Singh - Aug 30
Explore our developer-friendly HTML to PDF API Printed using PDFCrowd HTML to PDF