May 16, 2022 by
Category:

NgRx is a state management library. In this tutorial, you will create an Angular Task/ToDo list
application that uses NgRx to manage its state.
The tutorial will follow these steps:
• Check Setup
• Copy the Lab Starter Project
• See how the Application Works
• Add NgRx to the project
• Use NgRx to Pull Data from a Server
• Adding new Tasks

Part 1 – Check Setup

This tutorial uses a specific setup that should be done to follow the steps. Git, Node, Angular CLI, the Angular Framework, and the NgRx. Of these, Git, Node, and Angular CLI should be set up ahead of time. In this part of the tutorial, we will check the setup of these items and make sure it conforms to what we need.

Git

When a new Angular project is created, Angular CLI creates a local Git repository for the
project as well. In order to use the repository, you would need to have git installed.
1. Open a Terminal.
2. Execute the following command:
git –version
Your output should include the version number as
git version 2.32.0
Any version of Git 2.x or above is OK.

If the output says “‘git’ is not recognized…” then git is not installed. If Git is NOT
installed and you want to use it then you need to run this command to install it
sudo apt install git
While Git can be of help during Angular development, it is not required for this tutorial. The
the choice to use it is up to you.


NodeJS


NodeJS IS required for Angular development. We will be using it in this lab to install a
new Angular project and to add NgRx to the project.
3. Execute the following command.
node –version
4. Your output should include the version number
v16.14.2
Any version of NodeJS 14.x or later will work for this lab.
If the output says “‘node’ is not recognized…” then node is not installed. If Node is NOT
installed and you want to use it then you need to download from https://nodejs.org/ and
follow instructions on the site to install it.


Angular CLI

1. Check if Angular CLI is already installed
ng –version
If Angular CLI is installed the command’s output should show the version. Any 12.x
version will work for this lab.


If the output says “‘ng’ is not recognized…” then Angular CLI is not installed.
To install Angular CLI follow these instructions:
▪ Execute the following command to install Angular CLI:
npm install -g @angular/cli@12
Note, we used the global (-g) option to install the tool in a central location. This will let
us run ng (the CLI command) from any directory.
▪ Wait for the installation to complete.
▪ Enter this command to verify that the install worked as expected.
ng –version
The output should verify that version 12.x.x of Angular CLI has been installed.


Part 2 – Copy the Lab Starter Project

In this part, you will copy the starter project and get it up and running. The starter project
implements a task/todo application using hard-coded data. Later in the tutorial, you will
integrate NgRx with the application.
1. Open the Files and navigate to the Home\LabFiles folder.


2. Right-click TaskAppStarter.zip and select Extract to…
3. Navigate to Home\LabWork and click Select.


4. Go to the Home\LabWork folder, you should see the TaskAppStarter folder.
5. Open TaskAppStarter and should see the taskapp folder.
6. Open taskapp, you will see the project files and directories including package.json:


7. In the Terminal, navigate into the project directory:
cd LabWork/TaskAppStarter/taskapp
8. Execute the following command to install project dependencies:
npm install
Wait for the installation to complete. This could take a few minutes.
You should see a similar result when the command is completed:


9. Execute the following command to start the development server:
npm start
Wait for the startup to complete. This could take a few minutes.

You should get this message when the command is completed:


10. Open a Mozilla browser to the following URL:
http:\localhost:4299
The port has been set (in package.json) to 4299 so that it does not interfere with any other
Angular apps you might be running.


Part 3 – See how the Application Works

1. When first started, the application should look like this:


To see how the application works, you will try the following scenarios:
• Create a new task
• Delete an item
• Mark an item as completed
• Select and Unselect a task
• Edit a selected task
• Cancel an Edit

Create new task:
2. Click on the Add button.
A new item will be added [300] as shown below.


Delete an item:
3. Click on top of the item “300, Task 300,”.
The Edit area will open up showing the item.


4. Click the Delete button in the Edit area.
The Edit area will close and the edited item will be removed from the list.

Mark an item as completed:
5. Click the checkbox next to item “102, Task 02,”
The checkbox will go from un-checked to checked.


6. Click the checkbox again.
The checkbox goes from checked back to un-checked.


Select and unselect a task:
7. Click on “102, Task 02,”.
The item will be selected. When an item is selected:The selected item is displayed in the list in Bold text.

An Edit section opens up which also displays the selected item.


