In this post, we are going to learn how NgRx Entity build a basic CRUD (select, create, update and delete) to build Angular ToDo app using NgRx entity.

NgRx Entity manages collections of entities. It provides APIs to manipulate and query entity collections. NgRx Entity helps to reduce the boilerplate coding of reducers that manage collections of entities. NgRx Entity provides performant CRUD operations for managing entity collections.

Here on this page step by step, we will provide a complete example to add, update, remove and select the entities from the collections to build Angular ToDo app using NgRx entity.

Step 1. Set up the Development Environment.

You need to set up your development environment before you can do anything.

Install Node.js® and npm if they are not already on your machine.

Verify that you are running at least node 9.11.x and npm 6.x.x by running node -v and npm -v in a terminal/console window.

To install Angular CLI, run:

$ npm install -g angular-cli

This will install the ng command globally on your system.

To verify whether your installation completed successfully, you can run:

$  ng version

Step 2. Create a new App with Routing.

Now that you have Angular CLI installed, you can use it to generate your Todo application:

$ ng new todo-ngrx-entity --routing

This creates an app called todo-app and the –routing flag generates a file called app-routing.module.ts, which is one of the files you need for setting up lazy loading for your feature module.

Step 3. Install NgRx.

We will install the Entity, Store, and Effects using NPM as following:

$ npm install @ngrx/entity --save
$ npm install @ngrx/store --save
$ npm install @ngrx/effects --save

Step 4. Serve the application.

You can now navigate to the new directory:

$ cd todo-ngrx-entity

Then start the Angular CLI development server:

$ ng serve

This will start a local development server that you can navigate to in your browser at http://localhost:4200/

Step 5. Create a feature module with routing.

Using the Angular CLI, generate a new feature module named list.

$ ng generate module components/list --routing

This creates a list folder with two files inside; ListModule and ListRoutingModule.

Step 6. Add a component to the feature module.

Using the Angular CLI, generate a new component named list.

$ ng generate component components/list

This creates a folder inside of list called list with the four files that make up the component.

Step 7. Set up the UI.

Replace the default placeholder markup in app.component.html with a so you can easily navigate to your modules in the browser:


<router-outlet></router-outlet>

Configure the routes.

The structure is as follows:
BUILD AN ANGULAR TODO APP USING NGRX ENTITY
In the AppRoutingModule, you configure the routes to the feature modules, in this case, ListModule. This way, the router knows to go to the feature module.

The feature module then connects the AppRoutingModule to the ListRoutingModule. Those routing modules tell the router where to go to load relevant components.

