Using ngrx in ionic 4 is just as simple as using ngrx in angular application. Don’t worry if you are not familiar with it. In this tutorial, I’ll explain to you how to use ngrx in ionic 4 application.
We are going to create a very basic Todo Application to demonstrate the structure of ngrx in ionic 4 application.
At the end of this tutorial, we’ll be having an ionic 4 application which is having one page called todo-list which shows the list of todos using ngrx-store.
How to use ngrx/store with Ionic 4:
Table of Contents
STEP 1: CREATE IONIC 4 PROJECT
First of all, we need a basic ionic 4 application.
If you don’t know how to create ionic 4 application, I would recommend reading my previous article: Ionic 4 For Beginners
STEP 2: GENERATE PAGES
We need one page to show the todo list, so we are going to create one page for it.
you can create a page in ionic by running the following command:
ionic generate page pages/todo-list
If you look into the above command, we’ve written pages/todo, that means we are telling ionic to generate todo-list page inside pages folder. and it generates the page with lazy loading feature.
It will create a todo-list folder in the project as follows:
If you’ll notice the todo-list folder, it comes up with todo-list.module.ts. This is going to support lazy loading feature.
When you’ll generate the page in ionic 4, all the generated page will be having its own module and its lazy-loading router configuration will be updated automatically when you’ll generate the page,
so app-routing.module.ts page will be having the following line in routes array:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
loadChildren: './pages/home/home.module#HomePageModule'
},
{
path: 'list',
loadChildren: './pages/list/list.module#ListPageModule'
},
{ path: 'todo', loadChildren: './pages/todo/todo.module#TodoPageModule' },
{ path: 'todo-list', loadChildren: './pages/todo-list/todo-list.module#TodoListPageModule' }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
which tell that when /todo-list is requested then load TodoListPageModule as lazy loaded module
STEP 3: INSTALL NGRX
Once you have a basic setup for ionic application, then install ngrx packages.
npm install --save @ngrx/store @ngrx/effects
NOTE: Here I am using in-memory web api, because it emulates CRUD operations over REST API
You can learn more about in memory web api here. so the following package is not compulsory to install.
npm install --save angular-in-memory-web-api
STEP 4: GENERATE SERVICE
The good practices to create services is inside its own module called CoreModule
.
Here in CoreModule, we can create services, guards, interceptors, models and etc.
We’ll create CoreModule
as following structure
You can see that I’ve created services folder, you can create other folder based on your need i.e guards, interceptors etc.
Now create todo.service.ts
in the app/core/services
directory and copy the following code into it.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { Todo } from '../../state/todo/todo.model';
@Injectable()
export class TodoService {
private todoUrl = 'api/todos';
constructor(private httpClient: HttpClient) { }
getTodos(): Observable<Array<Todo>> {
return this.httpClient.get<Todo[]>(this.todoUrl);
}
getTodo(id: number): Observable<Todo> {
return this.getTodos().pipe(
map(todos => todos.find(todo => todo.id === id))
);
}
save(todo: Todo): Observable<Todo> {
if (todo.id) {
return this.put(todo);
}
return this.post(todo);
}
delete(todo: Todo): Observable<Todo> {
const url = `${this.todoUrl}/${todo.id}`;
return this.httpClient
.delete<void>(url)
.pipe(switchMap(() => of(todo)));
}
private post(todo: Todo): Observable<Todo> {
return this.httpClient.post<Todo>(this.todoUrl, todo);
}
private put(todo: Todo): Observable<Todo> {
const url = `${this.todoUrl}/${todo.id}`;
return this.httpClient
.put(url, todo)
.pipe(switchMap(() => of(todo)));
}
}
This service contains methods for getTodos
(All todos), getTodo
(todo By Id) and it gets data from in-memory web API because we are using in-memory web api in our example, but you can set up to get real data from live API endpoint.
STEP 5: In Memory DataService
We’ll create inmemory dataservicce, it’s not compulsory to create but since we are not using real api endpoint, we’ll use it to fake the api endpoint. Generate new file in-memory-data-service.ts and copy the following code into it.
import { Todo } from '../state/todo/todo.model';
export class InMemoryDataService {
createDb() {
const todos: Array<Todo> = [
{ id: 1, name: "Shopping" },
{ id: 2, name: "Meeting" }
];
return { todos };
}
}
STEP 6: Core Module
Now create a new file called core.module.ts.
Here we’ll import InMemoryWebApiModule and will define all services created in this module, In our case, we have created on one service called todo.service.ts
Copy the following code into core.module.ts
import { HttpClientModule } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import {
ModuleWithProviders,
NgModule,
Optional,
SkipSelf
} from '@angular/core';
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from '../core/in-memory-data.service';
import { TodoService } from './services/todo.service';
@NgModule({
imports: [
CommonModule,
HttpClientModule,
InMemoryWebApiModule.forRoot(InMemoryDataService, { delay: 600 })
],
providers: [
TodoService]
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule
};
}
constructor(
@Optional()
@SkipSelf()
parentModule: CoreModule
) {
if (parentModule) {
throw new Error(
'CoreModule is already loaded. Import it in the AppModule only'
);
}
}
}
STEP 7: SETUP NGRX
First, create a new folder called state in app folder, this folder is responsible for containing the state for all modules that the app contains.
To easily distinguish the state of all modules, we’ll create a separate folder for each module inside state folder.
In our case, we’ll create todo folder inside state folder so it will look like the following structure.

