NGRX Complete Guide with Angular: Learn by Doing Simple CRUD Operation

Saunak Surani
Widle Studio LLP
Published in
7 min readAug 1, 2023

Angular is a powerful framework for building scalable and maintainable web applications. As applications grow in complexity, managing state becomes a crucial aspect of development. NGRX is a state management library for Angular applications that helps manage complex application state and improves application performance.

In this article, we will provide a complete guide to using NGRX with Angular. We will focus on performing simple CRUD (Create, Read, Update, Delete) operations using NGRX. By the end of this guide, you will have a solid understanding of how to implement NGRX in an Angular application and handle state in a predictable and efficient manner.

Table of Contents

  1. What is NGRX and Why Use It?
  2. Setting Up the Angular Project
  3. Creating the Store
  4. Defining Actions
  5. Implementing Reducers
  6. Creating Selectors
  7. Dispatching Actions
  8. Displaying Data in Components
  9. Updating Data with Actions
  10. Deleting Data with Actions
  11. Conclusion

1. What is NGRX and Why Use It?

NGRX is a state management library inspired by Redux, a popular state management tool in the React ecosystem. It follows the principles of a unidirectional data flow, where the state is immutable, and changes are made through pure functions. NGRX helps centralize the application state, making it easier to manage and debug.

The main benefits of using NGRX include:

  • Predictable State Management: NGRX follows a strict pattern, making it easy to understand and maintain the state of the application.
  • Scalability: As your application grows, NGRX provides a scalable architecture that keeps the state management organized and efficient.
  • Time Travel Debugging: NGRX allows you to trace actions and changes to the state, making debugging and testing much more comfortable.
  • Sharing State Between Components: NGRX enables sharing state across components, even if they are not directly related.
  • Improving Performance: NGRX optimizes performance by reducing the number of redundant state changes.

Now that we understand the benefits of using NGRX, let's set up an Angular project and get started with NGRX.

2. Setting Up the Angular Project

Before we begin, make sure you have Node.js and Angular CLI installed on your system. If not, you can install them by running the following commands:

npm install -g @angular/cli

To create a new Angular project, use the Angular CLI to generate the boilerplate code:

ng new ngrx-crud-example

Change into the newly created project directory:

cd ngrx-crud-example

We now have our Angular project ready, and we can proceed to install the necessary dependencies for NGRX.

3. Creating the Store

In NGRX, the store is where the application state is managed. To create the store, we need to install the @ngrx/store package:

npm install @ngrx/store --save

Next, we need to define the state of our application. For this example, let's assume we are building a simple task management application with tasks having an id, title, and description.

Create a new file named task.model.ts in the src/app directory and define the Task interface:

// src/app/task.model.ts

export interface Task {
id: number;
title: string;
description: string;
}

Now, create another file named task.reducer.ts in the src/app directory. This file will contain the initial state of our application and the reducer function that handles state changes:

// src/app/task.reducer.ts

import { Task } from './task.model';

export interface AppState {
tasks: Task[];
}

export const initialState: AppState = {
tasks: []
};

export function taskReducer(state = initialState, action): AppState {
switch (action.type) {
default:
return state;
}
}

In this code snippet, we defined the initial state of our application with an empty array of tasks. The taskReducer function is a pure function that receives the current state and an action as arguments and returns a new state based on the action type.

4. Defining Actions

Actions in NGRX are used to describe state changes in the application. They are plain JavaScript objects with a type property that describes the action being performed. We will define three types of actions for our task management application: AddTask, UpdateTask, and DeleteTask.

Create a new file named task.actions.ts in the src/app directory and define the actions:

// src/app/task.actions.ts

import { createAction, props } from '@ngrx/store';
import { Task } from './task.model';

export const addTask = createAction('[Task] Add Task', props<{ task: Task }>());
export const updateTask = createAction('[Task] Update Task', props<{ task: Task }>());
export const deleteTask = createAction('[Task] Delete Task', props<{ id: number }>());

Here, we used the createAction function from @ngrx/store to define our actions. The first argument of createAction is the action type, enclosed in square brackets to indicate it belongs to the Task feature. The second argument, props, is used to define the payload of the action. In our case, addTask and updateTask actions expect a task object, while deleteTask expects an id as the payload.

5. Implementing Reducers

Now that we have defined our actions, we need to implement the reducers that handle these actions and update the state accordingly.

Open the task.reducer.ts file and import the actions we defined earlier:

// src/app/task.reducer.ts

