在现实生活中,感知数据变化的方式有两种,第一种是**数据监听**,第二种是**观察者模式**。 以在京东购物为例,假设你最有购买一块CPU的需求,由于口袋不太满,所以想在搞活动的时候购买。满足此需求有两种实现的方式。 第一种:我们可以每天睡前打开京东看看价格。如果有一天CPU的价格达到了自己心中的预期,便可以购买了。这就是最典型的数据监听。 第二种:我们可以在对应的商品上点击降价提醒按钮,并输入自己心中的一个预期价格。此后,一旦CPU降到了我们预期的价格,我们则会收到一个降价的通知。而这便是我们说的**观察者模式**。 > 如果**观察者模式**不太容易理解,也可以记忆为**订阅者模式**、**关注者模式**,想想各种求订阅、求关注的情景,相信这种模式便不难理解了吧。 如果你也是我这样的懒人,相信也一定会选择第二种模式。这种模式可以使我们能够再懒一些,对于计算机而言,这种懒对应的便是:占用资源更低、效率更高。 ## 伟大的Angular 伟大的Agnular并不仅仅是一个前端的框架,它还是先进编程思想的传播者!这种感受会随着我们接触Angular的时间而逐步加深。 ## 响应式表单 响应式表单在数据监听方面实现了**观察者模式**,在此模式的支持下,数据变化时我们可以得到一个通知。接下来我们使用响应式表单对教师选择组件进行改造。 ```typescript +++ b/first-app/src/app/clazz/klass-select/klass-select.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit, EventEmitter, Output} from '@angular/core'; import {Teacher} from '../../entity/teacher'; import {HttpClient} from '@angular/common/http'; +import {FormControl} from '@angular/forms'; @Component({ @@ -10,7 +11,7 @@ import {HttpClient} from '@angular/common/http'; }) export class KlassSelectComponent implements OnInit { teachers = new Array<Teacher>(); - teacherId: number | undefined; + teacherId = new FormControl(); @Output() beChange = new EventEmitter<number>(); @@ -23,10 +24,4 @@ export class KlassSelectComponent implements OnInit { this.httpClient.get<Array<Teacher>>('teacher') .subscribe(teachers => this.teachers = teachers); } - - onChange(): void { - console.log('change called'); - console.log(this.teacherId); - this.beChange.emit(this.teacherId); - } } ``` 如上所示:使用对象`FormControl`来替代原来的`number`类型,并删除原来的`onChange`方法。然后对V层进行如下改造: ```html +++ b/first-app/src/app/clazz/klass-select/klass-select.component.html @@ -1,7 +1,5 @@ <select id="teacher" class="form-control" - name="teacher" - [(ngModel)]="teacherId" - (change)="onChange()"> + [formControl]="teacherId"> <option *ngFor="let teacher of teachers" [ngValue]="teacher.id"> {{teacher.name}} </option> ``` `formControl`并不要求表单项必须有`name`,在此我们一并删除了`name`属性。接下来向班级模块中添加响应式表单所在的模块`ReactiveFormsModule`: ```typescript +++ b/first-app/src/app/clazz/clazz.module.ts @@ -1,7 +1,7 @@ import {NgModule} from '@angular/core'; import {CommonModule} from '@angular/common'; import {AddComponent} from './add/add.component'; -import {FormsModule} from '@angular/forms'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import { KlassSelectComponent } from './klass-select/klass-select.component'; @@ -9,7 +9,8 @@ import { KlassSelectComponent } from './klass-select/klass-select.component'; declarations: [AddComponent, KlassSelectComponent], imports: [ CommonModule, - FormsModule + FormsModule, + ReactiveFormsModule ] }) export class ClazzModule { ``` 最后,向单元测试的动态测试模块中也添加`ReactiveFormsModule`模块: ```typescript +++ b/first-app/src/app/clazz/klass-select/klass-select.component.spec.ts -import {FormsModule} from '@angular/forms'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; imports: [ HttpClientModule, - FormsModule + FormsModule, + ReactiveFormsModule ], ``` ### 测试 启动单元并查看效果,查看控制台是否有报错信息: ![image-20210324084043748](https://img.kancloud.cn/ff/ab/ffab5f83c4049bc5e37438562aa276db_964x188.png) 小结:直接在表单中使用`[(ngModel)]`虽然便捷,但却不够**正综**。Angular的前身AngularJS尝经广泛的使用了这种`ngModel`数据绑定方式,当年也获取了很大的成功。但这种方式也会带来不小的开销,当项目稍大的时,便会暴露出性能上的弊端。Angular中启用了全新的**响应式表单**来替换原`ngModel`的绑定方式,但可能是出于照顾使用AngularJS的历史用户,使他们在升级时能够减小一点点负担,所以在新的Angular中仍然支持了这种模式。 ## 订阅数据变化 下面,我们展示如何来**关注**`teacherId`这个`FormControl`以成为它的粉丝,这样一来,当它有新的动态时便会给我们发送通知了。 ```typescript +++ b/first-app/src/app/clazz/klass-select/klass-select.component.ts @@ -20,6 +20,9 @@ export class KlassSelectComponent implements OnInit { } ngOnInit(): void { + // 关注teacherId + this.teacherId.valueChanges + .subscribe((data: number) => this.beChange.emit(data)); // 获取所有教师 this.httpClient.get<Array<Teacher>>('teacher') .subscribe(teachers => this.teachers = teachers); ``` `valueChanges`是`FormControl`提供的一个可对其进行关注的属性,我们只需要调用`valueChanges`上的`subscribe`方法,便完成了对数据的**关注**。当有新的数据时,我们传入的订阅方法将被执行,数据成功由beChange弹射器弹出给父组件。 ## 测试 我们像以往的测试一样选择教师: ![image-20210324084043748](https://img.kancloud.cn/ff/ab/ffab5f83c4049bc5e37438562aa276db_964x188.png) 选择的教师变更后,接收到了弹出的数据。 ![image-20210325161027648](https://img.kancloud.cn/8e/15/8e150d3277dc1c338d1077b359eba282_1260x268.png) ## 总结 本节中,我们使用响应式表单的`FormControl`对象来替换了原`number`类型。在与V层的绑定上`FormControl`并不要求表单项必须存在`name`属性。 在监听数据变化方法,响应式表单使用了观察者模式。 我们虽然还没有感受到这种模式给我们带来的好处。但相对于原`change()`方法,最少在代码量上有所降低。 在后续的章节中,我们会千篇一律的使用响应表单,相信你会逐步的感受它的魅力。 | 名称 | 链接 | | ------------ | ------------------------------------------------------------ | | 响应式表单 | [https://angular.cn/guide/reactive-forms#reactive-forms](https://angular.cn/guide/reactive-forms#reactive-forms) | | valueChanges | [https://angular.cn/api/forms/AbstractControl#valueChanges](https://angular.cn/api/forms/AbstractControl#valueChanges) | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.2.2.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.2.2.zip) |