Now we’ll create few files of ngrx setup in todo folder, so at the end, the folder will look like the following:
Ngrx Actions
At first, we are creating the string const variables as following:
export const GET_ALL_TODOS = '[TODO] Get All Todos';
We are exporting this const, because we will need these in the reducer and effects later.
We will use this const string to define the type of the actions.
Create todo.actions.ts
file in app/state/todo directory, this file is responsible for creating actions which are related to todo module.
Here we’ve created few actions to get todo list from API. and there is a success and fail action which will be called from effects based on the response of API.
Now copy the following code in your todo.action.ts
import { Action } from "@ngrx/store";
import { Todo } from "./todo.model";
export const GET_ALL_TODOS = '[TODO] Get All Todos';
export const GET_ALL_TODOS_SUCCESS = '[TODO] Get All Todos Success';
export const GET_ALL_TODOS_FAIL = '[TODO] Get All Todos Fail';
export const GET_TODO = '[TODO] Get Todo';
export const GET_TODO_SUCCESS = '[TODO] Get Todo Success';
export const GET_TODO_FAIL = '[TODO] Get Todo Fail';
//Get Todo List
export class GetAllTodos implements Action {
readonly type = GET_ALL_TODOS;
}
export class GetAllTodosSuccess implements Action {
readonly type = GET_ALL_TODOS_SUCCESS;
constructor(public payload: Todo[]) { }
}
export class GetAllTodosFail implements Action {
readonly type = GET_ALL_TODOS_FAIL;
constructor(public payload: any) { }
}
//Get todo by id
export class GetTodo implements Action {
readonly type = GET_TODO;
constructor(public payload: number) { }
}
export class GetTodoSuccess implements Action {
readonly type = GET_TODO_SUCCESS;
constructor(public payload: Todo) { }
}
export class GetTodoFail implements Action {
readonly type = GET_TODO_FAIL;
constructor(public payload: any) { }
}
export type TodoActions =
GetAllTodos
| GetAllTodosSuccess
| GetAllTodosFail
| GetTodo
| GetTodoSuccess
| GetTodoFail;
Ngrx Reducers
Now copy the following code in your todo.reducer.ts
import * as fromTodo from "./todo.actions";
import { Todo } from './todo.model';
export interface State {
todos: Todo[],
loading: boolean;
error: string;
}
export const initialState: State = {
todos: [],
loading: false,
error: ''
};
export function reducer(state = initialState, action: fromTodo.TodoActions): State {
switch (action.type) {
case fromTodo.GET_ALL_TODOS: {
return {
...state,
loading: true
};
}
case fromTodo.GET_ALL_TODOS_SUCCESS: {
return {
...state,
loading: false,
todos:action.payload
};
}
case fromTodo.GET_ALL_TODOS_FAIL: {
return {
...state,
loading: false,
error: 'error loading todos'
};
}
case fromTodo.GET_TODO: {
return {
...state,
loading: true
};
}
case fromTodo.GET_TODO_SUCCESS: {
return {
...state,
loading: false
};
}
case fromTodo.GET_TODO_FAIL: {
return {
...state,
loading: false,
error: 'error loading todo'
};
}
default: {
return state;
}
}
}
export const getAllTodos = (state: State) => state.todos;
export const getLoading = (state: State) => state.loading;
export const getError = (state: State) => state.error;
Ngrx Effects
Effect will communicate with service. It will call service and based on the response it will dispatch the success or fail action.
If you’ll look into getAllTodos$
effect, you can see that it is calling todoservice method getTodos
and based on its response it will dispatch either GetAllTodoSuccess
or GetAllTodosFail
.
Copy the following code in your todo.effects.ts
import { Injectable } from '@angular/core';
import { Actions, Effect } from '@ngrx/effects';
import { Action } from '@ngrx/store';
import { Observable, of } from 'rxjs';
import { map, switchMap, catchError } from 'rxjs/operators';
import { Todo } from './todo.model';
import * as TodoActions from './todo.actions';
import { TodoService } from '../../core/services/todo.service';
@Injectable()
export class TodosEffects {
@Effect()
getAllTodos$: Observable<Action> = this.actions$
.ofType(TodoActions.GET_ALL_TODOS)
.pipe(
switchMap(() => this.todoService.getTodos().
pipe(
map((todos: Todo[]) => new TodoActions.GetAllTodosSuccess(todos)),
catchError(err => of(new TodoActions.GetAllTodosFail(err)))
))
);
@Effect()
getTodoById: Observable<Action> = this.actions$
.ofType(TodoActions.GET_TODO)
.pipe(
map((action: TodoActions.GetTodo) => action.payload),
switchMap((id) => this.todoService.getTodo(id).
pipe(
map((todo: Todo) => new TodoActions.GetTodoSuccess(todo)),
catchError(err => of(new TodoActions.GetTodoFail(err)))
))
);
constructor(private actions$: Actions, private todoService: TodoService) { }
}
Ngrx Selector
The task of a selector is to select the property from the ngrx state, and we can call this selector from our component to fetch the data of ngrx state.
For example:
export const getAllTodos = createSelector(
getTodosState,
fromTodos.getAllTodos
);
Here you can see that we are telling ngrx that select getAllTodos from getTodoState.
So It will call getAllTodos
selector which is defined in reducer.
Now copy the following code in your index.ts
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromTodos from './todo.reducer';
import { State as TodoState } from './todo.reducer';
export const getTodosState = createFeatureSelector<TodoState>('todo');
export const getAllTodos = createSelector(
getTodosState,
fromTodos.getAllTodos
);
export const getLoading = createSelector(
getTodosState,
fromTodos.getLoading
);
export const getError = createSelector(
getTodosState,
fromTodos.getError
);
Todo Model
Generate todo model as following in your todo.model.ts
export interface Todo {
id: number;
name: string;
}
Root State
Generate AppState interface to represent the entire state of the application. You can add other modules same as todo module.
Copy the following code in app.reducer.ts in app/state directory.
import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import * as fromTodo from './todo/todo.reducer';
export interface AppState {
todo: fromTodo.State;
}
export const appReducer: ActionReducerMap<AppState> = {
todo: fromTodo.reducer
};
State Module
Now we’ll create the module for the ngrx state called StateModule, where we’ll define our reducers and effects.
Create a new file called state.module.ts in app/state directory and copy the following code into it.
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { appReducer } from './app.reducer';
import { TodosEffects } from './todo/todo.effects';
@NgModule({
imports: [
CommonModule,
StoreModule.forRoot(appReducer),
EffectsModule.forRoot([TodosEffects])
],
declarations: []
})
export class StateModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: StateModule
};
}
constructor(@Optional() @SkipSelf() parentModule: StateModule) {
if (parentModule) {
throw new Error(
'StateModule is already loaded. Import it in the AppModule only');
}
}
}
App Module
As of now, we’ve created StateModule and CoreModule, we need to import it in the main module called AppModule
Now update your AppModule as following:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { StateModule } from './state/state.module';
import { CoreModule } from './core/core.module';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';
import { ComponentsModule } from './components/components.module';
@NgModule({
declarations: [AppComponent],
entryComponents: [],
imports: [
BrowserModule,
IonicModule.forRoot(),
AppRoutingModule,
ComponentsModule,
StateModule.forRoot(),
CoreModule.forRoot()
],
providers: [
StatusBar,
SplashScreen,
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
],
bootstrap: [AppComponent]
})
export class AppModule {}
STEP 8: TODO LIST PAGE
Now, ngrx setup is completed and it’s time to update our ionic page to fetch a list of todos using ngrx.
Update todo-list.page.ts as following:
import { Component, OnInit } from '@angular/core';
import { Observable } from "rxjs";
import { Todo } from "../../state/todo/todo.model";
import { Store } from "@ngrx/store";
import { AppState } from '../../state/app.reducer';
import * as fromStore from '../../state/app.reducer';
import * as fromTodo from '../../state/todo/todo.actions';
import { getAllTodos, getLoading, getError } from '../../state/todo';
@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.page.html',
styleUrls: ['./todo-list.page.scss'],
})
export class TodoListPage implements OnInit {
todos$: Observable<Array<Todo>>
loading$: Observable<boolean>;
error$: Observable<string>;
constructor(private store: Store<fromStore.AppState>) {
this.todos$ = this.store.select(getAllTodos);
this.loading$ = this.store.select(getLoading);
this.error$ = this.store.select(getError);
}
ngOnInit() {
this.store.dispatch(new fromTodo.GetAllTodos());
}
}
In the above code you can see that we are fetching data from selectors:
this.todos$ = this.store.select(getAllTodos);
and update todo-list.page.html as following:
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>Todo List</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding="">
<ion-list>
<ion-item *ngfor="let todo of (todos$|async)">
{{todo.name}}
</ion-item>
</ion-list>
</ion-content>
Our todo app is ready now
.
Run the application using ionic serve. It will run the ionic app and open /todo-list url and it will display the following result.

Here you can see that it has displayed two results Shopping and Meeting from in-memory-web-service.
Download full tutorial of todo app from github
Thanks for reading How to use ngrx/store with Ionic 4 article.
Also, read my other articles :
- How to Convert HTML to PDF in Angular Application?
- Angular Multiple File Upload Example
- Angular File Upload Example
- Email Validation in Angular with Example
- Angular Material Card
- Angular Material List Example