基本的功能完成后,本节我们使用`ng s`进行集成测试,在启动测试时将报一个错误: ```bash Error: src/app/student/edit/edit.component.ts:46:32 - error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'number'. Type 'undefined' is not assignable to type 'number'. 46 this.studentService.update(id, { ~~ ``` `ng s`的启动的时候检查出来一个错误,这个错误是在说:我们在组件初始化的时候将`id`的类型定义成了`number | undefined`,而最终这个`id`却做为参数传给`this.studentService.update`。由于`studentService.update`方法中接收的第一参数类型被规定为`number`。所以最后实质上相当于把`'number | undefined'`类型赋值给了`number`类型,而这是不允许的。 为此我们应当如下对`onSubmit`方法进行改进: ```typescript +++ b/first-app/src/app/student/edit/edit.component.ts @@ -39,11 +39,12 @@ export class EditComponent implements OnInit { * @param id id * @param formGroup 表单组 */ - onSubmit(id: number, formGroup: FormGroup): void { + onSubmit(id: number | undefined①, formGroup: FormGroup): void { const formValue = formGroup.value as { name: string, phone: string, email: string, clazzId: number }; Assert.isString(formValue.name, formValue.phone, formValue.email, '类型必须为字符串'); Assert.isNumber(formValue.clazzId, '类型必须为number'); - this.studentService.update(id, { + Assert.isNumber(id, 'id类型必须为number'); ② + this.studentService.update(id as number③, { name: formValue.name, email: formValue.email, phone: formValue.phone, ``` ①处对类型进行统一,既然初始化的时候是`number | undefined`,那么我们这保持不变;③`studentService.update`既然只接收`number`,那么在传值前我们可以使用`as`关键字来做类型指定;在类型指定前为了保证这样的指定是没有风险的,在进行指定前对`id`进行断言。 ## 增加路由 在`student`路由文件中增加编辑组件路由: ```typescript +++ b/first-app/src/app/student/student-routing.module.ts @@ -2,6 +2,7 @@ import {NgModule} from '@angular/core'; import {Route, RouterModule} from '@angular/router'; import {StudentComponent} from './student.component'; import {AddComponent} from './add/add.component'; +import {EditComponent} from './edit/edit.component'; const routes = [ { @@ -11,6 +12,9 @@ const routes = [ { path: 'add', component: AddComponent + }, { + path: 'edit/:id', + component: EditComponent } ] as Route[]; ``` 此时点击编辑按钮后将进行正常的跳转,正常显示组件。 ![image-20210611142847709](https://img.kancloud.cn/44/ad/44ada7b2f84cc099b6db45f1dec98bd4_2018x760.png) 编辑的学生内容虽然可以正常显示了,但保存按钮却是disabled状态。这里由于我们在编辑组件的学号字段上加入了`numberNotExist`验证器的原因: ```typescript this.formGroup = new FormGroup({ name: new FormControl('', Validators.required), number: new FormControl('', Validators.required, yzAsyncValidators.numberNotExist() 👈), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) }); ``` 在进行学生编辑组件开发时,我们直接复制了新增学生的代码,在复制代码的同时其验证逻辑也跟着复制了过来。由于学生编辑组件并不涉及到学号的更新,所以学号上原本就不应该设置验证器。 在设置`numberNotExist`情况下,后台会以当前学号做为条件查询后台是否存在有当前学号的记录。这当然是存在的,该记录就是当前正在编辑的学生。 删除学号上的验证器后保存按钮生效: ```typescript +++ b/first-app/src/app/student/edit/edit.component.ts @@ -21,7 +21,7 @@ export class EditComponent implements OnInit { console.log(this.activatedRoute); this.formGroup = new FormGroup({ name: new FormControl('', Validators.required), - number: new FormControl('', Validators.required, yzAsyncValidators.numberNotExist()), + number: new FormControl(''), phone: new FormControl('', YzValidators.phone), email: new FormControl(), clazzId: new FormControl(null, Validators.required) ``` ![image-20210611144024063](https://img.kancloud.cn/be/02/be0220d259253fd2d08e6123ba6627e6_2152x764.png) ## 增加跳转 最后增加保存成功后的跳转: ```typescript +++ b/first-app/src/app/student/edit/edit.component.ts @@ -2,7 +2,7 @@ import {Component, OnInit} from '@angular/core'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {YzValidators} from '../../yz-validators'; import {YzAsyncValidators} from '../../yz-async-validators'; -import {ActivatedRoute} from '@angular/router'; +import {ActivatedRoute, Router} from '@angular/router'; import {StudentService} from '../../service/student.service'; import {Assert} from '@yunzhi/ng-mock-api'; @@ -17,7 +17,8 @@ export class EditComponent implements OnInit { constructor(private yzAsyncValidators: YzAsyncValidators, private activatedRoute: ActivatedRoute, - private studentService: StudentService) { + private studentService: StudentService, + private router: Router) { console.log(this.activatedRoute); this.formGroup = new FormGroup({ name: new FormControl('', Validators.required), @@ -50,7 +51,7 @@ export class EditComponent implements OnInit { phone: formValue.phone, clazz: {id: formValue.clazzId} }).subscribe(() => { - console.log('更新成功'); + this.router.navigate(['../../'], {relativeTo: this.activatedRoute}); }); } ``` 集成测试完成。 ## 参数快照 当前我们在获取路由中的参数ID时,使用的由`snapshot`上读取属性的方法。`snapshot`的中文译为快照,其中的照可以理解为照片。 为了把它称为`snapshot`呢?是由于该数据一旦生成就不会改变,这就像照片一样,一旦被拍了出来照片上的情景便会被留存下来。 为了更好的理解它,我们对当前模块中的路由做以下改动: ```typescript +++ b/first-app/src/app/student/student-routing.module.ts @@ -7,14 +7,16 @@ import {EditComponent} from './edit/edit.component'; const routes = [ { path: '', - component: StudentComponent - }, - { - path: 'add', - component: AddComponent - }, { - path: 'edit/:id', - component: EditComponent + component: StudentComponent, + children: [ + { + path: 'add', + component: AddComponent + }, { + path: 'edit/:id', + component: EditComponent + } + ] } ] as Route[]; ``` 即将添加组件、编辑组件放到列表组件的children中。然后对列表组件的V层中增加一个路由出口: ```html +++ b/first-app/src/app/student/student.component.html @@ -1,3 +1,4 @@ +<router-outlet></router-outlet> <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> ``` 此时当我们在学生列表中点击编辑按钮时,将得到如下界面: ![image-20210611151447615](https://img.kancloud.cn/d2/e5/d2e551f02fc9be0038bd2fc6f36b603f_2162x1342.png) 貌似一切都再正常不过,但如果此时我们再次添加其它学生对应的编辑按钮,则会发现除了浏览器`url`产生了变化以外,其它一切都没有变化。 ![image-20210611151622126](https://img.kancloud.cn/8f/75/8f756ff34d6f8d6d7fd1250803ee724a_2154x312.png) 比如点击第一个编辑按钮时,浏览器的地址为:`http://localhost:4200/student/edit/1`,当击第二个编辑按钮时,浏览器的地址为更为:`http://localhost:4200/student/edit/2`。除此以外,编辑组件显示的学生并没有任何变化。 这是由于Angular为了性能提升,在地址切换时,将按以下规律来选择是否构造新组件: 当浏览器地址由`http://localhost:4200/student/edit/1`变更为`http://localhost:4200/student/edit/2`时,Angular发现变更前后的两个地址对应同一个路由,该路由指定了学生编辑组件,则Angular将选择复用的以前的组件。 也就是说浏览器的地址虽然变更了,但组件还是那个组件,并没有发生任何变化。 而获取编辑的学生信息的代码位于学生编辑组件的`ngOnInit()`方法中,该方法仅会在组件实例化的时候被调用1次。所以上述事件的整个过程大体如下: ➊ 浏览器地址为`http://localhost:4200/student`时,学生列表组件不存在,则初始化学生列表组件。该组件中的`ngOnInit`方法被自动调用1次。 ➋ 第1次点击编辑按钮时,浏览器地址由`http://localhost:4200/student`跳转至`http://localhost:4200/student/edit/1`,学生编辑组件不存在,则初始化学生编辑组件。该组件中的`ngOnInit`方法被自动调用1次。 ➌ 第2次点击编辑按钮时,浏览器地址由`http://localhost:4200/student/edit/1`变更为`http://localhost:4200/student/edit/2`,学生编辑组件已存在,什么也不做。 当学习一个伟大的框架时,尽量不要怀疑框架的思想。比如我们前面遇到了问题,首先想的是应该如何改变自己的思想,让自己的思想与Angular相一致。在这种情况下,再去思索解决问题的方法。 Angular的思想是:如果我们想在路由改变时进行一些操作,则应该去订阅路由信息。因为一旦我们订阅了路由信息,路由发生变化时则会主通地通知我们。 下节中我们将使用**可订阅的路由参数**来取替**路由参数快照**来解决当前问题。 ## 资源链接 | 链接 | 名称 | | ------------------------------------------------------------ | -------- | | [https://github.com/mengyunzhi/angular11-guild/archive/step7.7.4.zip](https://github.com/mengyunzhi/angular11-guild/archive/step7.7.4.zip) | 本节源码 |