
Introduction to @ngrx/store
@ngrx/store offers controlled, reactive state management capabilities to Angular applications. Powered by RxJS, the Store is designed to assist in creating reliable, performant, and versatile applications on top of Angular.
In this tutorial, we’ll learn how @ngrx works by creating a simple shopping list that stores user's items. You can find the code for this article here: https://github.com/developer-school/introduction-to-ngrx-store
Here is an image of the shopping list we want to create:

This will allow a user to complete the following:
- Add items to the shopping list.
- Delete items from the shopping list.
Setting up the Project
Let’s use the Angular CLI to begin a new Angular project:
# Ensure you've got the Angular CLI installed
$ npm i @angular/cli -g
# Create a new Angular project
$ ng new ngrx-shopping-list
> Don't add routing at this stage
> Style: SCSS
# Change directory
$ cd ngrx-shopping-list
We can then go ahead and install @ngrx/store and run this in the browser:
$ npm install @ngrx/store
$ ng serve && code .
You can also use the shortcut ng add @ngrx/store
to have the Angular CLI automate some of what we cover in this article.
Adding the Shopping List
For this project, users are going to use a simple form to add items to a shopping list, therefore, we’ll need a model to represent this. Create the ShoppingItem
model at: src/app/store/models/shopping-item.model.ts
:
export interface ShoppingItem {
id?: string;
name: string;
}
Adding an Action
In @ngrx/store, Actions represent the main building blocks that express the unique events occurring throughout the Angular application. Every user interaction that triggers a state update should be described using Actions.
Actions have two main properties: type
and an optional payload
:
type
is a read-only string that represents the type of action dispatched into the application.payload
is an optional property that adds any related data required for completing the action.
We can go ahead and add two main actions to our application at src/app/store/actions/shopping.actions.ts
:
import { Action } from '@ngrx/store';
import { ShoppingItem } from '../models/shopping-item.model';
export enum ShoppingActionTypes {
ADD_ITEM = '[SHOPPING] Add Item',
}
export class AddItemAction implements Action {
readonly type = ShoppingActionTypes.ADD_ITEM
constructor(public payload: ShoppingItem) { }
}
export type ShoppingAction = AddItemAction
We're creating an enum
which contains the ADD_ITEM
Action type
. This can then be used inside of the AddItemAction
class which contains a payload
(i.e. a new ShoppingItem
).
Adding a Reducer
Reducers are the second essential aspect of an @ngrx/store application. They play the critical role of managing the transitions taking place from one state to the next.
As pure functions, reducers generate the same output for a given input. Reducers accept two arguments, which are the previous state as well as the latest dispatched Action
, and either returns a newly changed state or the unchanged state.
To add a reducer
to our project, let’s create this folder and file: /src/app/store/reducers/shopping.reducer.ts.
import { ShoppingActionTypes, ShoppingAction } from '../actions/shopping.actions';
import { ShoppingItem } from '../models/shopping-item.model';
const initialState: Array<ShoppingItem> = [
{
id: "1775935f-36fd-467e-a667-09f95b917f6d",
name: 'Diet Coke',
}
];
export function ShoppingReducer(state: Array<ShoppingItem> = initialState, action: ShoppingAction) {
switch (action.type) {
case ShoppingActionTypes.ADD_ITEM:
return [...state, action.payload];
default:
return state;
}
}
The reducer takes in the initial state which contains our Diet Coke
and a dispatched action
. It then checks which action.type
has been fired and subsequently reduces this to a value that represents the same structure as before but with different data.
For example, whenever we call the ADD_ITEM
action, the reducer
takes the previous state and appends the action.payload
(i.e. the new Shopping
item) to the end of the list.
This then gives us a new version of state that matches the previous structure or in the case of no action.type
being found inside of our shoppingReducer
, it simply returns the state
.
AppState
Next, we can create an interface for our AppState
inside of src/store/models/app-state.model.ts
.
import { ShoppingItem } from './shopping-item.model';
export interface AppState {
readonly shopping: Array<ShoppingItem>
}
This will allow us to type the structure of our Store and make it easier to select slices of state in the future. We'll be using this in a moment, until then, we need to register our shopping
reducer as a root
reducer.
Adding imports to App.Module
Let’s update the /src/app/app.module.ts
file with imports of @ngrx/store
and the reducer we created earlier. We'll also be using ngModel
later to capture user input, ensure you've imported FormsModule
too.
import { FormsModule } from '@angular/forms';
import { StoreModule } from '@ngrx/store';
import { ShoppingReducer } from './reducers/shopping.reducer';
@NgModule({
imports: [
BrowserModule,
FormsModule,
StoreModule.forRoot({
shopping: ShoppingReducer
})
],
Adding Reading, Writing, and Deleting Capabilities
Here's the fun part. We can wire the store up to our UI and see our shopping list in action!
I've elected to add some slight CSS to our list that you may want to add inside of app.component.scss
:
#wrapper {
text-align: center;
color: white;
display: flex;
height: 100vh;
flex-direction: column;
justify-content: center;
margin: 0px;
width: 400px;
margin: 0 auto;
}
#shopping-list {
box-shadow: 20px 20px 0px #222f3e;
}
form {
display: flex;
flex-direction: row;
}
form > input {
flex-grow: 1;
outline: none;
padding-left: 4px;
border: 0px;
height: 20px;
}
form > button {
border: 0px;
background: #10ac84;
color: white;
outline: none;
&:hover {
background: #006266;
}
}
ul {
list-style: none;
background: #1dd1a1;
padding: 10px 0px;
margin: 0px;
}
li {
padding-bottom: 4px;
}
h2 {
background: #10ac84;
padding: 10px 0px;
margin: 0px;
}
I've also changed the background color of the body
inside of styles.scss
:
body {
background-color: #576574;
}
Selecting state
In order to display shopping
items, we'll need to select
the shopping
slice of state from our store. @ngrx makes this easy with a select
method which provides us with a reactive Observable:
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { ShoppingItem } from './store/models/shopping-item.model';
import { AppState } from './store/models/app-state.model';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
shoppingItems: Observable<Array<ShoppingItem>>;
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.shoppingItems = this.store.select(store => store.shopping);
}
}
Our shoppingItems
observable matches the shopping
reducer that we defined in our StoreModule.forRoot()
earlier. We also created the AppState
interface to match this and give us strong typing.
As a note, we could have also selected our shopping
slice by string, providing it matched the reducer definition:
this.shoppingItems = this.store.select('shopping');
We can then display this on screen with the following mark-up:
<div id="wrapper">
<div id="shopping-list">
<div id="list">
<h2>Shopping List</h2>
<ul>
<li *ngFor="let shopping of shoppingItems | async">
{{ shopping.name }}
</li>
</ul>
</div>
</div>
</div>
The most important consideration here is the automatic subscription to our shoppingItems
observable with the async
pipe. Angular will then handle the subscription/unsubscription events for us automatically.
Here's what we have thus far:

Adding new items
In order to add a new item to our store, we'll need to dispatch
an AddItemAction
with the payload of a new ShoppingItem
. Let's take a look at how we can do that:
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { v4 as uuid } from 'uuid';
import { AppState } from './store/models/app-state.model';
import { ShoppingItem } from './store/models/shopping-item.model';
import { AddItemAction } from './store/actions/shopping.actions';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
shoppingItems: Observable<Array<ShoppingItem>>;
newShoppingItem: ShoppingItem = { id: '', name: '' }
constructor(private store: Store<AppState>) { }
ngOnInit() {
this.shoppingItems = this.store.select(store => store.shopping);
}
addItem() {
this.newShoppingItem.id = uuid();
this.store.dispatch(new AddItemAction(this.newShoppingItem));
this.newShoppingItem = { id: '', name: '' };
}
}
We're able to generate a unique id
with the uuid
library. We don't have to install this into our project as it comes by default as a dependency to angular
. At this point we'll also need to update our app.component.html
to add our form
:
<div id="wrapper">
<div id="shopping-list">
<div id="list">
<h2>Shopping List</h2>
<ul>
<li *ngFor="let shopping of shoppingItems | async">
{{ shopping.name }}
<br><br>
</li>
</ul>
</div>
<form (ngSubmit)="addItem()">
<input type="text" [(ngModel)]="newShoppingItem.name" placeholder="Item" name="itemName"/>
<button type="submit" >+</button>
</form>
</div>
</div>
Here's what happens once we hit the +
button:
- The
AddItemAction
is fired with the payload ofnewShoppingItem
. - Our
shoppingReducer
sees the newaction
and filters byaction.type
. - As the
action.type
is[SHOPPING] Add Item
thenewShoppingItem
is added to the end of our array:[...state, action.payload]
, - The
shopping
slice of state is updated and as we're subscribed to changes with theasync
pipe our UI updates.

