原文地址:http://blog.thoughtram.io//angular/2016/02/01/Zone-in-angular-2.html
在理解Zone这篇文章中,在我们的代码中通过构建一个分析异步操作的zone,来探讨Zone的力量。
我们了解到,Zone是一种使我们能够hook到异步任务的执行上下文,如果你还未读过那篇文章,我强烈建议你阅读,因为它是这篇文章的基础,在这篇文章中,我们将需要仔细看看Zone在Angular中扮演的角色。
Zone与Angular完美结合
事实上,Zone很好的解决了,在我们Angular应用中用来执行变化检测的问题。你有没有问自己,何时以及为什么Angular会进行变化检测?是什么告诉Angular “哥们,一个变化可能发生在我的应用中。你能查一下吗?”。
在我们深入这些问题前,让我们先考虑一下究竟是什么原因导致了我们应用的变化。或者更确切地说,有什么可以改变应用的状态。应用状态的改变是由三个因素引起:
Events - 使用事件,类似于click,change,input,submit,…
XMLHttpRequests - 例如。从远程服务获取数据时
Timers - setTimeout(),setInterval()
当然,这三个因素都有一些共同点。你能说出它吗?他们都是异步的。
为什么认为这是很重要的?因为我们发现这些是Angular实际有兴趣去更新视图的唯一情况。
假设我们有一个Angular 2组件,点击一个按钮时执行处理程序:
@Component({
selector: 'my-component',template: ` <h3>We love {{name}}</h3> <button (click)="changeName()">Change name</button> `
})
class MyComponent {
name:string = 'thoughtram';
changeName() {
this.name = 'Angular';
}
}
如果你不熟悉的(click)语法,你可能需要阅读Angular 2模板语法,简单的说就是为这个button元素设置一个点击事件处理。
当组件的button被点击,changeName()
被执行,从而将改变组件的name属性,由于我们希望这种变化在DOM被反映出来,Angular需要更新相应的视图绑定{{name}}
,很好,似乎是奇迹般地工作了。
另一个例子是,使用setTimeout()
来更新name属性,注意,我们删除了button
@Component({
selector: 'my-component',template: `
<h3>We love {{name}}</h3>
`
})
class MyComponent implements OnInit {
name:string = 'thoughtram';
ngOnInit() {
setTimeout(() => {
this.name = 'Angular';
},1000);
}
}
我们并没有特别的做什么来告诉的框架, 变化已经发生,没有ng-click
,没有$timeout
,$scope.$apply()
如果你已经阅读了理解Zone这篇文章,你知道这工作显然是因为Angular2充分利用了Zone,Zone猴子补丁了全局异步操作,如setTimeout()和addEventListener(),这就是为什么Angular可以很简单的知道什么时候去更新DOM.
事实上,每当VM执行完成会去告诉Angular进行变化检测,就是这么简单:
ObservableWrapper.subscribe(this.zone.onTurnDone,() => { this.zone.run(() => { this.tick(); }); }); tick() { // perform change detection this.changeDetectorRefs.forEach((detector) => { detector.detectChanges(); }); }
每当Angular zone发射出一个onTurnDone事件,它会运行一个任务执行整个应用的变化检测。如果你有兴趣了解Angular2变化检测是如何工作的,关注着吧,我们很快会发表另一篇文章。
等等。onTurnDone事件从哪里发射出来?这是不是默认Zone API的一部分?事实上,Angular引入了名为NgZone的自己的Zone.
Angular 2中的NgZone
NgZone基本上是一个Zone fork出来的,它继承了Zone的API,还添加了一些用来执行上下文额外功能,其中一件事是添加了下面这些我们可以订阅的自定义事件API,因为这些是observable流
onTurnStart() - 在Angular事件启动前通知订阅者,发射每一次是由Angular处理的浏览器任务。
onTurnDone() - 在Angular zone完成当前任务时立即通知订阅者
onEventDone() - 在完成onTurnDone()回调之后在VM事件之前立即通知订阅者, 用来测试验证应用程序状态。
如果”Observables”和”Streams”对于你来说是新的,你可能需要阅读Taking advantage of Observables in Angular 2
主要原因是Angular添加自己的事件发射器,而不是依靠beforeTask和afterTask回调,是为了跟踪定时器和其他微任务。它是非常棒的,使用Observables的API来处理这些事件。
运行代码在Angular Zone外
由于NgZone其实只是全局Zone的一个fork,Angular对于在Zone内需要或不需要执行变化检查,都具有完全的控制权,这为什么是有用的?因为我们并不总是希望Angular神奇地进行变化检测。
正如前面提到过,Zone几乎在任何浏览器的全局异步操作打上了猴子补丁。并且NgZone只是Zone fork出来的,当异步操作发生时就会通知框架进行变化检测,所以当类似于mousemove事件发生时,它也将引发变化检测。
每次鼠标被触发时,我们可能不希望进行变化检测,因为它会减慢我们的应用和结果,是非常糟糕的用户体验。
这就是为什么NgZone带有一个runOutsideAngular()
API
,其执行在NgZone的父Zone的给定的任务,这将不发出一个onTurnDone事件,因此,不进行任何改变的检测。为了证明这是非常有用的功能,让我们来看看下面的代码:
@Component({
selector: 'progress-bar',template: ` <h3>Progress: {{progress}}</h3> <button (click)="processWithinAngularZone()"> Process within Angular zone </button> `
})
class ProgressBar {
progress: number = 0;
constructor(private zone: NgZone) {}
processWithinAngularZone() {
this.progress = 0;
this.increaseProgress(() => console.log('Done!')); } }
在这里没有什么特别的,有一个组件,其模板中的按钮被点击时,会调用processWithinAngularZone()方法。然而,这个方法调用increaseProgress()。让我们来看看这个方法:
increaseProgress(doneCallback: () => void) { this.progress += 1; console.log(`Current progress: ${this.progress}%`); if (this.progress < 100) { window.setTimeout(() => { this.increaseProgress(doneCallback); },10); } else { doneCallback(); } }
increaseProgress()每10毫秒调用1次自己,直到调用100次,一旦完成,给定的doneCallback将执行,请注意我们是如何使用的setTimeout()来增长progress值.
在浏览器运行此代码,基本上我们已经知道代码将如何展示,每一次setTimeout()调用,Angular会执行变化检测和更新视图,我们会看到每10毫秒progress被增长,当在Angular的区外运行这段代码将变得更有趣,让我们添加一个方法,就是这样。
processOutsideAngularZone() {
this.progress = 0;
this.zone.runOutsideAngular(() => { this.increaseProgress(() => { this.zone.run(() => { console.log('Outside Done!'); }); }); }); }
processOutsideAngularZone()也调用increaseProgress(),但这次使用runOutsideAngularZone(),它会导致在每次timeout后Angular不会通知执行变化检测,我们可以在组件里注入token为NgZone,来访问Angular zone.
progress增加时UI不会更新,然而,一旦increaseProgress()完成后,我们使用zone.run()
在Angular zone 内运行另一个任务,这又导致Angular 来执行变化检测并更新视图,换句话说,我们不会看到progress不断增加,一旦完成我们只看到progress最后的值,在这里可以看到全部的代码
Zone 现在也被提议在TC39作为标准,也许是另一个理由让我们来仔细学习它