本节来完成编辑班级中的教师列表部分,编辑教师时,应当自动选中该班级对应的教师。至于应该选中哪个教师,则应该按教师`id` 进行对应。 为此,我们需要简单改造一下MockApi。使教师列表的返回ID在1-10之间: ```typescript +++ b/first-app/src/app/mock-api/teacher.mock.api.ts @@ -1,4 +1,5 @@ import {ApiInjector, MockApiInterface, randomNumber} from '@yunzhi/ng-mock-api'; +import {randomString} from '@yunzhi/ng-mock-api/testing'; /** * 教师模拟API @@ -9,16 +10,16 @@ export class TeacherMockApi implements MockApiInterface { // 获取所有教师 method: 'GET', url: 'teacher', - result: [ - { - id: randomNumber(), - name: '教师姓名1' - }, - { - id: randomNumber(), - name: '教师姓名2' + result: () => { + const teachers = []; + for (let i = 1; i <= 10; i++) { + teachers.push({ + id: i, + name: randomString('教师姓名') + }); } - ] + return teachers; + } }]; } } ``` 然后获取某个班级时对应的教师ID为1至10之间的任意值: ```typescript +++ b/first-app/src/app/mock-api/clazz.mock.api.ts @@ -87,7 +87,7 @@ export class ClazzMockApi implements MockApiInterface { id, name: randomString('班级名称'), teacher: { - id: randomNumber(), + id: randomNumber(9) + 1, name: randomString('教师') } } as Clazz; ``` ## @Input() 为教师选择组件增加一个`@Input()`,至于该`@Input()`是注解到属性还是`set`方法上,则当前应该看具体的场景: 1. 如果考虑父组件对应子组件上输入数值的变化,则应该注解到`set`方法上。 2. 如果不考虑父组件上的数值变化对子组件的影响,则应该注解到属性上。 对于当前情景而言,父组件对子组件设置的教师id的值是通过请求后台数据后获取到的。执行过程大体如下: 1. 父组件初始化 2. 父组件请求后台班级数据,此时班级数据为空,班级对应的教师数据当然也为空。 3. 子组件初始化,绑定`@Input`. 4. 父组件的请求得到了响应,此时获取了班级数据,获取到了班级对应的教师数据,需要重新对子组件`@Input()` 进行设置。 所以教师选择组件更适合于将`@Input()`注解到`set`方法上: ```typescript +++ b/first-app/src/app/clazz/klass-select/klass-select.component.ts + @Input() + set id(id: number) { + // 使用接收到的id设置teacherId + this.teacherId.setValue(id); + } + ``` ## 测试 我们仍在其父班级编辑组件中对子组件进行测试。为此,需要在父组件中传入这个`id`属性: ```typescript +++ b/first-app/src/app/clazz/edit/edit.component.html @@ -12,7 +12,7 @@ <div class="mb-3 row"> <label class="col-sm-2 col-form-label">班主任</label> <div class="col-sm-10"> - <app-klass-select></app-klass-select> + <app-klass-select [id]="teacherId"></app-klass-select> <small class="text-danger"> 必须指定一个班主任 </small> ``` C层中增加`teacherId`,初始化并赋值: ```typescript +++ b/first-app/src/app/clazz/edit/edit.component.ts @@ -14,6 +14,7 @@ export class EditComponent implements OnInit { * 班级名称. */ nameFormControl = new FormControl('', Validators.required); + teacherId: number | undefined; constructor(private activatedRoute: ActivatedRoute, private httpClient: HttpClient) { @@ -34,6 +35,7 @@ export class EditComponent implements OnInit { .subscribe(clazz => { console.log('接收到了clazz', clazz); this.nameFormControl.patchValue(clazz.name); + this.teacherId = clazz.teacher.id; }, error => console.log(error)); } ``` 测试结果: ![image-20210402153955072](https://img.kancloud.cn/fe/5e/fe5e81aec86e90026b72f5836679bf95_2416x456.png) ## @Output() 输入完成后,再增加一个输出对接: ```typescript +++ b/first-app/src/app/clazz/edit/edit.component.html @@ -12,7 +12,7 @@ <div class="mb-3 row"> <label class="col-sm-2 col-form-label">班主任</label> <div class="col-sm-10"> - <app-klass-select [id]="teacherId"></app-klass-select> + <app-klass-select [id]="teacherId" (beChange)="onTeacherChange($event)"></app-klass-select> <small class="text-danger"> 必须指定一个班主任 </small> ``` 对应增加C层的方法: ```typescript +++ b/first-app/src/app/clazz/edit/edit.component.ts @@ -39,4 +39,8 @@ export class EditComponent implements OnInit { }, error => console.log(error)); } + onTeacherChange($event: number): void { + console.log('接收到了选择的teacherId', $event); + this.teacherId = $event; + } ``` 测试效果: ![image-20210402154452053](https://img.kancloud.cn/64/a8/64a88f1d7ecfbf04b4848f0efc561cb1_1090x216.png) 每点击一个教师后C层的方法都会被触发一次,有意思的是组件初始化时,我们并没有点击教师列表,但父组件的`onTeacherChange`方法也被调用了一次。 这是由于在初始化时教师选择组件中的执行顺序如下: 1. 班级编辑组件初始化,teacherId为undefined,异步请求数据开始。 2. 教师选择组件初始化,执行`set id(undefined)`方法,调用`teacherId`的`setValue()`方法。 3. 执行教师选择组件的`ngOnInit()`方法,在该方法中对`teacherId`的数值变更进行了订阅,此后`teacherId`再有新值时,将得到一个通知。得到通知后将向父组件弹出得到的`teacherId`。 4. 父组件的异步请求数据返回,再次调用教师选择组件的`set id(xx)`方法,调用`teacherId`的`setValue()`方法。此时在3中的订阅将得到一个通知,近而向父组件弹出得到的teacherId。 5. 父组件接收到`teacherId`后,打印在控制台,组件初始化完毕。 上述流程导致了在初始化父组件将某个`teacherId`传给子组件后,子组件又将其传回了父组件。这虽然无关紧要,但的确是个多余的操作。 ## 加入验证 最后我们加入对班主任的验证,未设置班主任时显示相关的错误信息: ```html +++ b/first-app/src/app/clazz/edit/edit.component.html @@ -13,7 +13,7 @@ <label class="col-sm-2 col-form-label">班主任</label> <div class="col-sm-10"> <app-klass-select [id]="teacherId" (beChange)="onTeacherChange($event)"></app-klass-select> - <small class="text-danger"> + <small class="text-danger" *ngIf="teacherId === undefined"> 必须指定一个班主任 </small> </div> ``` 再次测试,错误消失: ![image-20210402160102894](https://img.kancloud.cn/5c/4c/5c4c6ba92091ad44c49553eaa6a5184a_2264x234.png) ## 本节作业 尝试在教师选择组件中,引入一个缓存变量`idInput` ,并使用其来规避在组件初始化时弹出的多余数据。 | 名称 | 链接 | | -------- | ------------------------------------------------------------ | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.4.3.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.4.3.zip) |