实现待办事项应用的增删功能:Angular 实战
在开发 Angular 应用时,我们不仅要能列出任务,还需要通过 Angular UI 实现添加新任务和删除现有任务的功能。接下来,我们将详细介绍如何实现这些功能。
设计添加任务功能
要实现添加新任务的功能,我们需要在组件和服务中添加一些新代码,管道部分保持不变,同时还需要修改
tasks.component.html
文件。我们先从组件相关的工作开始。
组件的演进
为了支持添加任务功能,组件需要通过以下测试:
-
newTask
应正确初始化
- 组件应能将无数据的
newTask
正确转换为 JSON
- 组件应能将有数据的
newTask
正确转换为 JSON
-
addTask
应向服务注册处理程序
-
updateMessage
应更新消息并调用
getTasks
-
disableAddTask
应使用
validateTask
下面是具体的实现步骤:
1.
验证
newTask
初始化
:打开
test/client/app/tasks/tasks.component-test.js
文件,添加以下测试代码:
it('newTask should be initialized properly', function() {
expect(tasksComponent.newTask.name).to.be.eql('');
expect(tasksComponent.newTask.date).to.be.eql('');
});
为了使这个测试通过,我们需要在组件的构造函数中进行简单修改。打开
public/src/app/tasks/tasks.component.js
文件,添加以下代码:
this.newTask = {name: '', date: ''};
-
验证无数据的
newTask转换为 JSON :在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
it('should properly convert newTask with no data to JSON', function() {
var newTask = tasksComponent.convertNewTaskToJSON();
expect(newTask.name).to.be.eql('');
expect(newTask.month).to.be.NAN;
expect(newTask.day).to.be.NAN;
expect(newTask.year).to.be.NAN;
});
-
验证有数据的
newTask转换为 JSON :在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
it('should properly convert newTask with data to JSON', function() {
var newTask = {name: 'task a', date: '6/10/2016'};
var newTaskJSON = {name: 'task a', month: 6, day: 10, year: 2016};
tasksComponent.newTask = newTask;
expect(tasksComponent.convertNewTaskToJSON()).to.be.eql(newTaskJSON);
});
实现
convertNewTaskToJSON
函数,满足上述两个测试:
convertNewTaskToJSON: function() {
var dateParts = this.newTask.date.split('/');
return {
name: this.newTask.name,
month: parseInt(dateParts[0]),
day: parseInt(dateParts[1]),
year: parseInt(dateParts[2])
};
},
-
验证
addTask与服务的交互 :在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
it('addTask should register handlers with service', function() {
var observableMock =
sandbox.mock(observable)
.expects('subscribe')
.withArgs(updateMessageBindStub, updateErrorBindStub);
var taskStub = {};
tasksComponent.convertNewTaskToJSON = function() { return taskStub; };
sandbox.stub(tasksService, 'add')
.withArgs(taskStub)
.returns(observable);
tasksComponent.addTask();
observableMock.verify();
});
为了使这个测试通过,我们需要在测试套件中进行两处修改:
var updateMessageBindStub = function() {};
sandbox.stub(tasksComponent.updateMessage, 'bind')
.withArgs(tasksComponent)
.returns(updateMessageBindStub);
在
public/src/app/tasks/tasks.component.js
文件中实现
addTask
函数:
addTask: function() {
this.service.add(this.convertNewTaskToJSON())
.subscribe(this.updateMessage.bind(this),
this.updateError.bind(this));
},
updateMessage: function() {},
-
设计
updateMessage函数 :在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
it('updateMessage should update message and call getTasks', function(done) {
tasksComponent.getTasks = function() { done(); };
tasksComponent.updateMessage('good');
expect(tasksComponent.message).to.be.eql('good');
});
修改
public/src/app/tasks/tasks.component.js
文件中的
updateMessage
函数:
updateMessage: function(message) {
this.message = message;
this.getTasks();
},
-
实现
disableAddTask函数 :-
验证
validateTask属性:在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
-
验证
it('should set validateTask to common function', function() {
expect(tasksComponent.validateTask).to.be.eql(validateTask);
});
为了使这个测试通过,我们需要在组件的构造函数中添加以下属性:
this.validateTask = validateTask;
同时,记得修改
karma.conf.js
文件的
files
部分,包含对
./public/javascripts/common/validate-task.js
文件的引用。
- 验证
disableAddTask
函数:在
test/client/app/tasks/tasks.component-test.js
文件中添加以下测试代码:
it('disableAddTask should use validateTask', function() {
tasksComponent.newTask = {name: 'task a', date: '6/10/2016'};
var validateTaskSpy = sinon.spy(tasksComponent, 'validateTask');
expect(tasksComponent.disableAddTask()).to.be.false;
expect(validateTaskSpy).to.have.been.calledWith(
tasksComponent.convertNewTaskToJSON());
});
在
public/src/app/tasks/tasks.component.js
文件中实现
disableAddTask
函数:
disableAddTask: function() {
return !this.validateTask(this.convertNewTaskToJSON());
},
服务的演进
服务中需要的新
add
函数应该向后端服务器发出 HTTP POST 请求,发送的数据必须是 JSON 格式。我们可以参考示例来了解该函数如何通过 HTTP 对象与后端进行交互。
1.
修改测试文件
:打开
test/client/app/tasks/tasks.service-test.js
文件,在
beforeEach
函数中为
http
存根添加一个新的
post
属性:
beforeEach(function() {
sandbox = sinon.sandbox.create();
http = {
get: function() {},
post: function() {}
};
tasksService = new app.TasksService(http);
//...
-
编写
add函数测试 :在test/client/app/tasks/tasks.service-test.js文件中添加以下测试代码:
it('add should pass task to /tasks using POST', function() {
var taskStub = {name: 'foo', month: 1, day: 1, year: 2017};
var options =
{headers: new ng.http.Headers({'Content-Type': 'application/json'})};
sandbox.stub(http, 'post')
.withArgs('/tasks', JSON.stringify(taskStub), options)
.returns(observable);
expect(tasksService.add(taskStub)).to.be.eql(observable);
expect(observable.map.calledWith(tasksService.extractData)).to.be.true;
expect(observable.catch.calledWith(tasksService.returnError)).to.be.true;
});
-
实现
add函数 :在public/src/app/tasks/tasks.service.js文件中添加以下代码:
add: function(task) {
var options =
{headers: new ng.http.Headers({'Content-Type': 'application/json'})};
return this.http.post('/tasks', JSON.stringify(task), options)
.map(this.extractData)
.catch(this.returnError);
},
-
修改
extractData函数 :由于后端在添加任务请求时返回纯文本,我们需要修改extractData函数以处理 JSON 响应或纯文本响应。在test/client/app/tasks/tasks.service-test.js文件中添加以下测试代码:
it('extractData should return text if not json()', function() {
var fakeBody = 'somebody';
var response = {status: 200, text: function() { return fakeBody; } };
expect(tasksService.extractData(response)).to.be.eql(fakeBody);
});
修改
public/src/app/tasks/tasks.service.js
文件中的
extractData
函数:
extractData: function(response) {
if(response.status !== 200)
throw new Error('Request failed with status: ' + response.status);
try {
return response.json();
} catch(ex) {
return response.text();
}
},
查看添加任务功能
在通过 UI 添加新任务之前,我们需要修改
tasks.component.html
文件,添加输入字段让用户输入新任务的详细信息。修改后的文件如下:
<body>
<div class="heading">TO-DO</div>
<div id="newtask">
<div>Create a new task</div>
<label>Name</label>
<input type="text" id="name" [(ngModel)]="newTask.name"/>
<label>Date</label>
<input type="text" id="date" [(ngModel)]="newTask.date"/>
<input type="submit" id="submit" (click)="addTask();"
[disabled]="disableAddTask()" value="create"/>
</div>
<div id="taskslist">
<p>Number of tasks: <span id="length">{{ tasks.length }}</span>
<span id="message">{{ message }}</span>
<table>
<tr *ngFor ="let task of tasks">
<td>{{ task.name }}</td>
<td>{{ task.month }}/{{ task.day }}/{{ task.year }}</td>
</table>
</div>
</body>
现在,我们可以启动数据库守护进程,运行
npm start
启动 Express,然后在浏览器中访问
http://localhost:3000
来测试添加任务功能。
设计删除任务功能
要删除现有任务,我们需要再次对组件和服务进行改进,并对
tasks.component.html
文件进行小修改。
组件的再次演进
组件需要一个新函数
deleteTask
,它将删除任务的请求传递给服务。这个新函数可以重用我们在组件中已经实现的响应处理程序。
1.
验证服务存根
:在
test/client/app/tasks/tasks.component-test.js
文件中,确保服务存根包含
delete
函数:
beforeEach(function() {
tasksService = {
get: function() {},
add: function() {},
delete: function() {}
};
tasksComponent = new app.TasksComponent(tasksService, sortPipe);
//...
-
编写
deleteTask测试 :在test/client/app/tasks/tasks.component-test.js文件中添加以下测试代码:
it('deleteTask should register handlers with service', function() {
var sampleTaskId = '1234123412341234';
var observableMock =
sandbox.mock(observable)
.expects('subscribe')
.withArgs(updateMessageBindStub, updateErrorBindStub);
sandbox.stub(tasksService, 'delete')
.withArgs(sampleTaskId)
.returns(observable);
tasksComponent.deleteTask(sampleTaskId);
observableMock.verify();
});
-
实现
deleteTask函数 :在public/src/app/tasks/tasks.component.js文件中添加以下代码:
deleteTask: function(taskId) {
this.service.delete(taskId)
.subscribe(this.updateMessage.bind(this),
this.updateError.bind(this));
},
服务的再次演进
服务现在需要一个额外的函数
delete
。
1.
修改测试文件
:打开
test/client/app/tasks/tasks.service-test.js
文件,在
beforeEach
函数中为
http
存根添加一个新的
delete
函数:
beforeEach(function() {
sandbox = sinon.sandbox.create();
http = {
get: function() {},
post: function() {},
delete: function() {}
};
tasksService = new app.TasksService(http);
//...
-
编写
delete函数测试 :在test/client/app/tasks/tasks.service-test.js文件中添加以下测试代码:
it('delete should pass task to /tasks using DELETE', function() {
var taskId = '1234';
sandbox.stub(http, 'delete')
.withArgs('/tasks/' + taskId)
.returns(observable);
expect(tasksService.delete(taskId)).to.be.eql(observable);
expect(observable.map.calledWith(tasksService.extractData)).to.be.true;
expect(observable.catch.calledWith(tasksService.returnError)).to.be.true;
});
-
实现
delete函数 :在public/src/app/tasks/tasks.service.js文件中添加以下代码:
delete: function(taskId) {
return this.http.delete('/tasks/' + taskId)
.map(this.extractData)
.catch(this.returnError);
},
查看删除任务功能
修改
tasks.component.html
文件,添加删除任务的链接:
<body>
<div class="heading">TO-DO</div>
<div id="newtask">
<div>Create a new task</div>
<label>Name</label>
<input type="text" id="name" [(ngModel)]="newTask.name"/>
<label>Date</label>
<input type="text" id="date" [(ngModel)]="newTask.date"/>
<input type="submit" id="submit" (click)="addTask();"
[disabled]="disableAddTask()" value="create"/>
</div>
<div id="taskslist">
<p>Number of tasks: <span id="length">{{ tasks.length }}</span>
<span id="message">{{ message }}</span>
<table>
<tr *ngFor ="let task of tasks">
<td>{{ task.name }}</td>
<td>{{ task.month }}/{{ task.day }}/{{ task.year }}</td>
<td>
<A (click)="deleteTask(task._id);">delete</A>
</td>
</table>
</div>
</body>
再次启动数据库守护进程,运行
npm start
启动 Express,然后在浏览器中访问
http://localhost:3000
来测试删除任务功能。
测量代码覆盖率
当前项目的
package.json
文件已经包含了运行 Istanbul 进行客户端代码覆盖率测试的脚本。在
karma.conf.js
文件中,
preprocessors
部分已经引用了
public/src
及其子目录下的所有文件进行覆盖率检测。
运行以下命令来同时运行测试并测量覆盖率:
npm run-script cover-client
测试运行完成后,打开
coverage
目录下的
index.html
文件查看覆盖率报告,你会看到 100% 的覆盖率。
通过以上步骤,我们成功开发了一个功能齐全的 Angular 前端待办事项应用,并且通过测试驱动开发保证了代码的质量和覆盖率。
实现待办事项应用的增删功能:Angular 实战
技术点分析
在整个开发过程中,涉及到多个重要的技术点,下面我们来详细分析。
组件与服务的交互
组件和服务是 Angular 应用中的核心概念,它们之间的交互是实现功能的关键。在添加和删除任务的功能中,组件负责接收用户的输入和操作,然后调用服务中的方法与后端进行通信。例如,
addTask
函数在组件中被调用,它将转换后的任务对象传递给服务的
add
方法,服务再通过 HTTP 请求将数据发送到后端。这种分层设计使得代码的职责更加清晰,便于维护和扩展。
| 功能 | 组件操作 | 服务操作 |
|---|---|---|
| 添加任务 |
转换任务对象为 JSON,调用服务的
add
方法
| 发送 HTTP POST 请求到后端 |
| 删除任务 |
调用服务的
delete
方法
| 发送 HTTP DELETE 请求到后端 |
测试驱动开发(TDD)
测试驱动开发是一种重要的开发方法,它强调在编写代码之前先编写测试用例。在我们的开发过程中,每一个功能都有对应的测试用例,通过测试用例来验证代码的正确性。例如,在实现
convertNewTaskToJSON
函数时,我们先编写了两个测试用例,分别验证无数据和有数据的
newTask
转换为 JSON 的情况,然后再实现函数代码,确保函数能够通过这两个测试用例。这种开发方式可以提高代码的质量,减少 bug 的产生。
以下是一个简单的 TDD 流程:
1. 编写测试用例
2. 运行测试,测试失败
3. 编写代码使测试通过
4. 重构代码,优化代码结构
graph LR
A[编写测试用例] --> B[运行测试,测试失败]
B --> C[编写代码使测试通过]
C --> D[重构代码,优化代码结构]
HTTP 请求处理
在服务中,我们使用 HTTP 请求与后端进行通信。对于添加任务,使用 HTTP POST 请求;对于删除任务,使用 HTTP DELETE 请求。同时,我们还需要处理请求的响应,例如在
extractData
函数中,需要处理 JSON 响应和纯文本响应。以下是处理 HTTP 请求的关键代码:
// 添加任务
add: function(task) {
var options =
{headers: new ng.http.Headers({'Content-Type': 'application/json'})};
return this.http.post('/tasks', JSON.stringify(task), options)
.map(this.extractData)
.catch(this.returnError);
},
// 删除任务
delete: function(taskId) {
return this.http.delete('/tasks/' + taskId)
.map(this.extractData)
.catch(this.returnError);
},
// 处理响应数据
extractData: function(response) {
if(response.status !== 200)
throw new Error('Request failed with status: ' + response.status);
try {
return response.json();
} catch(ex) {
return response.text();
}
},
总结与展望
通过本次开发,我们成功实现了待办事项应用的添加和删除任务功能,并且采用了测试驱动开发的方法,保证了代码的质量和覆盖率。整个开发过程中,我们深入理解了 Angular 组件和服务的使用,以及 HTTP 请求的处理。
在未来的开发中,我们可以进一步扩展这个应用。例如,添加任务的编辑功能,允许用户修改已有的任务信息;添加任务的排序和筛选功能,方便用户查找特定的任务;还可以优化 UI 界面,提高用户体验。同时,我们可以引入更多的测试框架和工具,进一步完善测试体系,确保代码的稳定性和可靠性。
总之,Angular 是一个强大的前端框架,通过不断学习和实践,我们可以开发出更加复杂和功能丰富的应用。希望本文能够对大家在 Angular 开发方面有所帮助。
超级会员免费看
10

被折叠的 条评论
为什么被折叠?