8. Click the item again to unselect it.
Edit a selected task:
9. Click on “101, Task 01,”
10. In the Edit area change the Task text to “Call the office”.
11. Click the Save button in the Edit area.
The Edit area will close and the edited item will display the changes you made.


Cancel an edit:
12. Select item “101, Task 01,” by clicking on it.
The Edit area will open up.
13. Click the ‘Cancel’ button in the Edit area.
The Edit area will close.
After going through the scenarios you should have a good understanding of how the
application is supposed to work.
These behaviors should work exactly the same after we have refactored the application to
use NgRx for state management.


Part 4 – Add NgRx to the Project

To use NgRx features you will need to install the NgRx packages into the project.
1. Open up a second Terminal.
2. Navigate to the project directory:
cd LabWork/TaskAppStarter/taskapp
3. Execute the following command to install the NgRx Store and Effects packages:
npm install –save @ngrx/store@12 @ngrx/effects@12
Next, we’ll need to add a directory and some files for our NgRx integration code.
4. Open Visual Code. You can click the link on the left menu.


5. Click File > Open Folder.
6. Navigate to Home/LabWork/TaskAppStarter/taskapp and click OK

7. Check the box and click Yes, I trust the authors.


8. Your project will be shown as below.



9. Expand taskapp\src\app\, then right-click on the app folder and select New Folder.
10. Enter state and hit enter. You new folder will be shown:


11. Right-click on the state folder and select New File and then create the following
files:
\LabWork\taskapp\src\app\state\index.ts
\LabWork\taskapp\src\app\state\actions.ts
\LabWork\taskapp\src\app\state\reducers.ts
\LabWork\taskapp\src\app\state\effects.ts
12. Make sure you created the 4 files in the state folder.


Coding Actions
13. Edit the actions.ts file, and add the following contents:
import { createAction, props } from ‘@ngrx/store’;
import { Task } from ‘../model/task.model’;
export const loadTasks = createAction(‘[Tasks] load’)
export const loadTasksSuccess = createAction(‘[Tasks] load success’,
props<{response:Task[]}>())
14. Save actions.ts

Coding Reducers
15. Edit the reducers.ts file, add the following contents:
import { Action, createReducer, on } from ‘@ngrx/store’;
import { Task } from ‘../model/task.model’;
import * as taskActions from ‘./actions’;
export interface State {
tasks: Task[];
selectedTask: Task;
}
export const getState = (state: State) => { return state; };
export const initialState: State = {
tasks: [{ id: 101, title: ‘take out garbage’, done: false }],
selectedTask: new Task
};
export function reducer(state: State | undefined, action: Action): any
{
return taskReducer(state, action);
}
const taskReducer = createReducer( initialState,
on(taskActions.loadTasks, (state) => ({ …state })),
on(taskActions.loadTasksSuccess,(state,action) =>
({…state, tasks: action.response})),
);
16. Save the reducers.ts file.

Coding Effects
17. Edit the effects.ts file, add the following contents:
import { Injectable } from ‘@angular/core’;
import { Actions, createEffect, ofType } from ‘@ngrx/effects’;
import { HttpClient } from ‘@angular/common/http’;
import { map, exhaustMap, catchError, tap } from ‘rxjs/operators’;
import { Observable, of } from ‘rxjs’;
import * as taskActions from ‘./actions’;
import { nullTask, Task } from ‘../model/task.model’;
@Injectable({ providedIn: ‘root’ })
export class TaskEffects {
constructor(private actions$: Actions, private http: HttpClient){}
// http requests
url: string = ‘http://localhost:3000/tasks’;
private getTasks = (): Observable =>
this.http.get(${this.url});
// triggered by the loadTasks action
loadTasks$ = createEffect(() =>
this.actions$.pipe(
ofType(taskActions.loadTasks),
exhaustMap(action =>
this.getTasks().pipe(
map((response: any) =>
taskActions.loadTasksSuccess({ response }))
)
)
)
);
}
Effects make extensive use of rxjs (reactive extensions for JavaScript) coding techniques.
Effects listen in on all actions being sent to the reducer but only react to the action
specified in the ‘ofType()’ call. In the above case the code listens for the ‘loadTasks’ action
and reacts by making an HTTP GET request. When data returns from the request it is
used to create a ‘loadTasksSuccess’ action that updates the state.tasks array.
18. Save the effects.ts file.
Coding Selectors
19. Edit the index.ts file and add the following contents:
import {
ActionReducerMap,
createFeatureSelector,
createSelector,
} from ‘@ngrx/store’;
import * as fromReducer from ‘./reducers’;
export interface State {
task: fromReducer.State
}
export const reducers: ActionReducerMap = {
task: fromReducer.reducer
};
export const getStateSelector = createSelector(
createFeatureSelector(‘task’),
fromReducer.getState
);
The code in the above file creates ‘selectors’ that are used to select a subset or slice of the
overall state object. At this point the state only includes the tasks array so we will retrieve
the entire state (using getStateSelector) and not a subset. As more features are coded into
the app you would create additional selectors ( getSelectedTaskSelector, etc) to support
them.
20. Save the index.ts file.
Next, we will update the app.module.ts file which will integrate the state code with the
rest of the application.

