Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Building SPA with Angular 4 and Redux

4.36/5 (5 votes)
28 Nov 2017CPOL19 min read 18K  
How to build SPA using Angular 4 and Redux

Image 1

In the current application development era, Single Page Application(SPA) is a great feature to develop the modern web based application. In these SPA applications, we tend to move the data level dependency from the server side to browser level as much as possible to improve the application performance. So as much as application level functionality moves to the browser level, the amount of the data and way to manage this data is also increased. Modern SPA application frameworks like Angular, React use the component based architecture that divides our application into small-small sections. Each component contains its own HTML page, stylesheet and its own state(data). This approach is very efficient because it divides our application into small sections that we can manage very easily and also allows us to reuse these components into our application.

Component based architecture is very feasible if the size of our application is not large. But if the size of our application is increased, then it becomes very difficult to maintain the state of each component. Let’s focus on some downside of this component based architecture.

Data Passing Approach using Properties

In Angular, we use the input properties to pass the data in the component tree. Suppose we have a component in the component tree and we want to pass some data to its grand children component. For this, first we have to pass this data to its children component that is parent component of the "grand children" component, then further the "parent" component passes this data to its own children component (grand children) using the input property.

Image 2

It is clear that in the above process, the parent component doesn’t use this data but if we want to pass the data to the grand children component, then we must use this intermediate component. In component based architecture, we have many intermediate components passing the data but don’t use this data so this approach is not efficient.

Inflexible Components

Using these properties to pass data between components is inflexible and we can’t reuse these components because if we want to use these components, then we have to pass the value for these components. Also, it makes it difficult to understand the assignment of a component that contains several input properties.

Maintain Data Synchronously

If state multiple components using the same states and state is changed within one component, it is necessary to notify all other components to update their state. Also, that is a very difficult and expensive task.

State of Application

If each component contains its own state, then it becomes very difficult to take a screenshot of the state of the whole application because we divided the state at the component level.

Redundant Data

If we have multiple components and each component contains its own copy of data (state), then there is a huge chance that some duplicate (same data) data is present in multiple components, so this redundant data is not good as a point of view of the modern application.

All the above shortcomings of the application can lead an application to inconsistent state application and make it difficult to manage the state of the whole application. To overcome all these shortcomings, we need a new way to manage the state of our application. Now Redux comes into the picture.

Redux is a predictable state container for JavaScript apps. It is an open-source JavaScript library developed to maintain the state of the application. Redux works on the centralized data mechanism that means, instead of storing the state at component level, we store the state in a centralized location and all the components can access this store. Sometimes, data architecture becomes a complex topic for the application but the advantage of the Redux is that it makes the data architecture very easy.

Redux Architecture

Image 3

Redux works on following these key points:

  1. Maintains the whole application data into a single state and we can access the state from the store of application.
  2. This store doesn’t mutate directly.
  3. Actions are used to trigger the change into state of the application.
  4. Action called the dispatcher.
  5. Dispatcher called the Reducer that takes the previous state and new data as input and generates a new state and now all the components use this new state.

Building Block of the Redux

Before starting the development with Redux, we must have basic knowledge of the building blocks or working block of the Redux architecture. Let’s understand the building blocks of the Redux.

Store

Store is a simple JavaScript object that contains the state of the whole application. We can update the state of application using the dispatch(action, payload) method. Dispatcher every time updates the previous state of the application and generates a new state.

Actions

Actions are payloads of information that are sent from our application to our store. Actions are the source to send the data to the store using the Store.dispatch() method. We can compare the action to events that indicate to the reducer what to perform with this new data and previous state.

Reducers

Actions tell the Reducer what to perform, but don’t say how to perform the task. Reducer is the function that specifies how the state will change. Reducer is the pure function that means we get the same output with the same input in all conditions and circumstances. Reducer takes the action name and previous state. Reducer always returns a new state that contains the modification included.

API

APIs are the way to interact with the external environment. APIs are used to get the data from server side and also to update the data to server side.

I think the above introduction is enough to start working with Redux and we will cover the remaining topic later in this article. If you want to read more about Redux, then you can go through the official website of Redux.

Create Bug Todo List Application with Angular and Redux