Deleting items
If you've come this far, great job! Here's a challenge for you:
Add the ability to delete a selected item by id
When we start thinking about how we can add another feature to our project using the @ngrx
pattern, things start to become systematic. We need the following:
- An action
- A reducer case and statement
To start with, update the shopping.actions.ts
with a new DELETE
action:
import { Action } from '@ngrx/store';
import { ShoppingItem } from '../models/shopping-item.model';
export enum ShoppingActionTypes {
ADD_ITEM = '[SHOPPING] Add Item',
DELETE_ITEM = '[SHOPPING] Delete Item'
}
export class AddItemAction implements Action {
readonly type = ShoppingActionTypes.ADD_ITEM
constructor(public payload: ShoppingItem) { }
}
export class DeleteItemAction implements Action {
readonly type = ShoppingActionTypes.DELETE_ITEM
constructor(public payload: string) { }
}
export type ShoppingAction = AddItemAction | DeleteItemAction
We've added an item to the ShoppingActionTypes
and a new DeleteItemAction
. As well as that, we've updated the ShoppingAction
type to be either an AddItemAction
or DeleteItemAction
.
Next up, we need to add a reducer case
which uses filter
to return a new
array that doesn't contain the selected shopping item.
It's important that you don't mutate the state itself and always return a new array. For example, never use Array.splice
to mutate the state itself.
export function ShoppingReducer(state: Array<ShoppingItem> = initialState, action: ShoppingAction) {
switch (action.type) {
case ShoppingActionTypes.ADD_ITEM:
return [...state, action.payload];
case ShoppingActionTypes.DELETE_ITEM:
return state.filter(item => item.id !== action.payload);
default:
return state;
}
}
Now we'll need to update our app.component.ts
and app.component.html
to include the ability to dispatch
a DeleteItemAction
based on a selected id
.
Starting with the HTML file, update your li
to include a click
event that calls a deleteItem
function:
<li *ngFor="let shopping of shoppingItems | async" (click)="deleteItem(shopping.id)">
{{ shopping.name }}
</li>
Finally, update app.component.ts
with the deleteItem
function which uses the specified id
:
import { AddItemAction, DeleteItemAction } from './store/actions/shopping.actions';
export class AppComponent implements OnInit {
// Omitted
deleteItem(id: string) {
this.store.dispatch(new DeleteItemAction(id));
}
}
Here's our completely empty shopping list with no items now that we've deleted everything:

Adding @ngrx/store-devtools
Wouldn't it be awesome if we could visualise our state changes without needing to build out a fancy UI? As well as this, what if something goes wrong?
We can take advantage of the Redux Extension for Chrome/Firefox/X and see each action.type, payload and much more every time its fired like this:

The best part? It's one command away from being in your project! Using the Angular CLI we can add it straight-up with one command:
$ ng add @ngrx/store-devtools
This does the following:
Installing packages for tooling via npm.
+ @ngrx/store-devtools@7.4.0
added 1 package
Installed packages for tooling via npm.
UPDATE src/app/app.module.ts (977 bytes)
UPDATE package.json (1381 bytes)
As well as installing @ngrx/store-devtools
, it also adds the StoreDevtoolsModule
into our app.module.ts
imports:
imports: [
StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production })
],
Now any time we perform an action (such as Add or Delete) we can see the a significant amount of surrounding data:

Summary
In this article we firstly looked at how to add @ngrx/store
to a new project. We then made a small shopping list style application that used the power of @ngrx
to manage the state
of our list.
Finally, we used @ngrx/store-devtools
to see debug information and it gave us the ability to go backward and forward in state-time.
Would you like to see more @ngrx content? Let me know at @paulhalliday_io
You can find the code for this article here: https://github.com/developer-school/introduction-to-ngrx-store
developer.school Newsletter
Join the newsletter to receive the latest updates in your inbox.