Updating the App Module
21. Edit the app.module.ts file.


22. Add the following imports after the other imports in the app.module.ts file:
import { StoreModule } from ‘@ngrx/store’;
import { reducers } from ‘./state’;
import { TaskEffects } from ‘./state/effects’;
import { EffectsModule } from ‘@ngrx/effects’;


23. Add the following module code to the end of the ‘imports:’ array as shown in bold:
imports: [
BrowserModule,
HttpClientModule,
FormsModule,
StoreModule.forRoot(reducers),
EffectsModule.forRoot([TaskEffects])
],
[remember to add the comma after FormsModule]


24. Save the app.module.ts file.
At this point, the application should still be running and should not be exhibiting any
compile or runtime errors.
We’ve added a bunch of code but our application is not yet using the new code. In the
next part of this lab you will update the application to start using the NgRx code.

Part 5 – Use NgRx to Pull Data from a Server

In this part of the tutorial, you will refactor the application so that data is retrieved from a
REST server instead of from a hard-coded array.
1. In a new Terminal, switch to the taskapp folder:
cd LabWork/TaskAppStarter/taskapp
2. Execute the following function to start the project REST server:
npm run server
You should see:


3. Once the server is up, try accessing it by opening the following URL in a web
browser:
http://localhost:3000/tasks

You should see the following three task records in the browser:
[
{
“id”: 201,
“title”: “Call office”,
“done”: true
},
{
“id”: 202,
“title”: “Buy milk”,
“done”: false
},
{
“id”: 203,
“title”: “Wash the car”,
“done”: false
}
]
4. Open the state\effects.ts file and locate the following code:
// http requests
url: string = ‘http://localhost:3000/tasks’;
private getTasks = (): Observable => this.http.get($ {this.url});
The getTasks function uses the Angular HttpClient to make an HTTP GET call to the
same URL that we just opened in the browser.
5. Locate the following code in the state\effects.ts file:
// triggered by the loadTasks action
loadTasks$ = createEffect(() =>
this.actions$.pipe(
ofType(taskActions.loadTasks),
exhaustMap(action =>
this.getTasks().pipe(
map((response: any) =>
taskActions.loadTasksSuccess({response}))
)
)
)
);

This code is triggered by NgRx when the loadTasks action is dispatched to the store. The
‘response’ is the data that is returned from the REST service, in JSON format. That data is
passed to the loadTaskSuccess action. Fromthere the action is passed to the reducer which
uses it to update the application state:
// from state\reducers.ts
on(taskActions.loadTasksSuccess,
(state,action) =>({…state, tasks: action.response})),
To trigger this sequence we need to add some code to constructor of the
tasklist.component.ts file.
6. Edit the app\tasks\tasklist.\tasklist.component.ts file.


7. Add the following imports at the top of the file after the other imports:
import { Store } from ‘@ngrx/store’;
import * as actions from ‘../../state/actions’;
import * as state from ‘../../state’;
import {takeUntil} from ‘rxjs/operators’;
import { Subject, Observable } from ‘rxjs’;
8. Update the constructor as shown below in bold to inject an instance of the Store:
constructor(private readonly store: Store) {
}