Now we start to develop the bug todo list application using Angular 4 and use Redux to maintain the state of our application. Actually the "bug todo" app will be a demo of the bug management system, where we can add the news bugs, check the status of the bugs and also add functionality to change the status of the bugs.

To create a new Angular 4 application, we will use the Angular CLI. Open a new command line terminal and run the ng new Bug-TodoApp. This command creates a new Angular 4 template for the project.

Now open this project into any Code Editor. Here, I am using "Visual Studio Code." Move to the root directory of the project and run the ng serve command. This command builds and runs our project at 4200 port with live reloading features. If you're using "Visual Studio Code", then you can also use the "integrated Terminal" from View menu to run the commands.

Image 4

Now open the given URL into any browser and you will find the below screen:

Image 5

Install Redux Packages

After successfully creating the Angular template, now we need to install the redux packages for our application. So run the npm install redux @angular-redux/store --save command to install the required packages. Now go to the package.json file. You will find both packages have been added to our project. After installing the Redux package, now we can create building block like Store, Action and Reducer for our application.

Create IBugModel Model

Now we create a model for our bugtodo project. For this, we create an Interface with some property. First of all, create a model folder into the src directory and add the BugModel.ts file into this directory. After adding the TypeScript file, now paste the following code into this file:

JavaScript
export interface IBugModel{
    bugId:number;
    description:string;
    project:string;
    priority:string;
    status:string;
}

Image 6

Create Store

Add a folder into Src directory and name this folder store. Now, in this folder, add a TypeScript file and name this file BugStore.ts. After creating this file, now paste the following code into this file.

JavaScript
import { IBugModel } from "../model/BugModel";

export interface IBugState {
    bugList:IBugModel[],
    totalBug:number,
    unassigned:number,
    assignedBug:number,
    pendingBug:number,
    completed:number,
    reopenBug:number,
    bugId:number    
}

export const INITIAL_STATE:IBugState={
    bugList:[],
    totalBug:0,
    unassigned:0,
    assignedBug:0,
    pendingBug:0,
    completed:0,
    reopenBug:0,
    bugId:0
  
}

In the above lines of code, we create an IBugStore interface and this interface will work as the store of our application state.

The bugList property will contain the list of all bugs and unassigned, assigned, pending, completed and reopenBug properties indicate the numbers of unassigned, assigned, pending, completed and reopen bugs.

We also create an INITIAL_STATE constant of type IBugState. This variable indicates the initial state of the application whenever our application runs the first time. If we are creating a state for the application, then we also need to define the initial state of the application that will load when the application runs for the first time.

Create Action

The Redux document describes what Action is used to indicate to the reducer what type of task will be performed with the payload. Now we create a file that will define all possible Action types. So, create an action folder into the src directory, after creating the folder. Now create a BugAction.ts file into this folder and paste the following code into this file:

JavaScript
export class bugTodoAction{  
    public static   Add_NewBug='Add';
    public static   Assign_Bug='Assign';
    public static   Reopen_Bug='Reopen';
    public static   Close_Bug='Close';
    public static   Pending_Bug='Pending';
    public static   Remove_All='Remove_All';
    public static   Open_Model='Open_Model';
    }

Create Reducer

Reducer is the heart of the Redux pattern, each reducer function takes two parameters. The first parameter contains the previous state of the application and the second parameter contains the action type and new data payload for the state change. Now create a reducer folder into src directory and create a Reducer.ts file and paste the following code into this file:

JavaScript
import {bugTodoAction} from "../action/BugAction";

