上个小节的问题关键在于:虽然我们在组件中使用了路由获取路由参数id的值,并使用该id获取了相应的数据,但这个过程仅仅限制在了组件初始化的过程中。而初始化完成后路由的值虽然发生了变化,但组件全然不知。那么如果在路由的值发生变化的时候,路由来通知一下组件,组件收到通知后再重新拉取数据就可以完全规避些问题。这种变化时发出通知的方式,我们称其为**观察者模式**。也就是说:如果想让路由在变化时发通知给组件,那么组件观察着的路由就可以了,这样一来如果路由发生了变化,组件中对此路由的观察者就会得知。 # 观察者模式 **观察者模式:** A声明自己是可以被观察的,B将自己列为A的观察对象,此时B便成为了A的观察者。这类似于微信中的朋友圈,当朋友圈的好友有了新的动态时,我们第一时间能获取到取于以下因素。 * 张三允许其它人添加为朋友。 * 我们向张三发起添加好友申请且被通过了。 * 张三发朋友圈的时候选择的可见范围中包括了我们。 * 我们未屏蔽张三的朋友圈。 在计算机的世界里,也是这样: * 路由允许其它人成为它的观察者,这是由`路由`来决定了,在`angular`中路由便有此属性。 * 教师编辑组件需要发起好友申请来表明想观察路由的想法,在`angular`中的路由的好友规则是:加我为朋友时不需要验证;这个添加的过程我们称为`subscribe 订阅`。 * 路由发生变化时,会向所有的观察者们发送消息,当然包含教师编辑组件,这个过程我们称为`notice 通知`。 * 教师编辑组件接收此消息。 编写代码: * `route.params`是允许被观察的。 * 统一规定通过`subscribe()`来发起好友申请) * 有新的动态后将新的动态传给`function(data) { ... }` ``` this.route.params.subscribe(function(data) { }); ``` 改写为箭头函数并加入测试信息: ``` this.route.params.subscribe(data => { console.log(data); }); ``` 最后,我们将上面的代码添加到ngOnInit()方法中,使得该方法在组件生成自动执行: ``` ngOnInit(): void { this.route.params.subscribe(data => { console.log(data); }); this.httpClient.get(this.getUrl()) .subscribe((data) => { this.teacher = data; }, () => { console.log(`请求 ${this.getUrl()} 时发生错误`); }); } ``` ## 测试 ![](https://img.kancloud.cn/61/c9/61c96a138413f40c202f693d56b66d52_694x439.gif) 通过测试发现,当路由发生变更时,执行了方法`console.log(data)`。这足以说明当路由发生变更时,我们得到了通知。有了这个基础,下面对代码进行重构。 ## 重构代码 由于该组件对应的URL是会产生变化的,所以`getUrl()`方法并不适用`每次运算的结果均相同`的`数据缓存`理论,此时应该对`getUrl()`进行改写。 由于在数据加载及数据更新时,都需要使用当前编辑教师的ID信息,当同一个类中两个方法中使用共同的变量(方法)时,我们将其共同使用的变量(方法)进行剥离。 按照一事一议,减少嵌套的原则,我们将加载数据的方法由`ngOnInit`中抽离。 最终TeacherEdit组件代码如下: ``` import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Router} from '@angular/router'; import {HttpClient} from '@angular/common/http'; import {AppComponent} from './app.component'; @Component({ templateUrl: './teacher-edit.component.html' }) export class TeacherEditComponent implements OnInit { public teacher: any = {}; private id: number; constructor(private route: ActivatedRoute, private httpClient: HttpClient, private appComponent: AppComponent, private router: Router) { } /** * 获取与后台对接的URL */ getUrl(): string { return 'http://localhost:8080/Teacher/' + this.id; } /** * 当路由参数发生变化时,加载教师数据。 */ load(): void { console.log('加载教师数据'); this.httpClient.get(this.getUrl()) .subscribe((data) => { this.teacher = data; }, () => { console.log(`请求 ${this.getUrl()} 时发生错误`); }); } ngOnInit(): void { this.route.params.subscribe(data => { console.log('路由参数发生变化,接收通知'); this.id = data.id; this.load(); }); } /** * 提交表单 */ onSubmit(): void { this.httpClient.put(this.getUrl(), this.teacher) .subscribe(() => { console.log('更新成功'); this.appComponent.ngOnInit(); this.router.navigate(['/']); }, () => { console.error(`更新数据时发生错误,url:${this.getUrl()}`); }); } } ``` ## 测试 ![](https://img.kancloud.cn/4d/22/4d224d58243d79294416491867708e51_692x291.gif) ## route.snapshot 如果组件生成后不再复用,即每次组件都是重新构建出来的。我们是可以简单地使用`route.snapshot`来获取路由参数的。但组件被复用后这招就不灵了,这是因为本身`snapshot`即是`快照`的意思,如果你使用过一些虚拟机产品对这个词汇肯定不会感觉陌生。`快照`是对某一事件在某个特定的点留下的照片,其保存的是某个实点的信息,该信息一旦生成便不会发生变更。所以即使是后续的路由值变更了,也不会对历史的`快照`产生影响。而如果我们通过`snapshot`来获取信息的话,则永远获取的是历史的时点值。如果在组件的生命周期(从其产生到销毁的整个过程)中路由均未发生变化的话,调用`snapshot`可以认为就是在调用当前路由,这也是为什么在前面我们这么使用没有发生错误的原因。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | Angular 中的观察者 | [https://www.angular.cn/guide/observables-in-angular#observables-in-angular](https://www.angular.cn/guide/observables-in-angular#observables-in-angular) | - | | 可观察对象 | [https://www.angular.cn/guide/observables](https://www.angular.cn/guide/observables) | - | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.7](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.7) | - |