9. Add the following line of code in bold to the the constructor. This code calls the
loadTasks() action:
constructor(private readonly store: Store) {
this.store.dispatch(actions.loadTasks());
}
The above code will trigger the HTTP call but we still need to read the state from the
store before we can use the retrieved data.
10. Add the following bolded code into the constructor after the ‘store.dispatch’ line:
constructor(private readonly store: Store) {
this.store.dispatch(actions.loadTasks());
this.store.select(state.getStateSelector).pipe(
takeUntil(this.destroy$)
).subscribe((data:any) => {
this.tasks = data.tasks;
// this.selectedTask = data.selectedTask;
// this.writableSelectedTask =
JSON.parse(JSON.stringify(this.selectedTask));
});
}
This code call the ‘getStateSelector’ selector which returns an Observable. The returned
data is assigned to the local ‘this.tasks’ property in the subscribe callback. Once the data is
in ‘this.tasks’ Angular will repaint the tasks list.
11. Add the following code after the constructor but inside the TaskListComponent
class:
destroy$: Subject = new Subject();
This code is used to terminate the selector subscription when the component is destroyed.
12. Save the tasklist.component.ts file.
13. Check on one of the Terminals and fix any errors or exceptions reported by the
compiler or that show up in the JavaScript console in the browser.
14. Go to the browser and enter the following link:
http://localhost:4299/
You should see the list showing the REST data and not the hard coded data.
Data from the REST server


15. Try the scenarios from earlier in the lab.
• Create a new task
• Delete an item
• Mark an item as completed
• Select and Unselect a task
• Edit a selected task
• Cancel an Edit
Do any of them fail? In fact, a few of them fail:
• Create a new task
• Delete an item
• Mark an item as completed
• Edit a selected task
When these scenarios fail you should find that an error similar to the one below shows up
in the JavaScript console:
core.js:6498 ERROR TypeError: Cannot assign to read-only property ‘x’
of object

In each of the failed cases, our old code is trying to modify the state directly. But the state
itself has been marked as ReadOnly by NgRx and does not allow the change. In order to
fix these issues we need to update our actions and reducer to handle these cases:
• Adding a Task to the Task array
• Deleting a Task from the Task array
• Updating the ‘done’ or ‘title’ fields of an existing Task record.


Part 6 – Adding a Task to the Task Array


In this part, we will implement an additional action and the corresponding case in the reducer
to support adding a task to the task array.
1. Open the state\actions.ts file in an editor.
2. Add the following action after the other actions in the file:
export const addTask = createAction(‘[Tasks] add’, props<{task:Task}>() )
3. Save the state\actions.ts file.
4. Open the state\reducers.ts file in an editor.
5. Add the following code after the other code in the ‘taskReducer’ function:
on(taskActions.addTask,(state,action)=>{
let clonedArray = JSON.parse(JSON.stringify(state.tasks))
clonedArray.push(action.task);
let newState = {…state, tasks:clonedArray}
return newState;
})
This code handles the addTask action. The existing task array is cloned and the new task
is pushed onto the end of the array. The cloned array along with the new added task are
returned.
6. Save the state\reducers.ts file.
7. Open the app\task\tasklist.component.ts file in an editor.
8. Locate the existing addTask() method. This method is called when the Add button is
pressed:
addTask(){
let id = this.idx++;
let title = ‘Task ‘ + id;
let task:Task = {id:id, title:title, done:false}
this.tasks.push(task);
}
9. Comment out the last line. This is the line that is producing the error.
// this.tasks.push(task);
10. Instead of modifying the local array directly we will add the following code in bold
after the commented out line:
// this.tasks.push(task);
this.store.dispatch(actions.addTask({task:task}));
11. Save the state\actions.ts file.
12. Go back and refresh the browser.
13. Re-run the ‘Create a new task’ scenario twice [click Add].
New items can now be added and no errors should show up in the JavaScript console.
The Add button was clicked two times and created two new records.


The same steps we took to implement the ‘Add’ functionality can be used to implement
the other use-cases:
• Deleting a Task from the Task array
• Updating the ‘done’ or ‘title’ fields of an existing Task record.
These use-cases are left as an extra-credit exercise for students who are interested.
Two solution projects are available for reference:
• \LabFiles\Solutions\Angular NgRx Project Solution.zip
• \LabFiles\Solutions\Angular NgRx Project Solution-extra.zip
14. Close the browser and Visual Code.
15. In the Terminals, press CTRL + C to stop the servers and then close the Terminals.


Part 7 – Review

This tutorial covered the following:
• Check Setup
• Copy the Lab Starter Project
• See how the Application Works
• Add NgRx to the project
• Use NgRx to Pull Data from a Server
• Adding new Tasks
26