export function rootReducre(state,action){
    switch(action.type){
        
        case bugTodoAction.Add_NewBug:
                action.todo.bugId=state.bugList.length+1;
                action.todo.status="Unassigned";

                return Object.assign({},state,{
                    bugList:state.bugList.concat(Object.assign({},action.todo)),
                    totalBug:state.bugList.length+1,
                    unassigned:state.unassigned+1,
                    assignedBug:state.assignedBug,
                    pendingBug:state.pendingBug,
                    completed:state.completed,
                    reopenBug:state.completed
                });

        case bugTodoAction.Assign_Bug:
            var bug=state.bugList.find(x=>x.bugId==action.bugNo);
            var currentStatus=bug.status;
            var index =state.bugList.indexOf(bug);
            
        
            if(bug.status=="Unassigned"){
                state.unassigned--;               
            }
            else if(bug.status=="Reopen"){
                state.reopenBug--;               
            }
            else if(bug.status=="Close"){
                state.completed--;               
            }
            else if(bug.status=="Pending"){
                state.pendingBug--;               
            }
            if(bug.status!="Assign"){
                state.assignedBug++;
            }
            
            bug.status=bugTodoAction.Assign_Bug;

            return Object.assign({},state,{
                bugList:[
                    ...state.bugList.slice(0,index),
                    Object.assign({},bug),
                    ...state.bugList.slice(index+1)
                ]
            });       
            
        case bugTodoAction.Close_Bug:
        var bug=state.bugList.find(x=>x.bugId==action.bugNo);
        var currentStatus=bug.status;
        var index =state.bugList.indexOf(bug);
        
        if(bug.status=='Assign'){
            state.assignedBug--;               
        }
        else if(bug.status=="Unassigned"){
            state.unassigned--;               
        }
        else if(bug.status=="Reopen"){
            state.reopenBug--;               
        }
        else if(bug.status=="Pending"){
            state.pendingBug--;               
        }
        
        if(bug.status!="Close"){
            state.completed++;
        }
        
        bug.status=bugTodoAction.Close_Bug;

        return Object.assign({},state,{
            bugList:[
                ...state.bugList.slice(0,index),
                Object.assign({},bug),
                ...state.bugList.slice(index+1)
            ],
            lastUpdate:new Date()
        });
        
        case bugTodoAction.Pending_Bug:
        var bug=state.bugList.find(x=>x.bugId==action.bugNo);
        var currentStatus=bug.status;
        var index =state.bugList.indexOf(bug);
        
        if(bug.status=='Assign'){
            state.assignedBug--;               
        }
        else if(bug.status=="Unassigned"){
            state.unassigned--;               
        }
        else if(bug.status=="Reopen"){
            state.reopenBug--;               
        }
        else if(bug.status=="Close"){
            state.completed--;               
        }
        if(bug.status!="Pending"){
            state.pendingBug++;
        }
        
        bug.status=bugTodoAction.Pending_Bug;

        return Object.assign({},state,{
            bugList:[
                ...state.bugList.slice(0,index),
                Object.assign({},bug),
                ...state.bugList.slice(index+1)
            ],
            lastUpdate:new Date()
        });

        case bugTodoAction.Remove_All:
        return Object.assign({},state,{
            bugList:[],
            totalBug:0,
            unassigned:0,
            assignedBug:0,
            pendingBug:0,
            completed:0,
            reopenBug:0
        });

        case bugTodoAction.Reopen_Bug:
        var bug=state.bugList.find(x=>x.bugId==action.bugNo);
        var currentStatus=bug.status;
        var index =state.bugList.indexOf(bug);
        
        if(bug.status=='Assign'){
            state.assignedBug--;               
        }
        else if(bug.status=="Unassigned"){
            state.unassigned--;               
        }
        else if(bug.status=="Pending"){
            state.pendingBug--;               
        }
        else if(bug.status=="Close"){
            state.completed--;               
        }
        if(bug.status!="Reopen"){
            state.reopenBug++;
        }
        bug.status=bugTodoAction.Reopen_Bug;

        return Object.assign({},state,{
            bugList:[
                ...state.bugList.slice(0,index),
                Object.assign({},bug),
                ...state.bugList.slice(index+1)
            ],
            lastUpdate:new Date()
        });

        case bugTodoAction.Open_Model:
            return Object.assign({},state,{
                bugId:action.bugId
            });
    }    
    return state;
}

We create a rootRedcuer function that takes the two parameters and using the switch statement, we define all the action blocks that are defined into the bugTodoAction.ts file. I know it is a little bit difficult to understand the above code, but don’t worry, we will cover all these methods and their functionalities in an upcoming part of the article. If your application is small, then you can create a single reducer and define all the actions into this single reducer function, but if the application is very large, then you can create multiple reducer functions and later combine all the reducer functions into a single unit. Here, we will use a single reducer method.

