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.
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
Redux works on following these key points:
- Maintains the whole application data into a single state and we can access the state from the store of application.
- This store doesn’t mutate directly.
- Actions are used to trigger the change into state of the application.
- Action called the dispatcher.
- 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.
Now open the given URL into any browser and you will find the below screen:
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:
export interface IBugModel{
bugId:number;
description:string;
project:string;
priority:string;
status:string;
}
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.
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:
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:
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:
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:
<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.
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
<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
<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
<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
<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.
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:
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:
<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.
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 .
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.
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.
Bug-status.component.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
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.
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.
Add another bug and you get that Total Bug
and Unassigned Bug
status has been updated again.
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.
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.
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
<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
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.
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.
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
.
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
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
<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.
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.
Now change the status of bug to Assign
.
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.
Let’s understand the reducer
function and how it updates the status of a bug.
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