Do you have an awesome application written with Angular v7 using NgRx v7, but have been feeling left out will all the mentions online and at conferences about Angular v8 and NgRx v8? Well, you are in luck! Today we will explore together, how to upgrade our applications to use Angular v8 using the Angular CLI tooling. We will also explore upgrading to NgRx v8. This will allow us to take advantage of the new features provided in NgRx v8. Included with NgRx v8 is a shiny set of creators, or type-safe factory functions, for actions, effects, and reducers.
This article has been adapted from an original post on Ultimate Courses.
The Angular team has provided a great website that walks through the process of upgrading in-depth. This website can be found at Angular Update Tool. We will touch on some of the information today.
The first step is the process is to upgrade our application to Angular v8. We will use the Angular CLI to manage this process for us.
This is the preferred method, as Angular has provided built-in migration scripts or schematics to alleviate some of the manual process involved had we just simply updated versions in our package.json.
Let’s start by running the following command in the terminal:
Update the Global Angular CLI version
npm install -g @angular/cli
Update the core framework and local CLI to v8
ng update @angular/cli @angular/core
Throughout this process, we might encounter issues with third-party libaries. In those instances, it is best to visit the GitHub issues and repositories for those libraries for resolution.
Now that we have upgraded our application to use Angular v8, let’s proceed with updating NgRx to v8. We will make use of the Angular CLI here as well.
Update NgRx to v8
ng update @ngrx/store
The prior command should update our package.json dependencies and run any NgRx-provided migrations to keep our application in working order.
Depending on your setup, ng update @ngrx/store may not automatically update the additional @ngrx/* libraries that you have installed. If this happens, the best course is to manually run npm install for each additional module in use with NgRx.
Examples are as follows:
npm install @ngrx/entity@latest
npm install @ngrx/effects@latest
npm install @ngrx/data@latest
npm install @ngrx/router-store@latest
The NgRx team has provided a detailed migration guide for updating to NgRx v8. More information on upgrading to v8 of NgRx can be found here: V8 Update Guide
One of the most popular ways to learn new methods, is through code examples. Let’s explore the following example of a simplified NgRx store that holds an array of Fruit objects.
Each Fruit object consists of three properties fruitId, fruitClass and fruitName.
interface Fruit {
fruitId: number;
fruitType: string;
fruitName: string;
}
For example, if we had an orange, it might look something like this:
const orange: Fruit = {
fruitId: 1,
fruitType: 'citrus',
fruitName: 'orange'
};
Exploring further, our State object in the NgRx store will contain properties like fruits, isLoading, and errorMessage.
fruits is defined as an array for Fruit objectsisLoading is a boolean to keep track of when the store is in the process of loading data from an external API.errorMessage is a string property that is null unless an error has occurred while requesting data from an external API.An example State interface might look like the following:
interface State {
fruits: Fruit[];
isLoading: boolean;
errorMessage: string;
}
An example store with fruits loaded might look like the following:
const state: State = {
fruits: [
{
fruitId: 1,
fruitType: 'citrus',
fruitName: 'orange'
}
],
isLoading: false,
errorMessage: null
}
Following proper redux pattern guidance, we cannot directly update state, so we need to define a set of actions to work with our state through a reducer. Let’s imagine we have 3 actions for this example:
[App Init] Load Request - This action is intended to be dispatched from our UI layer to indicate we are requesting to load Fruit objects into our store. This action does not have a payload or props.
[Fruits API] Load Success - This action is intended to be dispatched from our effects when an [App Init] Load Request has been dispatched, an API has been called and successful response is received from the API. This action contains a payload or props object that includes the array of Fruit object to be loaded into our store.
[Fruits API] Load Failure - This action is intended to be dispatched from our effects when an [App Init] Load Request has been dispatched, an API has been called and failure response is received from the API. This action contains a payload or props object that includes the error message of our API request, so that it can be loaded into our store.
The actual NgRx v7 implementation of our actions might look something like the following:
import { Action } from '@ngrx/store';
import { Fruit } from '../../models';
export enum ActionTypes {
LOAD_REQUEST = '[App Init] Load Request',
LOAD_FAILURE = '[Fruits API] Load Failure',
LOAD_SUCCESS = '[Fruits API] 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: { fruits: Fruit[] }) {}
}
export type ActionsUnion = LoadRequestAction | LoadFailureAction | LoadSuccessAction;
It’s important to note, that while
createActionis the hot new way of defining anActionin NgRx, the existing method of defining anenum,classand exporting a type union will still work just fine in NgRx v8.
Beginning with version 8 of NgRx, actions can be declared using the new createAction method. This method is a factory function, or a function that returns a function.
According to the official NgRx documentation, “The createAction function returns a function, that when called returns an object in the shape of the Action interface. The props method is used to define any additional metadata needed for the handling of the action. Action creators provide a consistent, type-safe way to construct an action that is being dispatched.”
In order to update to createAction, we need to do the following steps:
export const for our action. If our action has a payload, we will also need to migrate to using the props method to define our payload as props.Example for [App Init] Load Request
// before
export class LoadRequestAction implements Action {
readonly type = ActionTypes.LOAD_REQUEST;
}
// after
export const loadRequest = createAction('[App Init] Load Request');
Example for [Fruits API] Load Success
// before
export class LoadSuccessAction implements Action {
readonly type = ActionTypes.LOAD_SUCCESS;
constructor(public payload: { fruits: Fruit[] }) {}
}
// after
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
Remove the old action from the ActionTypes enum
Remove the old action from the ActionsUnion
Our final migrated actions file might look something like this:
import { Action, props } from '@ngrx/store';
import { Fruit } from '../../models';
export const loadRequest = createAction('[App Init] Load Request');
export const loadFailure = createAction('[Fruits API] Load Failure', props<{errorMessage: string}>());
export const loadSuccess = createAction('[Fruits API] Load Success', props<{fruits: Fruit[]}>());
As we can see, this is a huge reduction in code, we have gone from 24 lines of code, down to 6 lines of code.
A final note is that we need to update the way we dispatch our actions. This is because we no longer need to create class instances, rather we are calling factory functions that return an object of our action.
Our before and after will look something like this:
// before
this.store.dispatch(new featureActions.LoadSuccessAction({ fruits }))
// after
this.store.dispatch(featureActions.loadSuccess({ fruits }))
Continuing with our example, we need a reducer setup to broker our updates to the store. Recalling back to the redux pattern, we cannot directly update state. We must, through a pure function, take in current state, an action, and return a new updated state with the action applied. Typically, reducers are large switch statements keyed on incoming actions.
Let’s imagine our reducer handles the following scenarios:
[App Init] Load Request we want the state to reflect the following values:
state.isLoading: truestate.errorMessage: null[Fruits API] Load Success we want the state to reflect the following values:
state.isLoading: falsestate.errorMessage: nullstate.fruits: action.payload.fruits[Fruits API] Load Failure we want the state to reflect the following values:
state.isLoading: falsestate.errorMessage: action.payload.errorMessageThe actual NgRx v7 implementation of our reducer might look something like the following:
import { ActionsUnion, ActionTypes } from './actions';
import { initialState, State } from './state';
export function featureReducer(state = initialState, action: ActionsUnion): State {
switch (action.type) {
case ActionTypes.LOAD_REQUEST: {
return {
...state,
isLoading: true,
errorMessage: null
};
}
case ActionTypes.LOAD_SUCCESS: {
return {
...state,
isLoading: false,
errorMessage: null,
fruits: action.payload.fruits
};
}
case ActionTypes.LOAD_FAILURE: {
return {
...state,
isLoading: false,
errorMessage: action.payload.errorMessage
};
}
default: {
return state;
}
}
}
It’s important to note, that while
createReduceris the hot new way of defining a reducer in NgRx, the existing method of defining afunctionwith aswitchstatement will still work just fine in NgRx v8.
Beginning with version 8 of NgRx, reducers can be declared using the new createReducer method.
According to the official NgRx documentation, “The reducer function’s responsibility is to handle the state transitions in an immutable way. Create a reducer function that handles the actions for managing the state using the createReducer function.”
In order to update to createReducer, we need to do the following steps:
const reducer = createReducer for our reducer.switch case statements into on method calls. Please note, the default case is handled automatically for us. The first parameter of the on method is the action to trigger on, the second parameter is a handler that takes in state and returns a new version of state. If the action provides props, a second optional input parameter can be provided. In the example below we will use destructuring to pull the necessary properties out of the props object.export function reducer to wrap our const reducer for AOT support.Once completed, our updated featureReducer will look something like the following:
import { createReducer, on } from '@ngrx/store';
import * as featureActions from './actions';
import { initialState, State } from './state';
...
const featureReducer = createReducer(
initialState,
on(featureActions.loadRequest, state => ({ ...state, isLoading: true, errorMessage: null })),
on(featureActions.loadSuccess, (state, { fruits }) => ({ ...state, isLoading: false, errorMessage: null, fruits })),
on(featureActions.loadFailure, (state, { errorMessage }) => ({ ...state, isLoading: false, errorMessage: errorMessage })),
);
export function reducer(state: State | undefined, action: Action) {
return featureReducer(state, action);
}
Because we want to keep our reducer a pure function, it’s often desirable to place API requests into side-effects. In NgRx, these are called Effects and provide a reactive, RxJS-based way to link actions to observable streams.
In our example, we will have an Effect that listens for an [App Init] Load Request Action and makes an HTTP request to our imaginary Fruits API backend.
Upon a successful result from the Fruits API the response is mapped to an [Fruits API] Load Success action setting the payload of fruits to the body of the successful response.
Upon a failure result from the Fruits API the error message is mapped to an [Fruits API] Load Failure action setting the payload of errorMessage to the error from the failure response.
The actual NgRx v7 implementation of our effect might look something like the following:
@Effect()
loadRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType<featureActions.LoadRequestAction>(
featureActions.ActionTypes.LOAD_REQUEST
),
concatMap(action =>
this.dataService
.getFruits()
.pipe(
map(
fruits =>
new featureActions.LoadSuccessAction({
fruits
})
),
catchError(error =>
observableOf(new featureActions.LoadFailureAction({ errorMessage: error.message }))
)
)
)
);
It’s important to note, that while
createEffectis the hot new way of defining a reducer in NgRx, the existing method of defining a class property with an@Effect()decorator will still work just fine in NgRx v8.
Beginning with version 8 of NgRx, effects can be declared using the new createEffect method, according to the official NgRx documentation.
In order to update to createEffect, we need to do the following steps:
createEffect from @ngrx/effects@Effect() decoratorObservable<Action> type annotationthis.actions$.pipe(...) with createEffect(() => ...)<featureActions.LoadRequestAction> type annotation from ofTypeofType input parameter from featureActions.ActionTypes.LOAD_REQUEST to featureActions.loadRequestnew and to use the creator instead of class instance. For example, new featureActions.LoadSuccessAction({fruits}) becomes featureActions.loadSuccess({fruits}).Once completed, our updated loadRequestEffect will look something like the following:
loadRequestEffect$ = createEffect(() => this.actions$.pipe(
ofType(featureActions.loadRequest),
concatMap(action =>
this.dataService
.getFruits()
.pipe(
map(fruits => featureActions.loadSuccess({fruits})),
catchError(error =>
observableOf(featureActions.loadFailure({ errorMessage: error.message }))
)
)
)
)
);
If you would like to watch a full video walkthrough here you go.
This brings us to the end of this guide. Hopefully, you’ve been able to learn about upgrading your application to Angular v8 and NgRx v8. In addition, you should feel confident in taking advantage of some of the new features available in NgRx v8 to reduce the occurrence of what some might refer to as boilerplate. Happy updating and upgrading!
]]>
In this article we will build a fully-functional file upload control, that is powered by Angular and is backed by an NgRx feature store. The control will provide the user with the following features:
<input #file type="file" /> HTML element.reportProgress HttpClient option.As an added bonus, we will briefly dive into building the server-side ASP.NET Core WebAPI Controller that will handle the file uploads.
In this article, I will show you how to manage file uploads using NgRx. If you are new to NgRx, then I highly recommend that you first read my article, NgRx - Best Practices for Enterprise Angular Applications. We will be using the techniques described in that article to build out the NgRx components for file uploads.
If you are new to Angular, then I recommend that you check out one of the following resources:
For context, this article assumes you are using the following npm package.json versions:
@angular/*: 7.2.9@ngrx/*: 7.3.0Before diving into building the file upload control, make sure that you have the following in place:
Let’s create a brand new service in Angular. This service will be responsible for handling the file upload from the client to the server backend. We will use the amazing HttpClient provided with Angular.
$ ng g service file-upload
Because we are using the HttpClient to make requests to the backend, we need to inject it into our service. Update the constructor line of code so that it looks as follows:
constructor(private httpClient: HttpClient) {}
API_BASE_URLI typically store
APIbase URLs in thesrc/environmentsarea. If you’re interested in learning more aboutenvironmentsinAngularthen check out this great article: Becoming an Angular Environmentalist
Let’s create a new private field named API_BASE_URL so that we can use this in our calls to the backend API.
One way to accomplish this would be to do the following:
import { environment } from 'src/environments/environment';
...
private API_BASE_URL = environment.apiBaseUrl;
Let’s create a new public method named uploadFile to the service. The method will take in a parameter file: File and return an Observable<HttpEvent<{}>>.
Typically a
getorpostObservable<T>is returned from a service like this. However, in this situation we are going to actually return the rawrequestwhich is anObservable<HttpEvent<{}>>.
By returning a raw
requestwe have more control over the process, to pass options likereportProgressand allow cancellation of arequest.
public uploadFile(file: File): Observable<HttpEvent<{}>> {
const formData = new FormData();
formData.append('files', file, file.name);
const options = {
reportProgress: true
};
const req = new HttpRequest(
'POST',
`${this.API_BASE_URL}/api/file`,
formData,
options
);
return this.httpClient.request(req);
}
The completed file-upload.service.ts will look as follows:
import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class FileUploadService {
private API_BASE_URL = environment.apiBaseUrl;
constructor(private httpClient: HttpClient) {}
public uploadFile(file: File): Observable<HttpEvent<{}>> {
const formData = new FormData();
formData.append('files', file, file.name);
const options = {
reportProgress: true
};
const req = new HttpRequest(
'POST',
`${this.API_BASE_URL}/api/file`,
formData,
options
);
return this.httpClient.request(req);
}
}
To keep your NgRx store organized, I recommend creating a separate Upload File Feature Store. Let’s bundle it all together in a module named upload-file-store.module.ts and keep it under a sub-directory named upload-file-store.
Create a feature store module using the following command:
$ ng g module upload-file-store --flat false
Create a new file underneath the upload-file-store folder, named state.ts. The contents of the file will be as follows:
We are using a relatively new technique in that we will set up an
enumto track the status. Thisenumwill reflect the current state of the upload process. For more information on this method, check out Alex Okrushko’s article.
export enum UploadStatus {
Ready = 'Ready',
Requested = 'Requested',
Started = 'Started',
Failed = 'Failed',
Completed = 'Completed'
}
export interface State {
status: UploadStatus;
error: string | null;
progress: number | null;
}
export const initialState: State = {
status: UploadStatus.Ready,
error: null,
progress: null
};
If you would like to learn more about NgRx Actions, then check out the official docs.
Create a new file underneath the upload-file-store folder, named actions.ts. This file will hold the actions we want to make available on this store.
We will create the following actions on our feature store:
UPLOAD_REQUEST - This action is dispatched from the file upload form, it’s payload will contain the actual File being uploaded.
UPLOAD_CANCEL - This action is dispatched from the file upload form when the cancel button is clicked. This will be used to cancel uploads in progress.
UPLOAD_RESET - This action is dispatched from the file upload form when the reset button is clicked. This will be used to reset the state of the store to defaults.
UPLOAD_STARTED - This action is dispatched from the file upload effect, HttpClient when the API reports the HttpEventType.Sent event.
UPLOAD_PROGRESS - This action is dispatched from the file upload effect, HttpClient when the API reports the HttpEventType.UploadProgress event. The payload will contain the progress percentage as a whole number.
UPLOAD_FAILURE - This action is dispatched from the file upload effect when the API returns an error, or there is an HttpEventType.ResponseHeader or HttpEventType.Response with an event.status !== 200, or when an unknown HttpEventType is returned. The payload will contain the specific error message returned from the API and place it into an error field on the store.
UPLOAD_COMPLETED - This action is dispatched from the file upload effect when the API reports a HttpEventType.ResponseHeader or HttpEventType.Response event event.status === 200. There is no payload as the API just returns a 200 OK repsonse.
The final actions.ts file will look as follows:
import { Action } from '@ngrx/store';
export enum ActionTypes {
UPLOAD_REQUEST = '[File Upload Form] Request',
UPLOAD_CANCEL = '[File Upload Form] Cancel',
UPLOAD_RESET = '[File Upload Form] Reset',
UPLOAD_STARTED = '[File Upload API] Started',
UPLOAD_PROGRESS = '[File Upload API] Progress',
UPLOAD_FAILURE = '[File Upload API] Failure',
UPLOAD_COMPLETED = '[File Upload API] Success'
}
export class UploadRequestAction implements Action {
readonly type = ActionTypes.UPLOAD_REQUEST;
constructor(public payload: { file: File }) {}
}
export class UploadCancelAction implements Action {
readonly type = ActionTypes.UPLOAD_CANCEL;
}
export class UploadResetAction implements Action {
readonly type = ActionTypes.UPLOAD_RESET;
}
export class UploadStartedAction implements Action {
readonly type = ActionTypes.UPLOAD_STARTED;
}
export class UploadProgressAction implements Action {
readonly type = ActionTypes.UPLOAD_PROGRESS;
constructor(public payload: { progress: number }) {}
}
export class UploadFailureAction implements Action {
readonly type = ActionTypes.UPLOAD_FAILURE;
constructor(public payload: { error: string }) {}
}
export class UploadCompletedAction implements Action {
readonly type = ActionTypes.UPLOAD_COMPLETED;
}
export type Actions =
| UploadRequestAction
| UploadCancelAction
| UploadResetAction
| UploadStartedAction
| UploadProgressAction
| UploadFailureAction
| UploadCompletedAction;
If you would like to learn more about NgRx Reducers, then check out the official docs.
Create a new file underneath the upload-file-store folder, named reducer.ts. This file will hold the reducer we create to manage state transitions to the store.
We will handle state transitions as follows for the aforementioned actions:
UPLOAD_REQUEST - Reset the state, with the exception of setting state.status to UploadStatus.Requested.
UPLOAD_CANCEL - Reset the state tree. Our effect will listen for any UPLOAD_CANCEL event dispatches so a specific state field is not needed for this.
UPLOAD_RESET - Reset the state tree on this action.
UPLOAD_FAILURE - Reset the state tree, with the exception of setting state.status to UploadStatus.Failed and state.error to the error that was throw in the catchError from the API in the uploadRequestEffect effect.
UPLOAD_STARTED - Set state.progress to 0 and state.status to UploadStatus.Started.
UPLOAD_PROGRESS - Set state.progress to the current action.payload.progress provided from the action.
UPLOAD_COMPLETED - Reset the state tree, with the exception of setting state.status to UploadStatus.Completed so that the UI can display a success message.
import { Actions, ActionTypes } from './actions';
import { initialState, State, UploadStatus } from './state';
export function featureReducer(state = initialState, action: Actions): State {
switch (action.type) {
case ActionTypes.UPLOAD_REQUEST: {
return {
...state,
status: UploadStatus.Requested,
progress: null,
error: null
};
}
case ActionTypes.UPLOAD_CANCEL: {
return {
...state,
status: UploadStatus.Ready,
progress: null,
error: null
};
}
case ActionTypes.UPLOAD_RESET: {
return {
...state,
status: UploadStatus.Ready,
progress: null,
error: null
};
}
case ActionTypes.UPLOAD_FAILURE: {
return {
...state,
status: UploadStatus.Failed,
error: action.payload.error,
progress: null
};
}
case ActionTypes.UPLOAD_STARTED: {
return {
...state,
status: UploadStatus.Started,
progress: 0
};
}
case ActionTypes.UPLOAD_PROGRESS: {
return {
...state,
progress: action.payload.progress
};
}
case ActionTypes.UPLOAD_COMPLETED: {
return {
...state,
status: UploadStatus.Completed,
progress: 100,
error: null
};
}
default: {
return state;
}
}
}
If you would like to learn more about NgRx Effects, then check out the official docs.
Create a new file underneath the upload-file-store folder, named effects.ts. This file will hold the effects that we create to handle any side-effect calls to the backend API service. This effect is where most of the magic happens in the application.
Let’s add the necessary dependencies to our constructor as follows:
constructor(
private fileUploadService: FileUploadService,
private actions$: Actions<fromFileUploadActions.Actions>
) {}
Effects make heavy-use of
RxJSconcepts and topics. If you are new toRxJSthen I suggest you check out the official docs
Let’s create a new effect in the file named uploadRequestEffect$.
A couple comments about what this effect is going to do:
Listen for the UPLOAD_REQUEST action and then make calls to the fileUploadService.uploadFile service method to initiate the upload process.
Use the concatMap RxJS operator here so that multiple file upload requests are queued up and processed in the order they were dispatched.
Use the takeUntil RxJS operator listening for an UPLOAD_CANCEL action to be dispatched. This allows us to short-circuit any requests that are in-flight.
Use the map RxJS operator to map specific HttpEvent responses to dispatch specific Actions that we have defined in our Store.
Use the catchError RxJS operator to handle any errors that may be thrown from the HttpClient.
The effect will look something like this:
@Effect()
uploadRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType(fromFileUploadActions.ActionTypes.UPLOAD_REQUEST),
concatMap(action =>
this.fileUploadService.uploadFile(action.payload.file).pipe(
takeUntil(
this.actions$.pipe(
ofType(fromFileUploadActions.ActionTypes.UPLOAD_CANCEL)
)
),
map(event => this.getActionFromHttpEvent(event)),
catchError(error => of(this.handleError(error)))
)
)
);
For more information on listening to progress events, check out the official docs guide from here.
This method will be responsible for mapping a specific HttpEventType to a specific Action that is dispatched.
HttpEventType.Sent - This event occurs when the upload process has begun. We will dispatch an UPLOAD_STARTED action to denote that the process has begun.
HttpEventType.UploadProgress - This event occurs when the upload process has made progress. We will dispatch an UPLOAD_PROGRESS action with a payload of progress: Math.round((100 * event.loaded) / event.total) to calculate the actual percentage complete of upload. This is because the HttpClient returns an event.loaded and event.total property in whole number format.
HttpEventType.Response / HttpEventType.ResponseHeader - These events occur when the upload process has finished. It is important to note that this could be a success or failure so we need to interrogate the event.status to check for 200. We will dispatch the UPLOAD_COMPLETED action if event.status === 200 and UPLOAD_FAILURE if the event.status !== 200 passing the event.statusText as the error payload.
All Others (default case) - We treat any other events that may be returned as an error because they are unexpected behavior. We will dispatch a UPLOAD_FAILURE action with a payload of the event run through JSON.stringify.
private getActionFromHttpEvent(event: HttpEvent<any>) {
switch (event.type) {
case HttpEventType.Sent: {
return new fromFileUploadActions.UploadStartedAction();
}
case HttpEventType.UploadProgress: {
return new fromFileUploadActions.UploadProgressAction({
progress: Math.round((100 * event.loaded) / event.total)
});
}
case HttpEventType.ResponseHeader:
case HttpEventType.Response: {
if (event.status === 200) {
return new fromFileUploadActions.UploadCompletedAction();
} else {
return new fromFileUploadActions.UploadFailureAction({
error: event.statusText
});
}
}
default: {
return new fromFileUploadActions.UploadFailureAction({
error: `Unknown Event: ${JSON.stringify(event)}`
});
}
}
}
For more information on handling
HttpClienterrors, check out the official docs guide from here.
This method will be responsible for handling any errors that may be thrown from the HttpClient during requests. I am making use of a neat library from npm named serialize-error to give me a predictable error.message no matter what type of error is thrown.
Install the library as so:
$ npm install serialize-error
import serializeError from 'serialize-error';
...
private handleError(error: any) {
const friendlyErrorMessage = serializeError(error).message;
return new fromFileUploadActions.UploadFailureAction({
error: friendlyErrorMessage
});
}
The completed effect will look something like this:
import { HttpEvent, HttpEventType } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable, of } from 'rxjs';
import { catchError, concatMap, map, takeUntil } from 'rxjs/operators';
import serializeError from 'serialize-error';
import { FileUploadService } from 'src/app/_services';
import * as fromFileUploadActions from './actions';
@Injectable()
export class UploadFileEffects {
@Effect()
uploadRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType(fromFileUploadActions.ActionTypes.UPLOAD_REQUEST),
concatMap(action =>
this.fileUploadService.uploadFile(action.payload.file).pipe(
takeUntil(
this.actions$.pipe(
ofType(fromFileUploadActions.ActionTypes.UPLOAD_CANCEL)
)
),
map(event => this.getActionFromHttpEvent(event)),
catchError(error => of(this.handleError(error)))
)
)
);
constructor(
private fileUploadService: FileUploadService,
private actions$: Actions<fromFileUploadActions.Actions>
) {}
private getActionFromHttpEvent(event: HttpEvent<any>) {
switch (event.type) {
case HttpEventType.Sent: {
return new fromFileUploadActions.UploadStartedAction();
}
case HttpEventType.UploadProgress: {
return new fromFileUploadActions.UploadProgressAction({
progress: Math.round((100 * event.loaded) / event.total)
});
}
case HttpEventType.ResponseHeader:
case HttpEventType.Response: {
if (event.status === 200) {
return new fromFileUploadActions.UploadCompletedAction();
} else {
return new fromFileUploadActions.UploadFailureAction({
error: event.statusText
});
}
}
default: {
return new fromFileUploadActions.UploadFailureAction({
error: `Unknown Event: ${JSON.stringify(event)}`
});
}
}
}
private handleError(error: any) {
const friendlyErrorMessage = serializeError(error).message;
return new fromFileUploadActions.UploadFailureAction({
error: friendlyErrorMessage
});
}
}
If you would like to learn more about NgRx Selectors, then check out the official docs.
Create a new file underneath the upload-file-store folder, named selectors.ts. This file will hold the selectors we will use to pull specific pieces of state out of the store. These are technically not required, but strongly encouraged. Selectors improve application performance with the use of the MemoizedSelector wrapper. Selectors also simplify UI logic.
We will create a selector for each significant property of the state. This includes the following properties:
state.status - Since this is an enum we will create a selector for each enum choice.state.errorstate.progressThe completed selectors file will look something like the following:
import {
createFeatureSelector,
createSelector,
MemoizedSelector
} from '@ngrx/store';
import { State, UploadStatus } from './state';
const getError = (state: State): string => state.error;
const getStarted = (state: State): boolean =>
state.status === UploadStatus.Started;
const getRequested = (state: State): boolean =>
state.status === UploadStatus.Requested;
const getReady = (state: State): boolean => state.status === UploadStatus.Ready;
const getProgress = (state: State): number => state.progress;
const getInProgress = (state: State): boolean =>
state.status === UploadStatus.Started && state.progress >= 0;
const getFailed = (state: State): boolean =>
state.status === UploadStatus.Failed;
const getCompleted = (state: State): boolean =>
state.status === UploadStatus.Completed;
export const selectUploadFileFeatureState: MemoizedSelector<
object,
State
> = createFeatureSelector<State>('uploadFile');
export const selectUploadFileError: MemoizedSelector<
object,
string
> = createSelector(
selectUploadFileFeatureState,
getError
);
export const selectUploadFileReady: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getReady
);
export const selectUploadFileRequested: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getRequested
);
export const selectUploadFileStarted: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getStarted
);
export const selectUploadFileProgress: MemoizedSelector<
object,
number
> = createSelector(
selectUploadFileFeatureState,
getProgress
);
export const selectUploadFileInProgress: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getInProgress
);
export const selectUploadFileFailed: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getFailed
);
export const selectUploadFileCompleted: MemoizedSelector<
object,
boolean
> = createSelector(
selectUploadFileFeatureState,
getCompleted
);
We now need to update the feature module UploadFileStoreModule to wire-up the store.
The completed UploadFileStoreModule should look similar to this:
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { UploadFileEffects } from './effects';
import { featureReducer } from './reducer';
@NgModule({
declarations: [],
imports: [
CommonModule,
StoreModule.forFeature('uploadFile', featureReducer),
EffectsModule.forFeature([UploadFileEffects])
]
})
export class UploadFileStoreModule {}
Make sure to import this new UploadFileStoreModule where it is needed. In this example, we will import this into the AppModule as we do not have any lazy-loaded features.
Last, make sure that you update your AppModule to import the StoreModule.forRoot and EffectsModule.forRoot.
An updated AppModule may look as follows:
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { environment } from 'src/environments/environment';
import { AppComponent } from './app.component';
import { UploadFileStoreModule } from './upload-file-store/upload-file-store.module';
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
HttpClientModule,
StoreModule.forRoot({}),
EffectsModule.forRoot([]),
StoreDevtoolsModule.instrument({
maxAge: 25, // Retains last 25 states
logOnly: environment.production // Restrict extension to log-only mode
}),
UploadFileStoreModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
Up to this point, we have created a new FileUploadService that calls our backend API to upload a File object.
We have also created a new UploadFileStore feature store that provides Actions, a Reducer, Effects, and Selectors to manage the file upload process.
Last, the store has been imported into our AppModule for use.
Now that we have the foundation laid out for us we can turn our attention to the user interface and wire-up a new component to the UploadFileStore that we created to manage our process.
This will be the fun part!
Let’s start by creating a brand-new Component. This component will consist of the following elements:
An input element for the user to interact with to upload a file. The change event will dispatch the UploadFileStoreActions.UploadRequest() action
A progress percentage to connected to the UploadFileStoreSelectors.selectUploadFileProgress selector for real-time progress
A Cancel UPload button to dispatch the UploadFileStoreActions.UploadCancelRequest() action
An Upload Another File button to dispatch the UploadFileStoreActions.UploadResetRequest() action and allow for a new file upload
SIDE NOTE: This would be a good scenario to create a connected container with a dumb component, but for the brevity of this article I will show these combined as one. In the example repository, I will show both scenarios.
Click here for more details on using the powerful Angular CLI
$ ng g component upload-file
For simplicity of this article we will just display the progress percentage, this could easily be adapted to hook into the
valueproperty of a progress bar control, like the Angular Material library provides.
We need to wire-up our store into this component for use. Let’s start by injecting the store into the constructor. The finished constructor should look something like this:
...
constructor(private store$: Store<fromFileUploadState.State>) {}
Let’s create six (6) public fields on the component. A good practice is to place $ as a suffix so that you know these are Observable and must be subscribed to in the template.
completed$: Observable<boolean>;
progress$: Observable<number>;
error$: Observable<string>;
isInProgress$: Observable<boolean>;
isReady$: Observable<boolean>;
hasFailed$: Observable<boolean>;
Let’s hook these up to the store in our ngOnInit life-cycle hook.
ngOnInit() {
this.completed$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileCompleted)
);
this.progress$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileProgress)
);
this.error$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileError)
);
this.isInProgress$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileInProgress)
);
this.isReady$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileReady)
);
this.hasFailed$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileFailed)
);
}
Let’s add uploadFile, resetUpload, and cancelUpload methods to connect our button clicks to dispatch actions in the store.
uploadFile(event: any) {
const files: FileList = event.target.files;
const file = files.item(0);
this.store$.dispatch(
new fromFileUploadActions.UploadRequestAction({
file
})
);
// clear the input form
event.srcElement.value = null;
}
resetUpload() {
this.store$.dispatch(new UploadFileStoreActions.UploadResetAction());
}
cancelUpload() {
this.store$.dispatch(new UploadFileStoreActions.UploadCancelAction());
}
The finished component *.ts file should look similar to the following:
import { Component, OnInit } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import * as fromFileUploadActions from 'src/app/upload-file-store/actions';
import * as fromFileUploadSelectors from 'src/app/upload-file-store/selectors';
import * as fromFileUploadState from 'src/app/upload-file-store/state';
@Component({
selector: 'app-upload-file',
templateUrl: './upload-file.component.html',
styleUrls: ['./upload-file.component.css']
})
export class UploadFileComponent implements OnInit {
completed$: Observable<boolean>;
progress$: Observable<number>;
error$: Observable<string>;
isInProgress$: Observable<boolean>;
isReady$: Observable<boolean>;
hasFailed$: Observable<boolean>;
constructor(private store$: Store<fromFileUploadState.State>) {}
ngOnInit() {
this.completed$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileCompleted)
);
this.progress$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileProgress)
);
this.error$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileError)
);
this.isInProgress$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileInProgress)
);
this.isReady$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileReady)
);
this.hasFailed$ = this.store$.pipe(
select(fromFileUploadSelectors.selectUploadFileFailed)
);
}
uploadFile(event: any) {
const files: FileList = event.target.files;
const file = files.item(0);
this.store$.dispatch(
new fromFileUploadActions.UploadRequestAction({
file
})
);
// clear the input form
event.srcElement.value = null;
}
resetUpload() {
this.store$.dispatch(new fromFileUploadActions.UploadResetAction());
}
cancelUpload() {
this.store$.dispatch(new fromFileUploadActions.UploadCancelAction());
}
}
We are going to add five (5) major parts to our upload file component.
There is no upload file button, rather we will make use of the built-in input component and hook to the change event. Any time a file is added to the form this event will fire. We also only want to display this form if we are accepting new files to be uploaded, i.e. it has failed or it is ready. We will use the *ngIf structural directive to help here referencing our isReady$ and hasFailed$ observables.
<div class="message" *ngIf="(isReady$ | async) || (hasFailed$ | async)">
<input #file type="file" multiple (change)="uploadFile($event)" />
</div>
This message will be displayed when the progress is greater than or equal to 0% and the UploadStatus is Failed. We will use *ngIf to only display if it’s in this state using the isInProgress$ selector value. We will set the text of the progress message to the progress$ selector value.
<div class="message" *ngIf="(isInProgress$ | async)">
<div style="margin-bottom: 14px;">Uploading... %</div>
</div>
This button will utilize the *ngIf to only display if the upload is in progress using the isInProgress$ selector value. The click event will trigger the dispatch of the UploadCancelAction.
<div class="message" *ngIf="(isInProgress$ | async)">
<button (click)="cancelUpload()">Cancel Upload</button>
</div>
This button will utilize the *ngIf to only display if the upload is complete using the completed$ selector value. The click event will trigger the dispatch of the UploadResetAction.
<div class="message" *ngIf="(completed$ | async)">
<h4>
File has been uploaded successfully!
</h4>
<button (click)="resetUpload()">Upload Another File</button>
</div>
This button will utilize the *ngIf to only display if hasFailed$ selector value returns true. The actual error message is pulled from the error$ selector value.
<div class="message error" *ngIf="(hasFailed$ | async)">
Error:
</div>
<div class="message" *ngIf="(isReady$ | async) || (hasFailed$ | async)">
<input #file type="file" multiple (change)="uploadFile($event)" />
</div>
<div class="message" *ngIf="(isInProgress$ | async)">
<div style="margin-bottom: 14px;">Uploading... %</div>
</div>
<div class="message" *ngIf="(isInProgress$ | async)">
<button (click)="cancelUpload()">Cancel Upload</button>
</div>
<div class="message" *ngIf="(completed$ | async)">
<h4>
File has been uploaded successfully!
</h4>
<button (click)="resetUpload()">Upload Another File</button>
</div>
<div class="message error" *ngIf="(hasFailed$ | async)">
Error:
</div>
For formatting let’s add a few simple classes to our component stylesheet:
.message {
margin-bottom: 15px;
}
.error {
color: red;
}
For the purposes of this article we will add our new UploadFileComponent component to our AppComponent. The template will look as follows:
<app-upload-file></app-upload-file>
For a full mock back-end server checkout my [repository here:
For those of you brave souls that have made it this far… You might be asking what the backend API endpoint looks like. Well, here’s an example ASP.NET Core Controller offered free of charge ;-)
public class FileController : ControllerBase
{
[HttpPost("")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
try
{
foreach (var file in files)
{
Console.WriteLine($"Begin Uploaded File: {file.FileName}");
//simulate upload
Task.Delay(5000).Wait();
Console.WriteLine($"Finished Uploaded File: {file.FileName}");
}
return Ok();
}
catch (Exception ex)
{
return BadRequest($"Unable to upload file(s).");
}
}
}
I always like to provide working code examples that follow the article. You can find this articles companion application at the following repository:
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 your 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!
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
]]>
This article is a continuation of an Angular Hot Tip tweet that I sent out earlier this week. It became widely popular and generated quite a discussion. The concepts explored in this article reflect that discussion, so you should probably take some time and go check it out here:
There was a 'Not Found' error fetching URL: 'https://twitter.com/wesgrimes/status/1110603853089701888'
As an extension of the above mentioned tweet, we will discuss limitations with how and when ngOnDestroy is called. We will also discuss ways to overcome those limitations. If you are new to Angular, or new to lifecycle methods in Angular, then I suggest you check out the official docs here.
For context, this article assumes you are using the following npm package.json versions:
@angular/*: 7.2.9Before we dig too deep, let’s take a few minutes and review ngOnDestroy.
NgOnDestroy is a lifecycle method that can be added by implementing OnDestroy on the class and adding a new class method named ngOnDestroy. It’s primary purpose according to the Angular Docs is to “Cleanup just before Angular destroys the directive/component. Unsubscribe Observables and detach event handlers to avoid memory leaks. Called just before Angular destroys the directive/component.”
Let’s imagine that we have a component named MyValueComponent that subscribes to a value from MyService in the ngOnInit method:
import { Component, OnInit } from '@angular/core';
import { MyService } from './my.service';
@Component({
selector: 'app-my-value',
templateUrl: './my-value.component.html',
styleUrls: [ './my-value.component.css' ]
})
export class MyValueComponent implements OnInit {
myValue: string;
constructor(private myService: MyService) {}
ngOnInit() {
this.myService.getValue().subscribe(value => this.myValue = value);
}
}
If this component is created and destroyed multiple times in the lifecycle of an Angular application, each time it’s created the ngOnInit would be called creating a brand new subscription. This could quickly get out of hand, with our value being updated exponentially. This is creating what is called a “memory leak”. Memory leaks can wreak havoc on the performance of an application and in addition add unpredictable or unintended behaviors. Let’s read on to learn how to plug this leak.
To fix the memory leak we need to augment the component class with an implementation of OnDestroy and unsubscribe from the subscription. Let’s update our component adding the following:
OnDestroy to the typescript importOnDestroy to the implements listmyValueSub: Subscription to track our subscriptionthis.myValueSub equal to the value of this.myService.getValue().subscriptionngOnDestroythis.myValueSub.unsubscribe() within ngOnDestroy if a subscription has been set.The updated component will look something like this:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MyService } from './my.service';
@Component({
selector: 'app-my-value',
templateUrl: './my-value.component.html',
styleUrls: [ './my-value.component.css' ]
})
export class MyValueComponent implements OnInit, OnDestroy {
myValue: string;
myValueSub: Subscription;
constructor(private myService: MyService) {}
ngOnInit() {
this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value);
}
ngOnDestroy() {
if (this.myValueSub) {
this.myValueSub.unsubscribe();
}
}
}
Great! Now you have some background on ngOnDestroy and how cleaning up memory leaks is the primary use case for this lifecycle method. But what if you want to take it a step further and add additional cleanup logic? How about making server-side cleanup calls? Maybe preventing user navigation away?
As you read on we will discuss three methods to upgrade your ngOnDestroy for optimum use.
As with other lifecycle methods in Angular, you can modify ngOnDestroy with async. This will allow you to make calls to methods returning a Promise. This can be a powerful way to manage cleanup activities in your application. As you read on we will explore an example of this.
Let’s pretend that you need to perform a server-side logout when MyValueComponent is destroyed. To do so we would update the method as follows:
AuthService to your importsAuthService to your constructorasync in front of the method name ngOnDestroyAuthService to logout using the await keyword.Your updated MyValueComponent will look something like this:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { MyService } from './my.service';
import { AuthService } from './auth.service';
@Component({
selector: 'app-my-value',
templateUrl: './my-value.component.html',
styleUrls: [ './my-value.component.css' ]
})
export class MyValueComponent implements OnInit, OnDestroy {
myValue: string;
myValueSub: Subscription;
constructor(private myService: MyService, private authService: AuthService) {}
ngOnInit() {
this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value);
}
async ngOnDestroy() {
if (this.myValueSub) {
this.myValueSub.unsubscribe();
}
await this.authService.logout();
}
}
Tada! Now when the component is destroyed an async call will be made to log the user out and destroy their session on the server.
Many developers are surprised to learn that ngOnDestroy is only fired when the class which it has been implemented on is destroyed within the context of a running browser session.
In other words, ngOnDestroy is not reliably called in the following scenarios:
This could be a deal-breaker when thinking about the prior example of logging the user out on destroy. Why? Well, most users would simply close the browser session or navigate to another site. So how do we make sure to capture or hook into that activity if ngOnDestroy doesn’t work in those scenarios?
TypeScript decorators are used throughout Angular applications. More information can be found here in the official TypeScript docs.
To ensure that our ngOnDestroy is executed in the above mentioned browser events, we can add one simple line of code to the top of ngOnDestroy. Let’s continue with our previous example of MyValueComponent and decorate ngOnDestroy:
HostListener to the imports@HostListener('window:beforeunload') on top of ngOnDestroyOur updated MyValueComponent will look something like this:
import { Component, OnInit, OnDestroy, HostListener } from '@angular/core';
import { MyService } from './my.service';
import { AuthService } from './auth.service';
@Component({
selector: 'app-my-value',
templateUrl: './my-value.component.html',
styleUrls: [ './my-value.component.css' ]
})
export class MyValueComponent implements OnInit, OnDestroy {
myValue: string;
myValueSub: Subscription;
constructor(private myService: MyService, private authService: AuthService) {}
ngOnInit() {
this.myValueSub = this.myService.getValue().subscribe(value => this.myValue = value);
}
@HostListener('window:beforeunload')
async ngOnDestroy() {
if (this.myValueSub) {
this.myValueSub.unsubscribe();
}
await this.authService.logout();
}
}
Now our ngOnDestroy method is called both when the component is destroyed by Angular AND when the browser event window:beforeunload is fired. This is a powerful combination!
@HostListener() is an Angular decorator that can be placed on top of any class method. This decorator takes two arguments: eventName and optionally args. In the above example, we are passing window:beforeunload as the DOM event. This means that Angular will automatically call our method when the DOM event window:beforeunload is fired. For more information on @HostListener check out the official docs.
If you want to use this to prevent navigation away from a page or component then:
$event to the @HostListener argumentsevent.preventDefault()event.returnValue to a string value of the message you would like the browser to displayAn example would look something like this:
@HostListener('window:beforeunload', ['$event'])
async ngOnDestroy($event) {
if (this.myValueSub) {
this.myValueSub.unsubscribe();
}
await this.authService.logout();
$event.preventDefault();
$event.returnValue = 'A message.';
}
PLEASE NOTE: This is not officially supported by Angular!
OnDestroyandngOnDestroysuggest that there is no input argument onngOnDestroyallowed. While unsupported, it does in fact still function as normal.
window:beforeunload is an event fired right before the window is unloaded. More details can be found in the documentation here: https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event.
A couple points to be aware of:
This event is currently supported in all major browsers EXCEPT iOS Safari.
If you need this functionality in iOS Safari then consider reviewing this Stack Overflow thread.
If you are using this event in an attempt to block navigation away you must set the event.returnValue to a string of the message you would like to display. More details in this example.
I realize that some of the tips recommended in this article are not mainstream and may generate some concern. Please remember as always to try these out and see if they fit for what you are doing in your application. If they work great! If not, then it’s ok to move on.
If you have any comments or questions feel free to contact me on Twitter
I would highly recommend enrolling in the Ultimate Angular courses. It is well worth the money and I have used it as a training tool for new and experienced Angular developers. Follow the link below to signup.
Ultimate Courses: Expert online courses in JavaScript, Angular, NGRX and TypeScript
]]>
This article is not intended to be a tutorial on routing in Angular. If you are new to Routing in Angular then I highly recommend you check out one of the the following resources:
The following represents a pattern that I’ve developed at my day job after building several enterprise Angular applications. While most online tutorials do a great job laying out the fundamentals, I had a hard time locating articles that showed recommended conventions and patterns for large and scalable applications.
With this pattern you should have a clean and concise organization for all routing related concerns in your applications.
For context, this article assumes you are using the following version of Angular:
The official Angular docs recommend creating a full-blown
app-routing.module.tsfor your top-level routing. I have found this extra layer to be unnecessary in most cases.
HOT TIP: Only register top-level routes here, if you plan to implement feature modules, then the child routes would live underneath the respective
feature.routes.tsfile. We want to keep this top-level routes file as clean as possible and follow the component tree structure.
Let’s go with the following approach:
Create a new file named app.routes.ts in the root src/app directory. This file will hold our top-level Routes array. We will come back later throughout the article and fill this in. For now, let’s scaffold it with the following contents:
import { Routes } from '@angular/router';
export const AppRoutes: Routes = [];
Register AppRoutes in the app.module.ts file.
AppRoutes from app.routes.ts.RouterModule from @angular/router.RouterModule.forRoot(AppRoutes) to your imports arrayYour updated app.module.ts will look similar to the following:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { AppRoutes } from './app.routes';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, RouterModule.forRoot(AppRoutes)],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
In similar fashion to how we constructed the app.routes.ts we will create a feature.routes.ts to list out the individual routes for this feature module. We want to keep our routes as close to the source as possible. This will be in keeping with a clean code approach, and having a good separation of concerns.
Create a new file named feature/feature.routes.ts where feature matches the name of your feature.module.ts prefix. This file will hold our feature-level Routes array. Keeping in mind that you would replace Feature with the actual name of your module, let’s scaffold it with the following contents:
import { Routes } from '@angular/router';
export const FeatureRoutes: Routes = [];
Register FeatureRoutes in the feature/feature.module.ts file. We will make use of the RouterModule.forChild import so that these routes are automatically registered with lazy loading.
FeatureRoutes from feature.routes.ts.RouterModule from @angular/router.RouterModule.forChild(FeatureRoutes) to your imports arrayYour updated feature/feature.module.ts will look similar to the following:
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FeatureRoutes } from './feature.routes';
@NgModule({
declarations: [],
imports: [CommonModule, RouterModule.forChild(FeatureRoutes)]
})
export class FeatureModule {}
An example of a feature.routes.ts file with child route(s) may look like the following:
import { Routes } from '@angular/router';
import { FeatureOneComponent } from './feature-one.component';
import { FeatureSpecificCanActivateGuard } from './_guards';
export const FeatureOneRoutes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'feature-one-component'
},
{
path: 'feature-one-component',
component: FeatureOneComponent,
canActivate: [FeatureSpecificCanActivateGuard]
}
];
Lazy loading is the concept of deferring load of code assets (javascript, styles) until the user actually needs to utilize the resources. This can bring large performance increases to perceived load times of your application as the entire code set doesn’t have to download on first paint.
Angular provides a nice way to handle this with the
loadChildrenoption for a given route. More information can be found in the official Angular docs.
Once you’ve created your app.routes.ts and *.routes.ts files, you need to register any feature modules that you want to load lazily.
Update the AppRoutes array in the app.routes.ts file to include a new route the feature:
import { Routes } from '@angular/router';
export const AppRoutes: Routes = [
{
path: 'feature',
loadChildren: './feature/feature.module#FeatureModule'
}
];
By adding the above route to the array, when the user requests /feature in the browser, Angular lazy loads the module using the path given and then automatically registers any routes defined in the feature.routes.ts FeatureRoutes array using the RouterModule.forChild import.
For each additional feature module, you would add another item to the AppRoutes array. If you have multiple features, it might look something like the following:
import { Routes } from '@angular/router';
export const AppRoutes: Routes = [
{
path: '',
pathMatch: 'full',
redirectTo: 'feature-one'
},
{
path: 'feature-one',
loadChildren: './feature-one/feature-one.module#FeatureOneModule'
},
{
path: 'feature-two',
loadChildren: './feature-two/feature-two.module#FeatureTwoModule'
}
];
Here are a few tips to keep your router guards organized. These are just guidelines, but I have found them to be very helpful.
Guards should use the following naming convention:
name.function.guard.tsNameFunctionGuardEach part being identified as:
name - this is the name of your guard. What are you guarding against?function - this is the function your guard will be attached to. Angular supports CanActivate, CanActivateChild, CanDeactivate, and Resolve.An example of an Auth Guard that is attached to the CanActivate function would be named as follows:
auth.can-activate.guardAuthCanActivateGuard_guards folderOrganize all top-level guards under a folder named src/app/_guards. I have seen apps dump guards in the top level directory and this is messy, especially if you end up with more than a few guards.
The jury is still out on whether or not using barrel exports is officially considered a “best practice” or even supported by the Angular style guide. However, I am a big fan of the clean organization this provides. This method is offered as a suggestion.
Make sure that src/app/_guards has a nice and clean index.ts barrel export. Barrel exports are simply index.ts files that group together and export all public files from a directory. An example is as follows:
export * from './auth.can-activate.guard';
export * from './require-save.can-deactivate.guard';
Without Barrel Exporting:
import { AuthCanActivateGuard } from 'src/app/_guards/auth.can-activate.guard';
import { RequireSaveCanDeactivateGuard } from 'src/app/_guards/require-save.can-deactivate.guard';
With Barrel Exporting:
import { AuthCanActivateGuard, RequireSaveCanDeactivateGuard } from 'src/app/_guards';
An example application with a _guards directory would look as follows:

If you have guards that are only used in a particular FeatureRoutes array, then store these routes underneath a folder named _guards underneath your feature folder. Make sure to follow the same naming conventions defined above, as well as barrel exporting.
_guards underneath your feature folderindex.ts for clean importingAn example feature directory with _guards would look as follows:

A completed application structure should look something like the following:

I have created a demonstration repository on GitHub. Feel free to fork, clone, and submit PRs.
https://github.com/wesleygrimes/angular-routing-best-practices
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 routes in 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 routing and I will update the article to reflect as such. Happy Coding!
]]>
As a senior developer in a small-to-medium sized software firm, I am often tasked with training new developers, or seasoned developers in new technologies. I am always on the lookout for ways to ease the burden and standardize the process for all parties involved.
One-on-one training and instructor-led training sessions are great, but not everyone has the resources to do this, and often times our current workloads and “deliverables” prevent us from setting aside a week (or more) to devote to training on new topics. Most of you reading this are well aware of the mainstream online training offerings that exist. Pluralsight and Lynda come to mind.
While these are fantastic resources, it’s often hard to find Angular training courses that teach on the latest and greatest versions of front-end libraries and frameworks. In this article, I will explore, Ultimate Courses, the offerings created and curated by Todd Motto (Google Developer Expert and Angular extraordinaire).
For Angular development, Ultimate Courses offers two packages to choose from: Angular Kickstart Package and Angular Ultimate Package. Let’s quickly review the differences.
If your team has previous TypeScript experience, then this is the package I would recommend. It includes:
Learning Angular, for most developers, is not just as simple as learning the frameworks features, conventions and tooling. For most, it requires getting up to speed on TypeScript, a powerful, typed superset of JavaScript. Teaching developers TypeScript is a must for any online solution that I recommend, and thankfully Ultimate Courses’ Angular Ultimate Package has you covered here. It includes:
Courses can be purchased in packages as stated above, however, they can also be purchased individually as needed which may make sense for some scenarios.
If you are working with a team of developers, Ultimate Courses offers user licensing with discounts as the user count grows. This is a great option for teams of developers learning Angular.
This course starts out from the high-level and slowly dives deeper into the basic building blocks of an Angular single page application. The content is separated into the following sections:
I won’t dive too deep into each these sections, but I will say for an introductory course, this offering does a fantastic job of giving you just enough information to be dangerous (in a good way), while not overwhelming first-time Angular developers.
This course takes the concepts learned in Angular Fundamentals and goes deep, way deep. The topics covered in this course are vital to learn as any Angular app that grows in complexity will almost always need to handle these situations. I appreciate Todd’s attention to detail. Topics covered include:
This course is an introduction to TypeScript. Developers coming from C# will appreciate this course in particular. In addition, this course can be purchased separately from the package if you are building with TypeScript. Topics include:
Just as with any language, there are folks that use the basics and are off to the races. There are some cases, however, where you need to dig deep and really understand what’s happening. If you are building Angular or NodeJS libraries, then this course is probably for you. Topics include:
Personally, I have been using NgRx for a long time. I no longer build Angular applications without it! So for me, I was very curious to learn how Todd explains this topic. Redux is a complicated pattern for first-time developers to grasp, but it really is a must for creating quality applications.
In the Angular realm, the Redux pattern is implemented in several libraries, the most popular being NgRx and NGXS. For those of you new to Redux, redux is a pattern for managing global state in an application. It was originally developed at Facebook, and since has taken off and is widely used through most modern front-end frameworks. NgRx is, by far, the most widely used Angular redux library. As such, Ultimate Courses has chosen to focus it’s offerings on NgRx. As we focus on this course, I must say upfront, I was pleasantly surprised and impressed with Todd’s approach to teaching NgRx. The course has been so well received, in fact, that even Mike Ryan (NgRx Core Team/Google Developer Expert) recommends this course as the best way to get started!
The course starts out by walking through what exactly state management is, how redux accomplishes that, and how JavaScript presents challenges with mutation.
Once you have grasped the concept of state management using the Redux pattern, the course then has you build your very own vanilla Redux store using plain TypeScript. Realizing then, that NgRx is built on top of these concepts, it’s an easy transfer into learning NgRx.
After having built a vanilla redux store, the course then walks through the process of setting up a store using the tools provided by NgRx. The course walks you through creating actions, reducers, selectors, effects. The course then walks through the process of structuring lists of entities using the Entity pattern.
Even folks with some NgRx experience will find this course helpful as it does a deep-dive into more advanced concepts like routing with the store, preloading state, and unit testing your NgRx store.
Below is a detailed list of the topics covered in this course:
After taking these courses, and comparing other available options, I can safely recommend the Angular Ultimate Package for teams looking to get into Angular Enterprise development. Todd’s down-to-earth approach of explaining complex concepts, makes these courses both fun and educational. As an added bonus, Todd does the voice-overs himself so you get to learn Angular with a British accent. Win-Win-Win.

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 Ultimate Courses
]]>
Recently, I had a need arise to programatically and recursively traverse through all *.ts files in a given project and remove all unused TypeScript imports. At the time this article was written, there was no way to do this within Visual Studio Code without opening each individual *.ts file and hitting CTRL + Shift + O on Windows/Linux. After some research, and much appreciated help from Twitter colleagues, I found a solution that works. This should work for Angular, React, Vue.js, or any plain TypeScript project.
We will use the tslint command line tool, in conjuction with the tslint-etc rules, to automatically detect and remove all unused imports in the directory, recursively. If you have a large project, the process can take some time to run. It is important to double-check all files for correctness once the fix process is complete.
Install the follow node packages required to for this process to work.
$ npm install -g typescript tslint tslint-etc
tslint config fileCreate a new file named tslint-imports.json in the root of your project. This creates a hyper-focused tslint process that will only check for unused declarations. It is important to note that this will throw tslint errors on unused imports, parameters and variables. We are only using this for the --fix process. As such, the tslint-etc rules only autofix unused imports.
This file needs the following contents:
{
"extends": [
"tslint-etc"
],
"rules": {
"no-unused-declaration": true
}
}
The following command will traverse, recursively through all *.ts files in the project and remove the unused imports. It save the files automatically in place.
BE CAREFUL! This process is only reversible if you are using a source control solution like git or svn, where you can revert changes.
$ tslint --config tslint-imports.json --fix --project .
At this point, I would highly recommend you double-check your files for correctness. This tool worked on all but 2 of my 195 *.ts files. Two of the components were incorrectly updated. I was able to spot this by running an ng build --prod as it was an Angular application. You could run a manual tslint --project . if your project doesn’t use a build tool.
Have you ever needed to dynamically load content or components in your Angular applications? How about in a way that the built-in structural directives (*ngIf*, *ngSwitch) just don’t provide? Are you also in need of the optimization benefits of using Ahead-of-Time compilation?
Well, I have good news for you…(And no you don’t have to be Chuck Norris!) If you stay tuned, I will help you get a solution up and running that will provide a solid way to choose from and load dynamically, at run-time, a set of predefined modules & components in your application.
This article is assumes you are building an Angular 6+ application generated using the Angular CLI. For information on using the Angular CLI check out the official documentation.
This arose out of a business need for the company that I work for. What’s important to note here is that many articles and examples exist on loading content dynamically in Angular, but none that I found worked reliably when compiling Angular with the
— prodor— aotflags enabled. The good news is that what I describe in this article works fantastically with Ahead-of-Time compiling.
We’re going to build a special module with a dynamic component outlet that can be included and used anywhere in your application. The only requirement is that you register, upfront, an array mapping your dynamic components to their parent modules. You will also add these modules to the lazyModules property in your angular.json file. By doing so, the compiler will pre-compile these modules. The compiler then splits them off into separate minified chunks and makes them available to the SystemJS loader at runtime, with AOT.
Assuming that you have an existing Angular 6+ CLI generated project let’s run through the following steps to scaffold the necessary parts that make up this new Dynamic Content Outlet.
Generate a new module named DynamicContentOutletModule by running the following command in your shell of choice:
$ ng g m dynamic-content-outlet
We will come back later to this module and wire things up.
Create a new file underneath the newly created folder src/app/dynamic-content-outlet named dynamic-content-outlet.registry.ts. This will serve as the placeholder for array mapping component name to module path and module name. For now, it will be an empty array as follows.
interface RegistryItem {
componentName: string;
modulePath: string;
moduleName: string;
}
/**
* A registry array of Component Name to details
* that must be updated with each new component
* that you wish to load dynamically.
*/
export const DynamicContentOutletRegistry: RegistryItem[] = [];
Create a new file underneath the folder src/app/dynamic-content-outlet/dynamic-content-outlet-error.component.ts. This will serve as the component to be rendered anytime an error occurs attempting to load a dynamic component. You can customize the template property to use any custom styles or layout that you may have. The errorMessage input must stay the same and will be fed with the actual details of the error that occurred while attempting to dynamically render your component.
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-dynamic-content-outlet-error-component',
template: `
<div></div>
`
})
export class DynamicContentOutletErrorComponent {
@Input() errorMessage: string;
constructor() {}
}
Create a new file underneath the folder src/app/dynamic-content-outlet/dynamic-content-outlet.service.ts.
DynamicContentOutletRegistry to lookup the module by componentName.static property that we will add later on to each module we wish to dynamically load named dynamicComponentsMap. This allows us to get the type literal for the given componentName so that the resolveComponentFactory can instantiate the correct component. You might ask why we didn’t just add a fourth property to the DynamicContentOutletRegistry, well this is because if we import the type in the registry, then it defeats the purpose of lazy loading these modules as the the type will be included in the main bundle.DynamicContentOutletErrorComponent is rendered instead with the error message included.import {
ComponentFactoryResolver,
ComponentRef,
Injectable,
Injector,
NgModuleFactoryLoader,
Type
} from '@angular/core';
import { DynamicContentOutletErrorComponent } from './dynamic-content-outlet-error.component';
import { DynamicContentOutletRegistry } from './dynamic-content-outlet.registry';
type ModuleWithDynamicComponents = Type<any> & {
dynamicComponentsMap: {};
};
@Injectable()
export class DynamicContentOutletService {
constructor(
private componentFactoryResolver: ComponentFactoryResolver,
private moduleLoader: NgModuleFactoryLoader,
private injector: Injector
) {}
async GetComponent(componentName: string): Promise<ComponentRef<any>> {
const modulePath = this.getModulePathForComponent(componentName);
if (!modulePath) {
return this.getDynamicContentErrorComponent(
`Unable to derive modulePath from component: ${componentName} in dynamic-content.registry.ts`
);
}
try {
const moduleFactory = await this.moduleLoader.load(modulePath);
const moduleReference = moduleFactory.create(this.injector);
const componentResolver = moduleReference.componentFactoryResolver;
const componentType = (moduleFactory.moduleType as ModuleWithDynamicComponents)
.dynamicComponentsMap[componentName];
const componentFactory = componentResolver.resolveComponentFactory(
componentType
);
return componentFactory.create(this.injector);
} catch (error) {
console.error(error.message);
return this.getDynamicContentErrorComponent(
`Unable to load module ${modulePath}.
Looked up using component: ${componentName}. Error Details: ${
error.message
}`
);
}
}
private getModulePathForComponent(componentName: string) {
const registryItem = DynamicContentOutletRegistry.find(
i => i.componentName === componentName
);
if (registryItem && registryItem.modulePath) {
// imported modules must be in the format 'path#moduleName'
return `${registryItem.modulePath}#${registryItem.moduleName}`;
}
return null;
}
private getDynamicContentErrorComponent(errorMessage: string) {
const factory = this.componentFactoryResolver.resolveComponentFactory(
DynamicContentOutletErrorComponent
);
const componentRef = factory.create(this.injector);
const instance = <any>componentRef.instance;
instance.errorMessage = errorMessage;
return componentRef;
}
}
Create a new file underneath the folder src/app/dynamic-content-outlet/dynamic-content-outlet.component.ts. This component takes an input property named componentName that will call the DynamicContentOutletService.GetComponent method passing into it componentName. The service then returns an instance of that rendered and compiled component for injection into the view. The service returns an error component instance if the rendering fails for some reason. The component listens for changes via the ngOnChanges life-cycle method. If the @Input() componentName: string; is set or changes it automatically re-renders the component as necessary. It also properly handles destroying the component with the ngOnDestroy life-cycle method.
import {
Component,
ComponentRef,
Input,
OnChanges,
OnDestroy,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { DynamicContentOutletService } from './dynamic-content-outlet.service';
@Component({
selector: 'app-dynamic-content-outlet',
template: `
<ng-container ##container></ng-container>
`
})
export class DynamicContentOutletComponent implements OnDestroy, OnChanges {
@ViewChild('container', { read: ViewContainerRef })
container: ViewContainerRef;
@Input() componentName: string;
private component: ComponentRef<{}>;
constructor(private dynamicContentService: DynamicContentOutletService) {}
async ngOnChanges() {
await this.renderComponent();
}
ngOnDestroy() {
this.destroyComponent();
}
private async renderComponent() {
this.destroyComponent();
this.component = await this.dynamicContentService.GetComponent(
this.componentName
);
this.container.insert(this.component.hostView);
}
private destroyComponent() {
if (this.component) {
this.component.destroy();
this.component = null;
}
}
}
Make sure your src/app/dynamic-content-outlet/dynamic-content-outlet.module.ts file looks like the following:
import { CommonModule } from '@angular/common';
import {
NgModule,
NgModuleFactoryLoader,
SystemJsNgModuleLoader
} from '@angular/core';
import { DynamicContentOutletErrorComponent } from './dynamic-content-outlet-error.component';
import { DynamicContentOutletComponent } from './dynamic-content-outlet.component';
import { DynamicContentOutletService } from './dynamic-content-outlet.service';
@NgModule({
imports: [CommonModule],
declarations: [
DynamicContentOutletComponent,
DynamicContentOutletErrorComponent
],
exports: [DynamicContentOutletComponent],
providers: [
{
provide: NgModuleFactoryLoader,
useClass: SystemJsNgModuleLoader
},
DynamicContentOutletService
]
})
export class DynamicContentOutletModule {}
Phew! Take a deep breath and grab a cup of coffee (french press fair trade organic dark roast). The hard work is behind you. Next we will go through the process of actually putting this new module into play!

For any component that you would like dynamically rendered you need to do the following four steps. These steps must be followed exactly.
Confirm that the component is listed in the entryComponents array in the module that the component is a part of.
Add to the module, a new static property called dynamicComponentsMap. This allows us to get the type literal for the given componentName so that the resolveComponentFactory can instantiate the correct component.
You might ask why we didn’t just add a fourth property to the
DynamicContentOutletRegistrynamedcomponentType; well this is because if we import the type in the registry, then it defeats the purpose of lazy loading these modules as the the type will be included in the main bundle.
A prepared module might look as follows:
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { DynamicMultipleOneComponent } from './dynamic-multiple-one.component';
import { DynamicMultipleTwoComponent } from './dynamic-multiple-two.component';
@NgModule({
declarations: [MySpecialDynamicContentComponent],
imports: [CommonModule],
entryComponents: [MySpecialDynamicContentComponent]
})
export class MySpecialDynamicContentModule {
static dynamicComponentsMap = {
MySpecialDynamicContentComponent
};
}
For any component that you would like dynamically rendered, add a new entry to the DynamicContentOutletRegistry array in src/app/dynamic-content-outlet/dynamic-content-outlet.registry.ts.
The following properties must be filled out:
componentName: This should match exactly the name of the Component you wish to load dynamically.
modulePath: The absolute path to the module containing the component you wish to load dynamically. This is only the path to the module and does NOT include moduleName after a #.
moduleName: This is the exact name of the module.
{
componentName: 'MySpecialDynamicContentComponent',
modulePath: 'src/app/my-special-dynamic-content/my-special-dynamic-content.module',
moduleName: 'MySpecialDynamicContentModule'
},
In your angular.json update the projects > ** > architect > build > options > lazyModules array and add an item for each module that you added to the registry in order for the Angular AOT compiler to detect and pre-compile your dynamic modules. If you have multiple projects in a folder, make sure you add this for the correct project you are importing and using dynamic modules in. The updated file will look similar to this:
{
...
"projects": {
"angular-dynamic-content": {
...
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
...
"lazyModules": ["src/app/my-special-dynamic-content/my-special-dynamic-content.module"]
},
}
}
}
}
}
Up to this point you have created your dynamic content outlet module and registered your components to be available in the outlet. The last thing we need to do is wire up our new DynamicContentOutletModule to be used in our application. In order to do so you need to:
DynamicContentOutletModule to the imports array of any feature module or the main AppModule of your Angular application.imports array@NgModule({
...
imports: [
...
DynamicContentOutletModule
],
...
})
export class AppModule {}
<app-dynamic-content-outlet [componentName]="'MyComponent'">
</app-dynamic-content-outlet>
This is very similar in nature to Angular’s built-in <router-outlet>/</router-outlet> tag.
ng serve --prod ing!If you are interested in a more in-depth real-world example, then check out the Github Repository which will demonstrate the following:
GitHub Repository Example https://github.com/wesleygrimes/angular-dynamic-content
Hopefully you have found this solution helpful. Here is the full GitHub repository example for you to clone and play around with. PR’s are welcome, appreciated, encouraged and accepted!
I would highly recommend enrolling in the Ultimate Angular courses. 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
I want to take a moment and thank all those I was able to glean this information from. I did not come up with all this on my own, but I was able to get a working solution by combining parts from each of these articles!
Here is what you need to know about dynamic components in Angular
The Need for Speed Lazy Load Non-Routable Modules in Angular
Also, a huge thank you to Medium reader ivanwonder and Github user Milan Saraiya for pointing this out and providing a fork example of resolution.
This article is not intended to be a tutorial on NgRx. There are several great resources that currently exist, written by experts much smarter than me. I highly suggest that you take time and learn NgRx and the redux pattern before attempting to implement these concepts.
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.
This article assumes that you are building an Angular v7 CLI generated application.
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}
Create a Root Store Module as a proper Angular NgModule’s that bundle together NgRx store logic. Feature store modules will be imported into the Root Store Module allowing for a single root store module to be imported into your application’s main App Module.
RootStoreModule using the Angular CLI:$ ng g module root-store --flat false --module app.module.ts
RootState interface to represent the entire state of your application using the Angular CLI:$ 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.
Create feature store modules as proper Angular NgModule’s that bundle together feature slices of your store, including state, actions, reducer, selectors, and effects. Feature modules are then imported into your RootStoreModule. This will keep your code cleanly organizing into sub-directories for each feature store. In addition, as illustrated later on in the article, public actions, selectors, and state are name-spaced and exported with feature store prefixes.
In the example implementation below we will use the feature name MyFeature, however, this will be different for each feature you generate and should closely mirror the RootState property name. For example, if you are building a blog application, a feature name might be Post.
Depending on the type of feature you are creating you may or may not benefit from implementing NgRx Entity. If your store feature slice will be dealing with an array of type then I suggest following the Entity Feature Module implementation below. If building a store feature slice that does not consist of a standard array of type, then I suggest following the Standard Feature Module implementation below.
MyFeatureStoreModule 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;
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
});
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;
}
}
}
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
);
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 }))
)
)
)
);
}
MyFeatureStoreModule 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 { User } from '../../models';
export enum ActionTypes {
LOGIN_REQUEST = '[My Feature] Login Request',
LOGIN_FAILURE = '[My Feature] Login Failure',
LOGIN_SUCCESS = '[My Feature] Login Success'
}
export class LoginRequestAction implements Action {
readonly type = ActionTypes.LOGIN_REQUEST;
constructor(public payload: { userName: string; password: string }) {}
}
export class LoginFailureAction implements Action {
readonly type = ActionTypes.LOGIN_FAILURE;
constructor(public payload: { error: string }) {}
}
export class LoginSuccessAction implements Action {
readonly type = ActionTypes.LOGIN_SUCCESS;
constructor(public payload: { user: User }) {}
}
export type Actions =
| LoginRequestAction
| LoginFailureAction
| LoginSuccessAction;
state.ts file in the app/root-store/my-feature-store directory:import { User } from '../../models';
export interface State {
user: User | null;
isLoading: boolean;
error: string;
}
export const initialState: State = {
user: null,
isLoading: false,
error: null
};
reducer.ts file in the app/root-store/my-feature-store directory:import { Actions, ActionTypes } from './actions';
import { initialState, State } from './state';
export function featureReducer(state = initialState, action: Actions): State {
switch (action.type) {
case ActionTypes.LOGIN_REQUEST:
return {
...state,
error: null,
isLoading: true
};
case ActionTypes.LOGIN_SUCCESS:
return {
...state,
user: action.payload.user,
error: null,
isLoading: false
};
case ActionTypes.LOGIN_FAILURE:
return {
...state,
error: action.payload.error,
isLoading: false
};
default: {
return state;
}
}
}
selectors.ts file in the app/root-store/my-feature-store directory:import {
createFeatureSelector,
createSelector,
MemoizedSelector
} from '@ngrx/store';
import { User } from '../../models';
import { State } from './state';
const getError = (state: State): any => state.error;
const getIsLoading = (state: State): boolean => state.isLoading;
const getUser = (state: State): any => state.user;
export const selectMyFeatureState: MemoizedSelector<
object,
State
> = createFeatureSelector<State>('myFeature');
export const selectMyFeatureError: MemoizedSelector<
object,
any
> = createSelector(
selectMyFeatureState,
getError
);
export const selectMyFeatureIsLoading: MemoizedSelector<
object,
boolean
> = createSelector(
selectMyFeatureState,
getIsLoading
);
export const selectMyFeatureUser: MemoizedSelector<
object,
User
> = createSelector(
selectMyFeatureState,
getUser
);
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()
loginRequestEffect$: Observable<Action> = this.actions$.pipe(
ofType<featureActions.LoginRequestAction>(
featureActions.ActionTypes.LOGIN_REQUEST
),
switchMap(action =>
this.dataService
.login(action.payload.userName, action.payload.password)
.pipe(
map(
user =>
new featureActions.LoginSuccessAction({
user
})
),
catchError(error =>
observableOf(new featureActions.LoginFailureAction({ error }))
)
)
)
);
}
Now that we have created our feature module, either Entity or Standard typed above, we need to import the parts (state, actions, reducer, effects, selectors) into the Angular NgModule for the feature. In addition, we will create a barrel export in order to make imports in our application components clean and orderly, with asserted name-spaces.
app/root-store/my-feature-store/my-feature-store.module.ts with the following:import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { MyFeatureStoreEffects } from './effects';
import { featureReducer } from './reducer';
@NgModule({
imports: [
CommonModule,
StoreModule.forFeature('myFeature', featureReducer),
EffectsModule.forFeature([MyFeatureStoreEffects])
],
providers: [MyFeatureStoreEffects]
})
export class MyFeatureStoreModule {}
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.import * as MyFeatureStoreActions from './actions';
import * as MyFeatureStoreSelectors from './selectors';
import * as MyFeatureStoreState from './state';
export { MyFeatureStoreModule } from './my-feature-store.module';
export { MyFeatureStoreActions, MyFeatureStoreSelectors, MyFeatureStoreState };
Now that we have built our feature modules, let’s pick up where we left off in best practice ##1 and finish building out our RootStoreModule and RootState.
app/root-store/root-state.ts and add a property for each feature that we have created previously:import { MyFeatureStoreState } from './my-feature-store';
import { MyOtherFeatureStoreState } from './my-other-feature-store';
export interface State {
myFeature: MyFeatureStoreState.State;
myOtherFeature: MyOtherFeatureStoreState.State;
}
app/root-store/root-store.module.ts by importing all feature modules, and importing the following NgRx modules: StoreModule.forRoot({}) and EffectsModule.forRoot([]):import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { MyFeatureStoreModule } from './my-feature-store/';
import { MyOtherFeatureStoreModule } from './my-other-feature-store/';
@NgModule({
imports: [
CommonModule,
MyFeatureStoreModule,
MyOtherFeatureStoreModule,
StoreModule.forRoot({}),
EffectsModule.forRoot([])
],
declarations: []
})
export class RootStoreModule {}
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:import { createSelector, MemoizedSelector } from '@ngrx/store';
import { MyFeatureStoreSelectors } from './my-feature-store';
import { MyOtherFeatureStoreSelectors } from './my-other-feature-store';
export const selectError: MemoizedSelector<object, string> = createSelector(
MyFeatureStoreSelectors.selectMyFeatureError,
MyOtherFeatureStoreSelectors.selectMyOtherFeatureError,
(myFeatureError: string, myOtherFeatureError: string) => {
return myFeature || myOtherFeature;
}
);
export const selectIsLoading: MemoizedSelector<
object,
boolean
> = createSelector(
MyFeatureStoreSelectors.selectMyFeatureIsLoading,
MyOtherFeatureStoreSelectors.selectMyOtherFeatureIsLoading,
(myFeature: boolean, myOtherFeature: boolean) => {
return myFeature || myOtherFeature;
}
);
app/root-store/index.ts barrel export for your store with the following:import { RootStoreModule } from './root-store.module';
import * as RootStoreSelectors from './selectors';
import * as RootStoreState from './state';
export * from './my-feature-store';
export * from './my-other-feature-store';
export { RootStoreState, RootStoreSelectors, RootStoreModule };
Now that we have built our Root Store Module, composed of Feature Store Modules, let’s add it to the main 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';
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());
}
}
Once we have completed implementation of the above best practices our Angular application structure should look very similar to something like this:
├── 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
I have put together a fully working example of the above best practices. It’s a simple Chuck Norris Joke Generator that has uses @angular/material and the http://www.icndb.com/ api for data.
https://github.com/wesleygrimes/angular-ngrx-chuck-norris
You can see the live demo at https://angular-ngrx-chuck-norris.stackblitz.io and here is the Stackblitz editor:
angular-ngrx-chuck-norris - StackBlitz
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!
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
]]>