Until now, we have configured all the building blocks (Action, Store, Reducer) of the Redux pattern. Let’s implement this Redux pattern into an application.

Open the app.module.ts file and replace the code with the below lines of code:

JavaScript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import {FormsModule} from "@angular/forms";
import {NgRedux,NgReduxModule} from "@angular-redux/store";
import { IBugState, INITIAL_STATE } from '../store/BugStore';
import { rootReducre } from '../reducer/Reducer';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    NgReduxModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(ngRedux:NgRedux<IBugState>){
    ngRedux.configureStore(rootReducre,INITIAL_STATE);
  }
 }

In the above lines of code, we import some modules and add the NgReduxModule into the imports array. In the constructor function, we configure the state for the application using the NgRedux and also configure the reducer function that will handle all the actions. In configureStore function of ngRedux class, we pass the reducer function name and the initial state of the application.

Define the Layout for Application

Until now, we have configured all the Redux configuration blocks, defined the state and reducer function for the state into the AppModule.ts file. I think all major tasks have been completed, now we only need to define the view of our application and perform the required action.

Add Bootstrap 4 Configuration

We will use "Bootstrap 4" for design purposes, so open the index.html file and paste the following links into title section:

HTML
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/
 4.0.0-beta.2/css/bootstrap.min.css" 
 integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" 
 crossorigin="anonymous">
  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" 
   integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" 
   crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"/
   integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh"
   crossorigin="anonymous"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" 
   integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ"
   crossorigin="anonymous"></script>

If you are new to Bootstrap 4 or want to read more about Bootstrap 4, then you can go to the official website of Bootstrap.

Add Required Component

Open the command line terminal and run the ng g c bugTodo command. This command adds a new component in your project. In this component, we will write the code to add a new bug or clear all bugs list. After adding this component, now we add another component, so run the ng g c bugStatus command. This command adds a new component and we name this component as bug-status.component.ts. We will use this component to show the status of the bugs. We also show the list of all bugs that are generated until now. To add the other component, run the ng g c bugList command. This command adds a component as bug-list.component.ts in our project. After generating all three components, the following will be the structure of the project.

Image 7

Design Layout of the Application

After creating all the required components, now we design the layout of the application. Open the app.component.html file and replace the code with the following code:

App.component.html

HTML
<main role="main" class="container"> 
        <div class="row row-offcanvas row-offcanvas-right"> 
          <div class="col-12 col-md-9">
           <app-bug-todo></app-bug-todo>
           <app-bug-list></app-bug-list>
          </div> 
          <div class="col-6 col-md-3 sidebar-offcanvas" id="sidebar">
            <app-bug-status></app-bug-status>       
          </div>       
        </div>
        <hr> 
      </main>

In the same way, replace the code of all the remaining components:

bug-todo.component.html

HTML
<div class="card">
  <h4 class="card-header">Bug Dashboard</h4>
  <div class="card-body">
    <h4 class="card-title">Add new bug</h4>

    <form >
      <div class="form-row">
        <div class="col-auto">
          <input
           type="text" class="form-control"
           placeholder="Description" id="description"
           name="description"
            />
        </div>
        <div class="col-auto">
          <select
          type="text" class="form-control"
          placeholder="Priority" id="reprioritysponsible"
          name="priority"
          >
          <option value="Bike">Bike</option>
          <option value="Car">Car</option>
          <option value="Health">Health</option>
          <option value="Home">Home</option>
          <option value="ProHealth">ProHealth</option>
          </select>
        </div>
        <div class="col-auto">
              <select
               type="text" class="form-control"
               placeholder="Priority" id="reprioritysponsible"
               name="priority"
               >
               <option value="Low">Low</option>
               <option value="Medium">Medium</option>
               <option value="High">High</option>
               </select>
        </div>
        <div class="col-auto">
          <button type="submit" class="btn btn-info">Add Bug</button>
        </div>
      </div>
    </form>
    <br/>
    <a href="#" class="btn btn-danger">Clear List</a>
  </div>
</div>

bug-status.component.html