Let’s start Lazy Loading

  • Routes at the app level

    Using the app router, point the lazy route to the lazy module. We can do this using loadChildren property with the path to the module file, then reference the module itself with a hash #. This tells angular to only load ListModule when the listing URL is activated.

    src/app/app-routing.ts

    
    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    
    const routes: Routes = [
      {
        path: '',
        redirectTo: 'add',
        pathMatch: 'full'
      },  
      {
        path: 'list',
        loadChildren: './components/list/list.module#ListModule'
      },
      {
        path: 'add',
        loadChildren: './components/add/add.module#AddModule'
      },    
      {
        path: 'edit/:id',
        loadChildren: './components/edit/edit.module#EditModule'
      }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule],
      providers: []
    })
    export class AppRoutingModule {
    }
    
  • Inside the feature module

    src/app/components/list/list.module.ts

    
    import { NgModule } from '@angular/core';
    import { CommonModule } from '@angular/common';
    import { ListRoutingModule } from './list-routing.module';
    import { ListComponent } from './list.component';
    
    @NgModule({
      imports: [CommonModule, ListRoutingModule],
      declarations: [ListComponent],
      providers: []
    })
    export class ListModule {
    }
    
  • Configure the feature module’s routes

    src/app/components/list/list-routing.module.ts

    
    import { NgModule } from '@angular/core';
    import { RouterModule, Routes } from '@angular/router';
    import { ListComponent } from './list.component';
    
    const routes: Routes = [
      {
        path: '',
        component: 'ListComponent'
      }
    ];
    
    @NgModule({
      imports: [RouterModule.forRoot(routes)],
      exports: [RouterModule]  
    })
    export class ListRoutingModule {
    }
    
  • Inside the component

    src/app/components/list/list.component.ts

    
    import {Component, OnInit} from '@angular/core';
    import {Router} from '@angular/router';
    import {Observable} from 'rxjs/Rx';
    import {todo} from './../../models/todo.model';
    import {TodoService} from './../../services/todo.service';
    
    @Component({
      selector: 'app-list',
      templateUrl: './list.component.html',
      styleUrls: ['./list.component.scss']
    })
    export class ListComponent implements OnInit {
      public todoSub: Observable<todo>;
      constructor(private router: Router , private todoService: TodoService ) { }
      
      ngOnInit() {
        this.todoSub = this.todoService.list();
      }
      editRecord(id) {
        this.router.navigate(['/edit', id]);
      }
      deleteRecord(id) {
        this.todoService.remove(id);
      }
      public trackByToodFun(index, item) {
        return item.id;
      }
    }
    
  • Inside the actions

    Action file that contains an enum of action types, action class and an exported type union of action classes. The action class has a ‘type’ property. They have an optional ‘payload’ property for sending in data to the reducer.

    Actions are basically the same with NgRx Entity, but you will need to be careful to make sure the payload data is appropriate for the adapter method.

    src/app/actions/todo.actions.ts

    
    import {Injectable} from '@angular/core';
    import {Action} from '@ngrx/store';
    import {todo} from './../models/todo.model';
    
    export const ADD_TODO = '[TODO] Add';
    export const REMOVE_TODO = '[TODO] Remove';
    export const UPDATE_TODO = '[TODO] Update';
    
    export class AddTodo implements Action {
      readonly type = ADD_TODO;
      constructor(public payload: todo) { }
    }
      
    export class UpdateTodo implements Action {
      readonly type = UPDATE_TODO;
      constructor(public id: number, public changes) { }
    }
      
    export class RemoveTodo implements Action {
      readonly type = REMOVE_TODO;
      constructor(public id: number) { }
    }
    
    export type Actions = AddTodo | UpdateTodo | RemoveTodo;
    
  • Inside the reducers

    Reducer contains a state interface, an initial state object for the reducer, and a reducer function. Reducers are pure functions that are the only ones that can change state. They aren’t really changing state but making a copy of existing state and changing one or more properties on the new state.

    NgRx Entity provides EntityState interface that is a predefined generic interface for a given entity collection.

    NgRx Entity has EntityAdapter interface that provides many collection methods for managing the entity state.

    createEntityAdapter method instantiates generic EntityAdapter for a single entity state collection.

    EntityAdapter has getInitialState method to get initial state of the entity, getSelectors method to get entity selectors. EntityAdapter extends EntityStateAdapter and inherits its methods to add, update and remove entities.

    The entity adapter will allow us to take a collection and manage it with a set of Adapter Collection Methods are addOne, updateOne and removeOne, etc.

    src/app/reducers/todo.reducer.ts

    
    import {Action} from '@ngrx/store';
    import {todo} from './../models/todo.model';
    import *as TodoActions from './../actions/todo.actions';
    import {EntityState, EntityAdapter, createEntityAdapter} from '@ngrx/entity';
    import {createSelector, createFeatureSelector} from '@ngrx/store';
    
    export interface TodoState extends EntityState<todo> { }
    
    export const adapter : EntityAdapter<todo> = createEntityAdapter<todo>({ });
    
    const initialState : todo = <todo>{ };
    
    export const initialTodoState : TodoState = adapter.getInitialState( );
    
    export function todoReducers(state = initialTodoState, action: TodoActions.Actions) {
        switch(action.type) {
            case TodoActions.ADD_TODO:
                return adapter.addOne(action.payload, state);
            case TodoActions.UPDATE_TODO:
                if(state.entities[action.id] === undefined) {
                    return state;
                }            
                return adapter.updateOne({
                    id: action.id,
                    changes: action.changes,
                }, state);
            case TodoActions.REMOVE_TODO:
                return adapter.removeOne(action.id, state);
            default:
                return state;
        }
    }
    
    export const getTodoState = createFeatureSelector<TodoState>('todos');      
    
    export const {selectAll, selectEntities, selectIds, selectTotal} = adapter.getSelectors(getTodoState); 
    
  • Inside the services

    The operation triggered by dispatching an action is going to be a pure function called, within the redux architecture, reducers. These reducers receive action and the state, depending on the action dispatched, they perform an operation and return a new state object.

    When using the createSelector and createFeatureSelector functions @ngrx/store keeps track of the latest arguments in which your selector function was invoked. Due to selectors are pure functions, the last result can be returned when the arguments match without re-invoking your selector function. This can provide performance benefits.

    The createSelector can be used to select some data from the state based on several slices of the same state.

    NgRx. selectors also accept an extra props argument. Which means you can now define a selector like the following example:

    src/app/services/todo.service.ts

    
    import {Injectable} from '@angular/core';
    import {Observable} from 'rxjs/Rx';
    import *as TodoActions from './../actions/todo.actions';
    import *as fromTodoReducer from './../reducers/todo.reducer';
    import {createSelector, createFeatureSelector} from '@ngrx/store';
    import {Dictionary} from '@ngrx/entity';
    import {Store, select} from '@ngrx/store';
    import {AppState} from './../app.state';
    import {todo} from './../models/todo.model';
    
    @Injectable()
    export class TodoService {
        private allTodos;
        private todoById;
        
        constructor(private store: Store<AppState>) {
            this.allTodos = createSelector(fromTodoReducer.selectAll, (entities) => {
                return entities;
            });
            
            this.todoById = createSelector(fromTodoReducer.selectEntities, 
                (entities: Dictionary<todo>, props:{id: number}) => {    
                return entities[props.id];
            });
        }
        
        public add(data: todo) {
            data.id = new Date().getTime(); 
            this.store.dispatch(new TodoActions.AddTodo(data));
        }
        
        public list(){
            return this.store.pipe(select(this.allTodos));     
        }
        
        public remove(id: number) { 
            this.store.dispatch(new TodoActions.RemoveTodo(id));
        }
        
        public getDetail(id: number) { 
            return this.store.pipe(select(this.todoById, {id: id}));
        }
        
        public edit(id: number, changes: todo) { 
            this.store.dispatch(new TodoActions.UpdateTodo(id, changes));
        }                  
    }     
    

You can download the source code for the Angular ToDo App using NgRx Entity from github repository angular-todo-app-ngrx-entity. So these are all steps you can follow to build Angular ToDo app using NgRx entity. Surely it helps you!!

2 responses to “Build an Angular ToDo App using NgRx Entity”

  1. Steve says:

    PWA is the future of Web development. Thank you so much for your information about PWA it is really helpful for me. please keep us update

    • Sanjay Patel says:

      You’re Welcome, Steve. Right said PWA is the future of web development. Keep visiting our blog for the latest information.

Leave a Reply

Your email address will not be published. Required fields are marked *