import { Task } from './task.model';
import { addTask, updateTask, deleteTask } from './task.actions';

export interface AppState {
tasks: Task[];
}

export const initialState: AppState = {
tasks: []
};

export function taskReducer(state = initialState, action): AppState {
switch (action.type) {
case addTask.type:
return { ...state, tasks

: [...state.tasks, action.task] };
case updateTask.type:
return {
...state,
tasks: state.tasks.map(task => (task.id === action.task.id ? action.task : task))
};
case deleteTask.type:
return { ...state, tasks: state.tasks.filter(task => task.id !== action.id) };
default:
return state;
}
}

In this updated code, we imported the actions and added cases to our taskReducer function to handle each action type. For addTask, we create a new state by adding the new task to the existing tasks array. For updateTask, we map through the tasks and update the one with a matching id. For deleteTask, we filter out the task with the specified id.

6. Creating Selectors

Selectors in NGRX are used to access specific pieces of state from the store. They allow us to compute derived data based on the state and avoid accessing the state directly.

Create a new file named task.selectors.ts in the src/app directory and define the selectors:

// src/app/task.selectors.ts

import { createSelector } from '@ngrx/store';
import { AppState } from './task.reducer';

export const selectTasks = (state: AppState) => state.tasks;

export const selectTaskById = (id: number) =>
createSelector(selectTasks, tasks => tasks.find(task => task.id === id));

In this code snippet, we used the createSelector function from @ngrx/store to define our selectors. The selectTasks selector retrieves the tasks array from the state, while selectTaskById takes an id argument and returns the task with the corresponding id.

7. Dispatching Actions

Now that we have set up the store, defined actions, and implemented reducers and selectors, let's move on to dispatching actions from our components.

Open the app.component.ts file in the src/app directory. In this file, we will dispatch actions to add, update, and delete tasks.

// src/app/app.component.ts

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from './task.reducer';
import { addTask, updateTask, deleteTask } from './task.actions';
import { Task } from './task.model';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
tasks: Task[] = [];

constructor(private store: Store<AppState>) {
this.store.select('tasks').subscribe(tasks => this.tasks = tasks);
}

addNewTask() {
const newTask: Task = {
id: this.tasks.length + 1,
title: 'New Task',
description: 'This is a new task.'
};
this.store.dispatch(addTask({ task: newTask }));
}

updateTask(task: Task) {
const updatedTask: Task = { ...task, title: 'Updated Task', description: 'This task has been updated.' };
this.store.dispatch(updateTask({ task: updatedTask }));
}

deleteTask(id: number) {
this.store.dispatch(deleteTask({ id }));
}
}

In this code snippet, we imported the necessary modules and dependencies, including Store from @ngrx/store, our actions, and the Task interface. We also defined methods to add, update, and delete tasks and dispatched the corresponding actions using the store.dispatch() method.

8. Displaying Data in Components

Now that we can dispatch actions, let's create the user interface to display our tasks and allow users to add, update, and delete them.

Open the app.component.html file in the src/app directory and update it as follows:

<!-- src/app/app.component.html -->

<div *ngFor="let task of tasks" class="task">
<h3>{{ task.title }}</h3>
<p>{{ task.description }}</p>
<button (click)="updateTask(task)">Update</button>
<button (click)="deleteTask(task.id)">Delete</button>
</div>
<button (click)="addNewTask()">Add New Task</button>

In this HTML template, we use Angular's *ngFor directive to loop through the tasks and display their titles and descriptions. We also add buttons to update and delete each task and a button to add a new task.

9. Updating Data with Actions

When the "Update" button is clicked for a task, the updateTask() method is called, which dispatches the updateTask action with the updated task object.

10. Deleting Data with Actions

When the "Delete" button is clicked for a task, the deleteTask() method is called, which dispatches the deleteTask action with the task's id.

11. Conclusion

In this article, we explored how to use NGRX in an Angular application to manage state and handle simple CRUD operations. We set up the store, defined actions, implemented reducers, and created selectors to access specific pieces of state. We also learned how to dispatch actions from components to modify the state.

NGRX provides a robust and scalable solution for managing application state, and it is especially useful for large and complex applications. By leveraging NGRX, you can ensure that your Angular applications are more maintainable, predictable, and efficient.

With this knowledge, you can now start integrating NGRX into your own Angular projects and build more powerful and well-structured applications.

--

--

Saunak Surani
Widle Studio LLP

Passionate about technology, design, startups, and personal development. Bringing ideas to life at https://widle.studio