HTML
<ul class="list-group">
    <li class="list-group-item active">Bug Status</li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
      Total Bugs
      <span class="badge badge-primary badge-pill">14</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Assigned Bugs
      <span class="badge badge-secondary badge-pill">2</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Unassigned Bugs
      <span class="badge badge-primary badge-pill">2</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Pending  Bugs
      <span class="badge badge-warning badge-pill">2</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
      Reopen Bug
      <span class="badge badge-danger badge-pill">1</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Completed Bug
        <span class="badge badge-success badge-pill">1</span>
      </li>
  </ul>

bug-list.component.html

HTML
<h4>Bug List</h4>
<table class="table table-bordered">
    <thead>
      <tr>
        <th scope="col">BugId</th>
        <th scope="col">Project</th>
        <th scope="col">Description</th>
        <th scope="col">Status</th>
        <th scope="col">Priority</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <th scope="row">1</th>
        <td>Health</td>
        <td>Mark</td>
        <td>Otto</td>
        <td>@mdo</td>
      </tr>
      <tr>
        <th scope="row">2</th>
        <td>Health</td>
        <td>Mark</td>
        <td>Otto</td>
        <td>@TwBootstrap</td>
      </tr>
      <tr>
        <th scope="row">3</th>
        <td>Health</td>
        <td>Jacob</td>
        <td>Thornton</td>
        <td>@fat</td>
      </tr>
      <tr>
        <th scope="row">4</th>
        <td>Health</td>
        <td >Larry the Bird</td>
        <td>Thornton</td>
        <td>@twitter</td>
      </tr>
    </tbody>
  </table>

Now save all the changes and refresh your browser, after making all the changes, the following will be the design of our application.

Image 8

If you get the above screen without any error message into console, that means till now everything is configured very well. Ok, now our Redux structure is ready, design is also ready, so let’s use the Redux mechanism and bind the components with application state.

Add New Bug

Now we write the program to add a new bug into the bug list, so open the bug-todo.component.ts file and paste the following code into this file:

JavaScript
import { Component, OnInit } from '@angular/core';
import {NgRedux,select} from "@angular-redux/store";
import {bugTodoAction} from "../../action/BugAction";
import {IBugState} from "../../store/BugStore";
import {IBugModel} from "../../model/BugModel";
@Component({
  selector: 'app-bug-todo',
  templateUrl: './bug-todo.component.html',
  styleUrls: ['./bug-todo.component.css']
})
export class BugTodoComponent implements OnInit {

  bugObject:IBugModel={
    bugId:0,
    description:"",
    project:"Car",
    priority:"Low",
    status:""
  }

  constructor(private ngRedux:NgRedux<IBugState>) {
   }

  ngOnInit() {
  }

  submitForm=()=>{
    this.ngRedux.dispatch({type:bugTodoAction.Add_NewBug,todo:this.bugObject});
  }

  clearList=()=>{
    this.ngRedux.dispatch({type:bugTodoAction.Remove_All,todo:this.bugObject});
  }

}

In the above line of code, first we imported all the required modules into our project like IBugState, IBugModel and bugTodoAction. We also imported the ngRedux and select modules. We created an object bugObject of IBugModel type. We will use this object as a model for the form and also pass this object along with action payload to update the state for submitForm and clearForm method. We create the submitForm method that will be called when form will submit. In this method, we call the dispatch method of the Store instance. In this method, we pass the payload and this payload contains the action type and data for creating a new bug. We also create the clearList method, we will use this method to clear the bug list.

bug-todo.component.html

We will use the template driven approach to create the html from, so replace the code of this file with the following code:

HTML
<div class="card">
  <h4 class="card-header">Bug Dashboard</h4>
  <div class="card-body">
    <h4 class="card-title">Add new bug</h4>

    <form (ngSubmit)="submitForm()" #form="ngForm">
      <div class="form-row">
        <div class="col-md-6 col-sm-12">
          <textarea
           type="text" class="form-control"
           rows="3"
           placeholder="Description" id="description"
           [(ngModel)]="bugObject.description"
           name="description"
            ></textarea>

        </div>
        
      </div>
      <br/>
      <div class="form-row">
          <div class="col-auto">
              <select
              type="text" class="form-control"
              placeholder="Project" id="project"
              name="project"
              [(ngModel)]="bugObject.project"
              >
              <option value="Bike">Bike</option>
              <option value="Car">Car</option>
              <option value="Health">Health</option>
              <option value="Home">Home</option>
              <option value="ProHealth">ProHealth</option>
              </select>
            </div>
            <div class="col-auto">
                  <select
                   type="text" class="form-control"
                   placeholder="Priority" id="priority"
                   name="priority"
                   [(ngModel)]="bugObject.priority"
                   >
                   <option value="Low">Low</option>
                   <option value="Medium">Medium</option>
                   <option value="High">High</option>
                   </select>
            </div>
            <div class="col-auto">
              <button type="submit" class="btn btn-info">Add Bug</button>
            </div>
      </div>
    </form>
    <br/>
    <button type="button" class="btn btn-danger" (click)="clearList()">
            Clear List</button>
  </div>
