本节我们尝试一次删除多个学生。
## 原型
在原型的,则一定要先开发原型!是新手我们应该这样,是老手更应该这样。
若要一次删除多个学生,则需要为每条记录前加一个checkbox选择框。
```html
+++ b/first-app/src/app/student/student.component.html
@@ -7,6 +7,7 @@
<table class="table table-striped mt-2">
<thead>
<tr class="table-primary">
+ <th>选择</th>
<th>序号</th>
<th>姓名</th>
<th>学号</th>
@@ -18,6 +19,7 @@
</thead>
<tbody>
<tr *ngFor="let student of pageData.content; index as index">
+ <td><input type="checkbox"></td>
<td>{{index + 1}}</td>
<td>{{student.name}}</td>
<td>{{student.number}}</td>
```
![image-20210607155806991](https://img.kancloud.cn/ba/17/ba170e1455c5f7756f273d955c9eb270_1146x320.png)
接着再增加一个删除全部的按钮:
```html
+++ b/first-app/src/app/student/student.component.html
@@ -1,6 +1,7 @@
<div class="row">
<div class="col-12 text-right">
<a class="btn btn-primary mr-2" routerLink="./add"><i class="fas fa-plus"></i>新增</a>
+ <button class="btn btn-danger mr-2" type="button"><i class="fas fa-trash-alt"></i>删除</button>
</div>
</div>
```
![image-20210607160000952](https://img.kancloud.cn/79/45/7945392870548007761bb6682b1ed7e6_798x160.png)
## 功能
实现功能的方式有很多种,我们在此使用一个比较容易理解的。
- ①首先在C层中增加一个数组,用于存待删除学生的**索引值**。注意,这里我们未使用`ID`,因为通过索引值能够轻松的找到`ID`。
- ②某个`checkbox`并点击时,将索引值传给C层。C层根据索引在数组中查找,如果查到了,就将其由数组中删除;如果没有找到,就将其添加到数组中。
- ③点击删除按钮时,如果待删除的数组为空,则弹出提示框;如果数组不为空,则按索引值依次取出待删除的ID,并将其传入M层。
### ①初始化数组
```typescript
+++ b/first-app/src/app/student/student.component.ts
@@ -15,6 +15,8 @@ export class StudentComponent implements OnInit {
page = 0;
size = environment.size;
+ beDeletedIndexes = new Array<number>();
+
constructor(private studentService: StudentService) {
}
```
### ②传值
```html
+++ b/first-app/src/app/student/student.component.html
@@ -20,7 +20,7 @@
</thead>
<tbody>
<tr *ngFor="let student of pageData.content; index as index">
- <td><input type="checkbox"></td>
+ <td><input type="checkbox" (click)="onCheckboxClick(index)"></td>
<td>{{index + 1}}</td>
<td>{{student.name}}</td>
<td>{{student.number}}</td>
```
C层:
```typescript
/**
* checkbox被点击
* @param index 索引值
*/
onCheckboxClick(index: number): void {
if (this.beDeletedIndexes.indexOf(index) === -1) {
this.beDeletedIndexes.push(index);
} else {
this.beDeletedIndexes = this.beDeletedIndexes.filter(i => i !== index);
}
}
```
`filter()`是数组的一个内置方法,它的作用是将数组中的数据依次进行过滤,保留符合条件的,移除不符合条件的,比如:
![image-20210607162309892](https://img.kancloud.cn/e5/c7/e5c7052a94407b6bc37a335790010898_968x178.png)
上述方法的作用时将数据中的不等3的数据保留。
### ③点击删除按钮
```html
- <button class="btn btn-danger mr-2" type="button"><i class="fas fa-trash-alt"></i>删除</button>
+ <button class="btn btn-danger mr-2" type="button" (click)="onBatchDeleteClick()"><i class="fas fa-trash-alt"></i>删除</button>
</div>
```
C层方法:
```typescript
+ /**
+ * 批量删除按钮被点击
+ */
+ onBatchDeleteClick(): void {
+ if (this.beDeletedIndexes.length === 0) {
+ Report.warning('出错啦', '请先选择要删除的学生', '返回');
+ }
+ }
}
```
- Report是notiflix提供的另一个方法,详情请自行查阅官方文档。
![image-20210607163337933](https://img.kancloud.cn/8f/ef/8fef3e1ed931effdb2d93f77c60bfb42_1552x514.png)
完成功能:
```typescript
+++ b/first-app/src/app/student/student.component.ts
onBatchDeleteClick(): void {
if (this.beDeletedIndexes.length === 0) {
Report.warning('出错啦', '请先选择要删除的学生', '返回');
+ } else {
+ Confirm.show('请确认', '该操作不可逆', '确认', '取消',
+ () => {
+ // 根据index获取ids
+ const ids = [] as number[];
+ this.beDeletedIndexes.forEach(index => {
+ ids.push(this.pageData.content[index].id);
+ });
+ // 调用批量删除
+ this.studentService.batchDelete(ids)
+ .subscribe(() => {
+ this.beDeletedIndexes = [];
+ this.loadData(this.page);
+ });
+ });
}
}
}
```
最后,在M层初始化个方法,在方法中直接返回供开发用的可订阅对象:
```typescript
+++ b/first-app/src/app/service/student.service.ts
@@ -47,4 +47,12 @@ export class StudentService {
.append('size', size.toString());
return this.httpClient.get<Page<Student>>('/student/pageOfCurrentTeacher', {params: httpParams});
}
+
+ /**
+ * 批量删除
+ * @param ids 学生ID数组
+ */
+ batchDelete(ids: number[]): Observable<void> {
+ return of(undefined);
+ }
}
```
此时,点击删除按钮时将弹出确认对话框,点击确认后将触发M层的方法。并在删除成功后重新向后台发起请求,获取最新的分页数据。
![image-20210607154235559](https://img.kancloud.cn/3e/50/3e50225c687cfed9d4796bbffc74171c_908x338.png)
## M层
M层负责与API交互,批量删除API如下:
```bash
DELETE /student/batchDeleteIds
```
接收的请求参数为:`ids`,类型为数组。
根据上述信息建立MockApi如下:
```typescript
+++ b/first-app/src/app/mock-api/student.mock.api.ts
@@ -70,6 +70,14 @@ export class StudentMockApi implements MockApiInterface {
}, {
method: 'DELETE',
url: '/student/(\\d+)'
+ }, {
+ method: 'DELETE',
+ url: '/student/batchDeleteIds',
+ result: ((urlMatches: any, options: RequestOptions) => {
+ const httpParams = options.params as HttpParams;
+ const ids = httpParams.getAll('ids');
+ Assert.isArray(ids, '未接收到ids');
+ })
}
];
}
```
单元测试:
```typescript
+ fit('batchDeleteIds', () => {
+ const ids = [1, 2, 3];
+ let called = false;
+ service.batchDelete(ids)
+ .subscribe(() => {
+ called = true;
+ });
+ expect(called).toBeFalse();
+ getTestScheduler().flush();
+ expect(called).toBeTrue();
+ });
+
```
完成方法:
```typescript
+++ b/first-app/src/app/service/student.service.ts
@@ -53,6 +53,7 @@ export class StudentService {
* @param ids 学生ID数组
*/
batchDelete(ids: number[]): Observable<void> {
- return of(undefined);
+ const stringIds = ids.map(id => id.toString());
+ return this.httpClient.delete<void>('/student/batchDeleteIds', {params: {ids: stringIds}});
}
}
```
继`filter()`、`sort()`方法后,我们刚刚又使用了`map()`方法对数组中的各项进行了转换。
![image-20210607170131502](https://img.kancloud.cn/31/44/31449720f3e2b2dbaeb22e1a13a1a9aa_902x262.png)
单元测试通过,说明方法请求正确。
## 本节作业
Array中除`filter`、`map`外,还存在常用的`sort`等方法,请参阅官方文档进一步学习。
| 链接 | 名称 |
| ------------------------------------------------------------ | -------- |
| [https://github.com/mengyunzhi/angular11-guild/archive/step7.5.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.5.2.zip) | 本节源码 |
| [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) | map() |
| [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) | sort() |
| [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) | filter() |
| [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) | Array |
- 序言
- 第一章 Hello World
- 1.1 环境安装
- 1.2 Hello Angular
- 1.3 Hello World!
- 第二章 教师管理
- 2.1 教师列表
- 2.1.1 初始化原型
- 2.1.2 组件生命周期之初始化
- 2.1.3 ngFor
- 2.1.4 ngIf、ngTemplate
- 2.1.5 引用 Bootstrap
- 2.2 请求后台数据
- 2.2.1 HttpClient
- 2.2.2 请求数据
- 2.2.3 模块与依赖注入
- 2.2.4 异步与回调函数
- 2.2.5 集成测试
- 2.2.6 本章小节
- 2.3 新增教师
- 2.3.1 组件初始化
- 2.3.2 [(ngModel)]
- 2.3.3 对接后台
- 2.3.4 路由
- 2.4 编辑教师
- 2.4.1 组件初始化
- 2.4.2 获取路由参数
- 2.4.3 插值与模板表达式
- 2.4.4 初识泛型
- 2.4.5 更新教师
- 2.4.6 测试中的路由
- 2.5 删除教师
- 2.6 收尾工作
- 2.6.1 RouterLink
- 2.6.2 fontawesome图标库
- 2.6.3 firefox
- 2.7 总结
- 第三章 用户登录
- 3.1 初识单元测试
- 3.2 http概述
- 3.3 Basic access authentication
- 3.4 着陆组件
- 3.5 @Output
- 3.6 TypeScript 类
- 3.7 浏览器缓存
- 3.8 总结
- 第四章 个人中心
- 4.1 原型
- 4.2 管道
- 4.3 对接后台
- 4.4 x-auth-token认证
- 4.5 拦截器
- 4.6 小结
- 第五章 系统菜单
- 5.1 延迟及测试
- 5.2 手动创建组件
- 5.3 隐藏测试信息
- 5.4 规划路由
- 5.5 定义菜单
- 5.6 注销
- 5.7 小结
- 第六章 班级管理
- 6.1 新增班级
- 6.1.1 组件初始化
- 6.1.2 MockApi 新建班级
- 6.1.3 ApiInterceptor
- 6.1.4 数据验证
- 6.1.5 教师选择列表
- 6.1.6 MockApi 教师列表
- 6.1.7 代码重构
- 6.1.8 小结
- 6.2 教师列表组件
- 6.2.1 初始化
- 6.2.2 响应式表单
- 6.2.3 getTestScheduler()
- 6.2.4 应用组件
- 6.2.5 小结
- 6.3 班级列表
- 6.3.1 原型设计
- 6.3.2 初始化分页
- 6.3.3 MockApi
- 6.3.4 静态分页
- 6.3.5 动态分页
- 6.3.6 @Input()
- 6.4 编辑班级
- 6.4.1 测试模块
- 6.4.2 响应式表单验证
- 6.4.3 @Input()
- 6.4.4 FormGroup
- 6.4.5 自定义FormControl
- 6.4.6 代码重构
- 6.4.7 小结
- 6.5 删除班级
- 6.6 集成测试
- 6.6.1 惰性加载
- 6.6.2 API拦截器
- 6.6.3 路由与跳转
- 6.6.4 ngStyle
- 6.7 初识Service
- 6.7.1 catchError
- 6.7.2 单例服务
- 6.7.3 单元测试
- 6.8 小结
- 第七章 学生管理
- 7.1 班级列表组件
- 7.2 新增学生
- 7.2.1 exports
- 7.2.2 自定义验证器
- 7.2.3 异步验证器
- 7.2.4 再识DI
- 7.2.5 属性型指令
- 7.2.6 完成功能
- 7.2.7 小结
- 7.3 单元测试进阶
- 7.4 学生列表
- 7.4.1 JSON对象与对象
- 7.4.2 单元测试
- 7.4.3 分页模块
- 7.4.4 子组件测试
- 7.4.5 重构分页
- 7.5 删除学生
- 7.5.1 第三方dialog
- 7.5.2 批量删除
- 7.5.3 面向对象
- 7.6 集成测试
- 7.7 编辑学生
- 7.7.1 初始化
- 7.7.2 自定义provider
- 7.7.3 更新学生
- 7.7.4 集成测试
- 7.7.5 可订阅的路由参数
- 7.7.6 小结
- 7.8 总结
- 第八章 其它
- 8.1 打包构建
- 8.2 发布部署
- 第九章 总结