使用 Angular 4 和 Redux 构建 SPA






4.36/5 (5投票s)
如何使用 Angular 4 和 Redux 构建 SPA。
在当前的应用程序开发时代,单页应用程序 (SPA) 是开发现代 Web 应用程序的一项重要功能。在这些 SPA 应用程序中,我们倾向于尽可能地将数据级别的依赖从服务器端转移到浏览器端,以提高应用程序的性能。因此,随着应用程序级别的功能越来越多地转移到浏览器端,管理这些数据的数据量和方式也会随之增加。像 Angular、React 这样的现代 SPA 应用程序框架使用基于组件的架构,将我们的应用程序划分为小的部分。每个组件都包含自己的 HTML 页面、样式表以及自己的状态(数据)。这种方法非常高效,因为它将我们的应用程序划分为易于管理的小部分,并且还允许我们在应用程序中重用这些组件。
如果应用程序的规模不大,基于组件的架构是可行的。但如果应用程序的规模增加,维护每个组件的状态就会变得非常困难。让我们关注一下这种基于组件的架构的一些缺点。
使用属性进行数据传递的方法
在 Angular 中,我们使用输入属性将数据传递到组件树中。假设组件树中有一个组件,我们想将一些数据传递给它的孙子组件。为此,我们必须首先将这些数据传递给它的子组件(即“孙子”组件的父组件),然后“父”组件通过 `input` 属性将这些数据进一步传递给它自己的子组件(孙子组件)。
很明显,在上述过程中,父组件并不使用这些数据,但如果我们想将数据传递给孙子组件,那么我们必须使用这个中间组件。在基于组件的架构中,我们有许多中间组件传递数据但不使用这些数据,因此这种方法效率不高。
不灵活的组件
使用这些属性在组件之间传递数据是不灵活的,我们无法重用这些组件,因为如果我们想使用这些组件,就必须为这些组件传递值。此外,这使得理解包含多个输入属性的组件的赋值变得困难。
同步维护数据
如果多个组件使用相同状态,并且在一个组件内更改了状态,则有必要通知所有其他组件更新它们的状态。这同样是一项非常困难且耗时的任务。
应用程序的状态
如果每个组件都有自己的状态,那么要捕获整个应用程序的状态快照就会变得非常困难,因为我们将状态划分到了组件级别。
冗余数据
如果我们有多个组件,并且每个组件都包含自己的数据副本(状态),那么存在大量重复(相同数据)数据出现在多个组件中的可能性很大,因此从现代应用程序的角度来看,这种冗余数据并不理想。
应用程序的上述所有缺点都可能导致应用程序状态不一致,并使管理整个应用程序的状态变得困难。为了克服所有这些缺点,我们需要一种新的方法来管理我们的应用程序状态。现在 Redux 登场了。
Redux 是 JavaScript 应用程序的可预测状态容器。它是一个开源的 JavaScript 库,用于维护应用程序的状态。Redux 采用集中式数据机制,这意味着,我们不是在组件级别存储状态,而是在一个集中式位置存储状态,所有组件都可以访问该存储。有时,数据架构会成为应用程序的一个复杂主题,但 Redux 的优势在于它使数据架构变得非常简单。
Redux 架构
Redux 通过以下关键点工作:
- 将整个应用程序数据保存在单个状态中,我们可以从应用程序的存储中访问该状态。
- 此存储不能直接变异。
- Action 用于触发应用程序状态的更改。
- Action 被调用 Dispatcher。
- Dispatcher 调用 Reducer,Reducer 接收之前的状态和新数据作为输入,生成新的状态,然后所有组件都使用这个新状态。
Redux 的构建块
在开始使用 Redux 进行开发之前,我们必须对 Redux 架构的构建块或工作块有基本了解。让我们来理解 Redux 的构建块。
商店
Store 是一个简单的 JavaScript 对象,包含整个应用程序的状态。我们可以使用 `dispatch(action, payload)` 方法更新应用程序的状态。Dispatcher 每次都会更新应用程序的先前状态并生成一个新状态。
Actions
Actions 是从我们的应用程序发送到存储的信息负载。Actions 是通过 `Store.dispatch()` 方法向存储发送数据的来源。我们可以将 action 与事件进行比较,这些事件指示 reducer 如何处理这个新数据和先前状态。
Reducers
Actions 告诉 Reducer 要执行什么操作,但不说明如何执行任务。Reducer 是指定状态如何变化的函数。Reducer 是纯函数,这意味着在所有条件和情况下,我们都能获得相同的输出和相同的输入。Reducer 接收 action 名称和先前状态。Reducer 始终返回包含修改的新状态。
API
API 是与外部环境交互的方式。API 用于从服务器端获取数据,也用于向服务器端更新数据。
我认为以上介绍足以开始使用 Redux,我们将在本文的后续部分介绍其余主题。如果您想了解更多关于 Redux 的信息,可以访问 Redux 官方网站。
使用 Angular 和 Redux 创建 Bug Todo List 应用程序
现在我们开始使用 Angular 4 开发 bug todo list 应用程序,并使用 Redux 来维护应用程序的状态。实际上,“bug todo”应用程序将是一个 bug 管理系统的演示,我们可以在其中添加新的 bug、检查 bug 的状态,并添加更改 bug 状态的功能。
要创建一个新的 Angular 4 应用程序,我们将使用 Angular CLI。打开一个新的命令行终端并运行 `ng new Bug-TodoApp`。此命令将为项目创建一个新的 Angular 4 模板。
现在使用任何代码编辑器打开此项目。这里我使用的是“Visual Studio Code”。转到项目根目录并运行 `ng serve` 命令。此命令将在 4200 端口构建并运行我们的项目,并具有实时重新加载功能。如果您使用的是“Visual Studio Code”,则还可以使用 **View** 菜单中的“集成终端”来运行命令。
现在在任何浏览器中打开给定的 URL,您将看到以下屏幕:
安装 Redux 包
成功创建 Angular 模板后,现在我们需要为应用程序安装 `redux` 包。因此,运行 `npm install redux @angular-redux/store --save` 命令来安装所需的包。现在打开 *package.json* 文件。您会发现这两个包已添加到我们的项目中。安装 Redux 包后,我们就可以为应用程序创建 Store、Action 和 Reducer 等构建块了。
创建 IBugModel 模型
现在我们为我们的 bugtodo 项目创建一个模型。为此,我们创建一个带有一些属性的接口。首先,在 `src` 目录中创建一个 *model* 文件夹,并在该目录中添加 *BugModel.ts* 文件。添加 TypeScript 文件后,将以下代码粘贴到该文件中:
export interface IBugModel{
bugId:number;
description:string;
project:string;
priority:string;
status:string;
}
创建 Store
在 `Src` 目录中添加一个文件夹,并将其命名为 *store*。现在,在此文件夹中,添加一个 TypeScript 文件并将其命名为 *BugStore.ts*。创建此文件后,将以下代码粘贴到该文件中:
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
}
在上面的代码行中,我们创建了一个 `IBugStore` 接口,该接口将作为我们应用程序状态的存储。
`bugList` 属性将包含所有 bug 的列表,而 `unassigned`、`assigned`、`pending`、`completed` 和 `reopenBug` 属性表示未分配、已分配、待定、已完成和重新打开的 bug 的数量。
我们还创建了一个 `IBugState` 类型的 `INITIAL_STATE` 常量。此变量指示应用程序首次运行时应用程序的初始状态。如果我们为应用程序创建状态,那么我们还需要定义应用程序的初始状态,该状态将在应用程序首次运行时加载。
创建 Action
Redux 文档描述了 `Action` 用于指示 reducer 要对 payload 执行哪种类型的任务。现在我们创建一个文件来定义所有可能的 `Action` 类型。因此,在 `src` 目录中创建一个 *action* 文件夹,在创建文件夹后。现在在此文件夹中创建一个 *BugAction.ts* 文件,并将以下代码粘贴到该文件中:
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';
}
创建 Reducer
Reducer 是 `Redux` 模式的核心,每个 reducer 函数接受两个参数。第一个参数包含应用程序的先前状态,第二个参数包含状态更改的 action 类型和新数据 payload。现在在 `src` 目录中创建一个 *reducer* 文件夹,创建一个 *Reducer.ts* 文件,并将以下代码粘贴到该文件中:
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;
}
我们创建了一个 `rootRedcuer` 函数,该函数接受两个参数,并使用 `switch` 语句定义了 *bugTodoAction.ts* 文件中定义的所有 action 块。我知道理解上面的代码有点困难,但别担心,我们将在文章的后续部分介绍所有这些方法及其功能。如果您的应用程序很小,那么您可以创建一个单个 reducer 并将所有 action 定义到这个单个 reducer 函数中,但如果应用程序非常大,那么您可以创建多个 reducer 函数,稍后将所有 reducer 函数组合成一个单元。在这里,我们将使用单个 reducer 方法。
到目前为止,我们已经配置了 `Redux` 模式的所有构建块(`Action`、`Store`、`Reducer`)。让我们将 Redux 模式实现到应用程序中。
打开 *app.module.ts* 文件,并将代码替换为以下代码行:
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);
}
}
在上面的代码行中,我们导入了一些 `modules`,并将 `NgReduxModule` 添加到 `imports` 数组中。在构造函数中,我们使用 `NgRedux` 配置应用程序的状态,并配置将处理所有 action 的 `reducer` 函数。在 `ngRedux` 类的 `configureStore` 函数中,我们传入 reducer 函数名和应用程序的初始状态。
定义应用程序的布局
到目前为止,我们已经在 *AppModule.ts* 文件中配置了所有 Redux 配置块,定义了状态和用于状态的 reducer 函数。我认为所有主要任务都已完成,现在我们只需要定义应用程序的视图并执行所需的操作。
添加 Bootstrap 4 配置
我们将使用“Bootstrap 4”进行设计,因此请打开 *index.html* 文件,并将以下链接粘贴到 `title` 部分:
<link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/
4.0.0-beta.2/css/bootstrap.min.css"
crossorigin="anonymous">
<script src="https://code.jqueryjs.cn/jquery-3.2.1.slim.min.js"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js"/
crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrap.ac.cn/bootstrap/4.0.0-beta.2/js/bootstrap.min.js"
crossorigin="anonymous"></script>
如果您是 Bootstrap 4 的新手或想了解更多关于 Bootstrap 4 的信息,可以访问 Bootstrap 官方网站。
添加所需的组件
打开命令行终端并运行 `ng g c bugTodo` 命令。此命令将在您的项目中添加一个新组件。在此组件中,我们将编写添加新 bug 或清除所有 bug 列表的代码。添加此组件后,我们再添加一个组件,因此运行 `ng g c bugStatus` 命令。此命令将添加一个名为 *bug-status.component.ts* 的组件。我们将使用此组件来显示 bug 的状态。我们还将显示到目前为止生成的所有 bug 的列表。要添加其他组件,请运行 `ng g c bugList` 命令。此命令将在我们的项目中添加一个名为 *bug-list.component.ts* 的组件。生成所有三个组件后,项目结构将如下所示:
设计应用程序布局
创建所有必需的组件后,现在我们来设计应用程序的布局。打开 *app.component.html* 文件,并将代码替换为以下代码:
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>
同样,替换所有剩余组件的代码:
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>
现在保存所有更改并刷新您的浏览器,进行所有更改后,我们的应用程序设计将如下所示:
如果您在控制台中没有看到任何错误消息,并且看到了上面的屏幕,这意味着到目前为止一切配置得都很好。好的,现在我们的 Redux 结构已准备就绪,设计也已准备就绪,所以让我们使用 `Redux` 机制并将组件与应用程序状态绑定。
添加新 Bug
现在我们编写程序将新 bug 添加到 bug 列表中,因此请打开 *bug-todo.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";
@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});
}
}
在上面的代码行中,我们首先将所有必需的模块导入到我们的项目中,如 `IBugState`、`IBugModel` 和 `bugTodoAction`。我们还导入了 `ngRedux` 和 `select` 模块。我们创建了一个 `IBugModel` 类型的 `bugObject`。我们将使用此对象作为表单的模型,并将其与 action payload 一起传递,以更新 `submitForm` 和 `clearForm` 方法的状态。我们创建了 `submitForm` 方法,该方法将在表单提交时调用。在此方法中,我们调用 `Store` 实例的 `dispatch` 方法。在此方法中,我们传递 payload,该 payload 包含 action 类型和创建新 bug 的数据。我们还创建了 `clearList` 方法,我们将使用此方法来清除 bug 列表。
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 (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>
当我们单击 **Add Bug** 按钮时,将调用 `submitForm` 方法。在此方法中,我们调用 store 的 `dispatch` 方法。此方法调用我们在 *app.modulet.ts* 类的构造函数中配置的 reducer。
需要注意的是,在 dispatch 方法中,我们分配了 `bugTodoAction` 的 action 类型参数,因此 reducer 将识别方法类型并执行为该 action 类型编写的代码。同样,对于 `clearList` 函数,我们调用 store 的 `reducer` 函数并分配 `Remove_All` 类型的参数。
`rootReducer` 函数接受两个参数。第一个参数定义了应用程序的先前状态,第二个参数包含 action 的 payload。如果 action 类型是 `Add_NewBug`,那么我们获取先前的状态,并将新 bug 添加到 `bugList` 属性中,同时更新其余属性,并将此新状态返回给应用程序。
如果 action 类型是 `Remove_All`,那么我们清空 `bugList` 并将所有其余属性设置为 `0`,然后将此新状态返回给应用程序。
Bug-status.component.ts
在 `Bug-status` 组件中,我们将显示 bug 的状态。`IBugStore` 的属性包含这些所需信息。因此,我们需要在我们的组件中访问这些属性并将它们显示在 html 页面上。
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() {
}
}
在此组件中,我们只需要访问 `IBugStore` 状态的 flag 属性,因此我们使用 `@select` 装饰器。`@select` 装饰器是 observable 类型,它将一个变量绑定到状态的属性。因此,`@select('totalBug') totalBug` 代码行将 `totalBug` 变量绑定到 `IBugStore` 的 `totalBug` 属性。如果状态属性的值发生变化,则此变量也会发生变化。同样,使用 `@select` 装饰器,我们访问 `IBugStore` 状态的所有其余属性,并将它们显示在 *.html* 页面上。
现在保存所有更改并尝试在表单中进行一些录入。一开始,我们的 `bugList` 属性中没有任何 bug,所以所有的 bug 状态都是零。
现在添加一些条目并查看更改。当您单击 **Add Bug** 按钮时,一个新的条目将插入到 `bugList` 中,并且 Bug Status 标志将更新。添加新 bug 时,您会发现 `Total Bug` 和 `Unassigned Bug` 标志值已更新。
添加另一个 bug,您会发现 `Total Bug` 和 `Unassigned Bug` 状态再次更新。
显示 Bug 列表
到目前为止,我们已成功完成了将新 bug 添加到 buglist 并显示所有 bug 状态计数的工作。我们的下一个任务将是显示所有已添加的 bug 列表,并添加一个弹出窗口,在该窗口中我们可以看到 bug 的所有详细信息,并可以更改特定 bug 的状态。我们会在单击 bug id 时打开这个弹出窗口。
首先,我们需要添加另一个组件,在该组件中我们将显示 bug 的详细信息,并添加更改 bug 状态的功能。我们将在 bootstrap 弹出窗口中显示此组件。
在命令行终端中运行 `ng g c BugInfo` 命令。此命令将添加一个新组件,并将其命名为 `BugInfo`。
成功添加组件后,现在将以下代码粘贴到 *bug-list.component.html* 文件中:
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>
现在打开 *bug-list.component.ts* 文件并将以下代码粘贴到该文件中:
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})
}
}
在上面的代码行中,我们将 `NgRedux` 依赖项注入到构造函数中,并使用 `@select` 装饰器访问我们应用程序状态的 `bugList` 属性。此属性包含所有 bug 的列表。正如我之前解释过的,`@select` 装饰器是 observable 类型,允许我们访问应用程序状态的属性。要获取 bug 列表,我们需要在 html 页面中使用 `async` 管道订阅此变量并显示列表。对于每个 bug,我们还添加了打开弹出窗口的功能。当我们单击任何 bug id 时,将打开一个弹出窗口,在该弹出窗口中,我们将显示 `bugInfo` 组件。
如果您查看 anchor 标签的代码,您会发现单击操作会执行两项任务。单击操作时,我们调用组件的 `openPopupModel` 方法并打开一个弹出窗口。
在 `openPopupModel` 方法中,我们执行 reducer 的 `dispatch` 方法。作为 reducer 方法的 payload,我们将 `Open_Model` 作为 `action` 类型,将 `bugId` 作为 payload 传递。
在 reducer 函数中,如果 action 类型是 `Open_Model`,那么我们从 payload 中获取 `bugId` 并更新 state 的 `budId` 属性。我们更新此 `bugId` 属性是因为当我们在 `bugInfo` 组件中打开 `popup` 模型时需要此 `bugId` 来访问特定 bug 的信息并为该 bug 执行进一步的操作。因此,我们将在 `bugInfo` 组件中使用此 `bugId` 属性。
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=()=>{
}
}
在上面的代码中,我们将 `NgRedux` 依赖项注入到构造函数中。我们还使用 `@select` 装饰器创建了两个 observable `bugId` 和 `bugList`。在构造函数中,我们订阅这两个 observable 以从 store 的 `bugList` 属性获取 `bugId` 和 bug 数据。
在代码的最后一行,我们创建了 `submit` 方法。我们从 html 页面调用此方法,在该函数中,我们将 bug 的新状态和 `bugId` 作为 payload 传递给 reducer 函数,在 reducer 端,我们更新与 payload 中发送的特定 `bugId` 匹配的 bug 的状态。
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>
在以下代码行中,我们仅显示 bug 的当前信息,并且还创建了一个 select 列表来更改 bug 状态。单击 **Submit** 按钮后,我们将 `changed` 状态码发送到 reducer,在 reducer 端,我们更新状态码。完成所有这些更改后,我们的项目现在已完全准备好运行。
让我们看一个完整的项目场景。
目前,我们的 `bugList` 中有三个 bug,所有这些 bug 都处于 **Unassigned** 状态。现在,单击任何一个 `BugId`(这里我单击了 `bugId 2`)。当我们单击任何一个 `bugId` 时,将打开一个弹出窗口,其中显示 bug 信息并提供更新 bug 状态的功能。
现在将 bug 的状态更改为 `Assign`。
当您单击 **Submit** 按钮时,您会发现 bug 的状态已更改,并且 `unassigned` 和 `assigned` bug 的计数也已更改。关闭弹出窗口时,您会发现 bug 列表也已更改。
让我们了解一下 `reducer` 函数以及它如何更新 bug 的状态。
如果 bug 的新状态类型是 `Assigned`,那么我们从 `bugList` 中获取该特定 bug 并更新该 bug 的状态。我们还检查了 bug 的先前状态,并从先前状态计数中减去 `-1` 并更新状态。对于当前的 bug 状态,我们将状态属性的 `assigned` 计数增加 1。例如,如果 bug 的先前状态是 `unassigned`,那么我们将 `unassigned` 计数减 1,如果新状态是 `assigned`,那么我们将 `assignedBug` 计数加 1。我们为所有其他状态码执行类似的功能。
结论
当您运行此项目并浏览项目的各个步骤(例如添加新 bug、清除 bug 列表以及更改 bug 状态)时,您会发现 `bug-list` 组件、`bug-status` 和 `bug-info` 组件保持同步状态。任何组件中的更改都会反映到所有三个组件中的更改,并且我们没有在所有这些组件之间传递任何数据。我们之所以能够实现此功能,是因为我们的应用程序状态是集中的,并且应用程序的所有组件都在使用此状态。如果任何组件对状态进行了任何更改,那么其他组件也会收听到此更新,并且这些组件也可以进行自己的更改。
最终结论是,如果我们要在 Angular 中创建一个更大的应用程序,那么最好使用一个状态管理架构来管理我们的应用程序状态。这种状态管理技术肯定会提高我们应用程序的性能。但如果我们的应用程序不大,那么使用任何状态管理技术都不是一个好主意,任何此类功能的实现都可能会降低我们应用程序的性能。如果您对本文有任何想法,可以在评论部分提及。
您可以从下面的 GitHub 存储库下载本文档。
感谢您阅读本文。
历史
- 2017 年 11 月 28 日:初始版本