</div>

When we click on the Add Bug button, the submitForm method will call. In this method, we invoke the dispatch method of store. This method calls the reducer that we configured into app.modulet.ts class’ constructor function.

Image 9

A point to notice is that in the dispatch method, we assign the action type parameter of bugTodoAction so reducer will identify the method type and execute the code that is written for this action type. In the same way for the clearList function, we call the reducer function of the store and assign the type parameter of Remove_All type .

Image 10

Image 11

The rootReducer function takes two parameters. The first parameter defines the previous state of the application and the second parameter contains the payload of the action. If action type is the Add_NewBug, then we take the previous state and add a new bug into the bugList property and also update the remaining property and return this new state to the application.

Image 12

If action type is Remove_All, then we empty the bugList and set all remaining property to 0 and return this new state to the application.

Bug-status.component.ts

In the Bug-status component, we will show the status of the bugs. The property of IBugStore contains these required information. So we need to access these properties into our component and display into html page.

Image 13

Bug-status.component.html

HTML
<ul class="list-group">
    <li class="list-group-item active">Bug Status</li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
      Total Bugs
      <span class="badge badge-primary badge-pill">{{totalBug | async}}</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Assigned Bugs
      <span class="badge badge-secondary badge-pill">{{assignBug | async}}</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Unassigned Bugs
      <span class="badge badge-primary badge-pill">{{unassignedBug | async}}</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Pending  Bugs
      <span class="badge badge-warning badge-pill">{{pendingBug | async}}</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
      Reopen Bug
      <span class="badge badge-danger badge-pill">{{reopenBug | async}}</span>
    </li>
    <li class="list-group-item d-flex justify-content-between align-items-center">
        Completed Bug
        <span class="badge badge-success badge-pill">{{completedBug | async}}</span>
      </li>
  </ul>

Bug-status.component.ts

JavaScript
import { Component, OnInit } from '@angular/core';
import {NgRedux,select} from "@angular-redux/store";
import {bugTodoAction} from "../../action/BugAction";
import {IBugState} from "../../store/BugStore";
import {IBugModel} from "../../model/BugModel";
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-bug-status',
  templateUrl: './bug-status.component.html',
  styleUrls: ['./bug-status.component.css']
})
export class BugStatusComponent implements OnInit {

  @select('totalBug') totalBug;
  @select('assignedBug') assignBug;
  @select('unassigned') unassignedBug;
  @select('pendingBug') pendingBug;
  @select('reopenBug') reopenBug;
  @select('completed') completedBug;
  constructor(private ngRedux:NgRedux<IBugState>) {
  
   }

  ngOnInit() {
  }

}

In this component, we only needs to access the flag properties of the IBugStore state, so we use the @select decorator. The @select decorator is the observable type that binds a variable to the property of the state. So @select('totalBug') totalBug line of code binds the totalBug variable to the totalBug property of the IBugStore. If the value of state property changes, then this variable also changes. In the same way, using the @select decorator, we access the all remaining property of the IBugStore state and display into the .html page.

Now save all the changes and try to make some entry into form. In starting, we don’t have any bug into bugList property so all the bug statuses are zero.

Image 14

Now add some entry and see the changes. As you click on Add Bug button, a new entry will insert into bugList and Bug Status flags will update. When you add a new bug, you will find that Total Bug and Unassigned Bug flag values are updated.

Image 15

Add another bug and you get that Total Bug and Unassigned Bug status has been updated again.

Image 16

Display the Bug List

Up to now, we successfully completed the work of adding the new bugs into buglist and display the count of all bug status. Our next task will be to show all these added bugs into a list and add a popup in which we can see all the details of a bug and can change the status of the particular bug. We open this popup model on click of the bug id.

Image 17

First of all, we need to add an another component in which we will show the details of the bug and add the functionality to change the status of the bug. We will show this component in the bootstrap popup model.

Image 18

Run ng g c BugInfo command into your command line terminal. This command adds a new component and names this component as BugInfo.

After successfully adding the component, now paste the following code into bug-list.component.html file.

Bug-list.component.html

HTML
<h4>Bug List</h4>
<table class="table table-bordered">
    <thead>
      <tr>
        <th scope="col">BugId</th>
        <th scope="col">Project</th>
        <th scope="col">Description</th>
        <th scope="col">Current Status</th>
        <th scope="col">Priority</th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let data of bugList | async">
        <th scope="row">
          <a href=""  data-toggle="modal" (click)="openPopupModel(data.bugId)"
           data-target="#exampleModal"> {{data.bugId}}</a>
         
        </th>
        <td >{{data.project}}</td>
        <td>{{data.description}}</td>
        <td>{{data.status}}</td>
        <td>{{data.priority}}</td>
      </tr>
    </tbody>
  </table>

  <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" 
   aria-labelledby="exampleModalLabel" aria-hidden="true">
      <div class="modal-dialog" role="document">
        <div class="modal-content">
          <div class="modal-header">
            <h5 class="modal-title" id="exampleModalLabel">Bug Description</h5>
            <button type="button" class="close" 
             data-dismiss="modal" aria-label="Close">
              <span aria-hidden="true">×</span>
            </button>
          </div>
          <div class="modal-body">
           <app-bug-info></app-bug-info>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" 
             data-dismiss="modal">Close</button>
          </div>
        </div>
      </div>
    </div>

Now open the bug-list.component.ts file and paste the following code into this file.

Bug-list.component.ts

JavaScript
import { Component, OnInit } from '@angular/core';
import { NgRedux, select } from '@angular-redux/store';
import { IBugState } from '../../store/BugStore';
import { bugTodoAction } from '../../action/BugAction';

@Component({
  selector: 'app-bug-list',
  templateUrl: './bug-list.component.html',
  styleUrls: ['./bug-list.component.css']
})
export class BugListComponent implements OnInit {

  @select('bugList') bugList;
  constructor(private ngRedux:NgRedux<IBugState>) {

   }

  ngOnInit() {
  }

  openPopupModel=(bugId)=>{
      this.ngRedux.dispatch({type:bugTodoAction.Open_Model,bugId:bugId})
  }
}

In the above line of code, we inject the NgRedux dependency into constructor function and access the bugList property of our application state using the @select decorator. This property contains the list of all bugs. As I earlier explained, the @select decorator is observable type and allows us to access the property of application state. To get the bug list, we need to subscribe this variable into our html page that we will performed using the async pipe and show the list. For each bug, we also add the functionality to open the popup model. When we click on any bug id, a popup model will open and in this popup model, we will show the bugInfo component.

Image 19

If you go through the code of the anchor tag, you will find that on the click action, we are performing two tasks. On click action, we call the openPopupModel method of component and open a popup model.

Image 20

In openPopupModel method, we execute the dispatch method of reducer. In form of payload for the reducer method, we pass the Open_Model as action type and bugId.

Image 21

In reducer function, if action type is Open_Model, then we fetch out the bugId from the payload and update the budId property of the state. We update this bugId property because we need this bugId when we open the popup model to access the information about a particular bug and perform the further action for that bug. So we will use this bugId property in our bugInfo component.

Bug-info.component.ts

JavaScript
import { Component, OnInit } from '@angular/core';
import { select, NgRedux } from '@angular-redux/store';
import { IBugState } from '../../store/BugStore';
import { IBugModel } from '../../model/BugModel';
import { bugTodoAction } from '../../action/BugAction';

@Component({
  selector: 'app-bug-info',
  templateUrl: './bug-info.component.html',
  styleUrls: ['./bug-info.component.css']
})
export class BugInfoComponent implements OnInit {
  @select('bugId') bugId;
  @select('bugList') bugList;
  bugNumber:number;
  bugInfo:IBugModel;
  status:string="Assign";
  constructor(private ngRedux:NgRedux<IBugState>) {

    this.bugId.subscribe(data=>{
      this.bugList.subscribe(data1=>{
        this.bugNumber=data;
        this.bugInfo=data1.filter(x=>x.bugId==data)[0] 
      });
    });
    
   }

  ngOnInit() {
      
  }
  submit=()=>{
    this.ngRedux.dispatch({type:this.status,bugNo:this.bugNumber});
  }
  ChangeStatus=()=>{

  }
}

In the above code, we inject the NgRedux dependency into the constructor function. We also create two observable bugId and bugList using the @select decorator. In the constructor function, we subscribe both observable to get the bugId and bug data from the bugList property of the store.

At the last line of the code, we create submit method. We call this method from our html page and in this function, we pass the new status of bug and bugId as payload to the reducer function and at reducer end, we update the status of bug that matches this particular bugId sent in payload.

Bug-info.component.html

HTML
<div class="card" style="width:400px">
    <div class="card-body">
      <h4 class="card-title">Bug Number: {{(bugNumber)}}</h4>
      <div class="card-blockquote">
        <span style="font-weight: bold;">Priority: {{bugInfo?.priority}}</span>
        <span style="float: right;font-weight: bold;">Status: {{bugInfo?.status}}</span>
        <br/>
        <span style="font-weight: bold;">Project: {{bugInfo?.project}}</span>
      </div>
      <br/>
      <p class="card-text">{{bugInfo?.description}}</p>
      <div>
          <select
          type="text" class="form-control"
          [(ngModel)]="status"
          (change)="ChangeStatus()"
          >
          <option value="Assign">Assign</option>
          <option value="Pending">Pending</option>
          <option value="Close">Close</option>
          <option value="Reopen">Reopen</option>
          </select>
      </div>
      <br/>
      
      <input type="button" value="Submit" (click)="submit()" class="btn btn-primary"/>
    </div>
  </div>
  <br>

In the following lines of the code, we simply show the current information of the bug and we also create a select list to change the bug status. On Submit button click, we send the changed status code to the reducer and at reducer end, we update the status of code. After making all these changes, now our project is completely ready to run.

Let’s take a complete scenario of our project.

Image 22

Currently, we have three bugs in our bugList and all these bugs are Unassigned. Now, click on any BugId (here, I clicked on bugId 2). When we click on any bugId, a popup model opens that contains that displays the bug information and provides the functionality to update the status of the bug.

Image 23

Now change the status of bug to Assign.

Image 24

When you click on the Submit button, you will find that the status of bug has been changed and count of unassigned and assigned bug has been changed. When you close the popup model, you will find that bug list is also changed.

Image 25

Let’s understand the reducer function and how it updates the status of a bug.

Image 26

If the new status type for the bug is Assigned, then we get that particular bug from the bugList and update the status of this bug. We also checked the previous status of the bug and reduce the -1 from the previous status count and update the state. For current bug status, we increase the assigned counts by 1 for the state properties. For example, if the previous state of the bug is unassigned, then we reduce the unassigned count by 1 and if new status is assigned, then update the assignedBug count by 1. We performed similar functionality for all other status codes.

Conclusion

When you run this project and go through the various steps of the project (like adding a new bug), clear the list of bugs and change the status of the bug, you will find that bug-list component, bug-status and bug-info components remain in a synchronized state. A change in any components reflect the changes into all three components and we are not passing any data between all these components. We achieve this functionality because our application state is centralized and all components of application using this state. If any components make any change into state, then this update also listens by other components and these components can also make their changes.

The final conclusion is that if we are going to create a bigger application in Angular, then it will be better to use a state management architecture that can manage the state of our application. This state management technique will definitely increase the performance of our application. But if our application is not large, then it is not a good idea to use any state management technique and implementation of any such kind of functionality can reduce the performance of our application. If you have any ideas regarding this article, then you can mention them in the comment section.

You can download this article from the below GitHub repository:

Thank you for reading this article.

History

  • 28th November, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)