Home > Article > Web Front-end > Let's talk about change detection in Angular through examples
This article will take you through the change detection in Angular. We will start with a small example, and then gradually discuss the change detection in depth. I hope it will be helpful to everyone!
#Change detection in Angular is a mechanism used to synchronize the state of the application UI with the state of the data. When application logic changes component data, the values bound to DOM properties in the view also change. The change detector is responsible for updating the view to reflect the current data model. [Recommended related tutorials: "angular tutorial"]
It is easy to learn from the paper, but I know that I have to do it in detail. In order to make it easier for readers to understand, this article starts with a small example and then expands step by step. The example is as follows:
// app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'aa'; handleClick() { this.title = 'bb'; }} // app.componnet.html <div (click)="handleClick()">{{title}}</div>
The example is relatively simple, that is, a click event is bound to the div
element. Clicking on the element will change the value of the variable title
. The interface The display will also update. How does the framework know when it needs to update a view, and how does it update the view? Let’s find out.
When we click on the div
element, the handleClick
function will be executed. So how is this function triggered in Angular applications? If you have read my previous article about the introduction of zone.js
, you will know that the click event in the Angular application has been taken over by zone.js
. Based on this answer, it is obvious that the execution must be triggered by zone.js
at the beginning, but here we have to further analyze the direct calling relationship and expand it layer by layer. The code closest to the handleClick
function call is the following code:
function wrapListener(listenerFn, ...) { return function wrapListenerIn_markDirtyAndPreventDefault(e) { let result = executeListenerWithErrorHandling(listenerFn, ...); } }
The listenerFn
function in the above code points to handleClick
, but it Is the parameter of wrapListener
function. In the example, the element is bound to a click event. The related template compilation product is probably as follows:
function AppComponent_Template(rf, ctx) { ...... i0["ɵɵlistener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }) }
When loading the application for the first time, it will execute renderView
, then executeTemplate
, and then trigger With the above template function, the click function of the element is passed all the way to the listenerFn
parameter. At this point we understand that the trigger source of the click function is zone.js
, but the actual click function delivery is implemented by Angular. So how are zone.js
and Angular related? ? zone.js
will arrange a task for each asynchronous event. Based on the example in this article, invokeTask
is called by the following code:
function forkInnerZoneWithAngularBehavior(zone) { zone._inner = zone._inner.fork({ name: 'angular', properties: { 'isAngularZone': true }, onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => { try { onEnter(zone); return delegate.invokeTask(target, task, ...); } finally { onLeave(zone); } } }) }
See here Isn't it very familiar, because there are similar code snippets in the previous article introduced by zone.js
. The forkInnerZoneWithAngularBehavior
function is called by the constructor of class NgZone. So far we have introduced NgZone, a protagonist of Angular change detection, which is a simple encapsulation of zone.js
.
Now that we know how the click function in the example is executed, if the application data changes after the function is executed, how can the view be updated in time? We still go back to the forkInnerZoneWithAngularBehavior
function mentioned above. In the try finally
statement block, the invokeTask
function will eventually execute onLeave(zone )
function. Further analysis can see that the onLeave
function finally calls the checkStable
function:
function checkStable(zone) { zone.onMicrotaskEmpty.emit(null); }
is subscribed accordingly in the class ApplicationRef
constructor Got this emit
event:
class ApplicationRef { /** @internal */ constructor() { this._zone.onMicrotaskEmpty.subscribe({ next: () => { this._zone.run(() => { this.tick(); }); } }); }
In the subscription-related callback function, does this.tick()
look familiar? If you have read my previous article about Angular life cycle functions, then you will definitely have the impression that it is the key call to trigger view updates. Although this function was mentioned in the life cycle introduction article, the focus of this article is change detection. Therefore, although the function is the same, the focus has changed slightly. this.tick
The relevant calling sequence is roughly like this:
this.tick() -> view.detectChanges() -> renderComponentOrTemplate() -> refreshView()
HererefreshView
is more important and is analyzed separately:
function refreshView(tView, lView, templateFn, context) { ...... if (templateFn !== null) { // 关键代码1 executeTemplate(tView, lView, templateFn, ...); } ...... if (components !== null) { // 关键代码2 refreshChildComponents(lView, components); } }
In this processrefreshView
The function will be called twice. The first time it enters the key code 2 branch, and then the following functions are called in sequence to re-enter the refreshView
function:
refreshChildComponents() -> refreshChildComponents() -> refreshComponent() -> refreshView()
The second time Entering the refreshView
function call is the key code 1 branch, that is, the executeTemplate
function is executed. And what this function ultimately executes is the AppComponent_Template
function in the template compilation product:
function AppComponent_Template(rf, ctx) { if (rf & 1) { // 条件分支1 i0["ɵɵelementStart"](0, "div", 0); i0["ɵɵlistener"]("click", function AppComponent_Template_div_click_0_listener() { return ctx.handleClick(); }); i0["ɵɵtext"](1); i0["ɵɵelementEnd"](); } if (rf & 2) { // 条件分支2 i0["ɵɵadvance"](1); i0["ɵɵtextInterpolate"](ctx.title); } }
If there are still readers who are not sure how the functions in the above template compilation product come from, it is recommended to read the previous about This article explains the principles of dependency injection and will not be repeated due to space limitations. At this time, the AppComponent_Template
function executes the code in conditional branch 2, and the ɵɵadvance
function is to update the relevant index value to ensure that the correct element is found. The focus here is on the ɵɵtextInterpolate
function, which ultimately calls the function ɵɵtextInterpolate1
:
function ɵɵtextInterpolate1(prefix, v0, suffix) { const lView = getLView(); // 关键代码1 const interpolated = interpolation1(lView, prefix, v0, suffix); if (interpolated !== NO_CHANGE) { // 关键代码2 textBindingInternal(lView, getSelectedIndex(), interpolated); } return ɵɵtextInterpolate1; }
值得指出的是,该函数名末尾是数字1,这是因为还有类似的ɵɵtextInterpolate2
、ɵɵtextInterpolate3
等等,Angular 内部根据插值表达式的数量调用不同的专用函数,本文示例中文本节点的插值表达式数量为1,因此实际调用的是ɵɵtextInterpolate1
函数。该函数主要做了两件事,关键代码1作用是比较插值表达式值有没有更新,关键代码2则是更新文本节点的值。先来看看关键代码1的函数interpolation1
,它最终调用的是:
function bindingUpdated(lView, bindingIndex, value) { const oldValue = lView[bindingIndex]; if (Object.is(oldValue, value)) { return false; } else { lView[bindingIndex] = value; return true; } }
变更检测前的文本节点值称之为oldValue
, 该值存储在lView
中,lView
我在之前的文章中也提到过,忘记了的读者可以去看看lView
的作用。bindingUpdated
首先会比较新值和旧值,比较的方法便是Object.is
。如果新值旧值没有变化,则返回false
。如果有变化,则更新lView
中存储的值,并返回true
。关键代码2的函数textBindingInternal
最终调用的是下述函数:
function updateTextNode(renderer, rNode, value) { ngDevMode && ngDevMode.rendererSetText++; isProceduralRenderer(renderer) ? renderer.setValue(rNode, value) : rNode.textContent = value; }
走完上述流程,我们点击div
元素时,界面显示内容便会由aa
变为bb
,即完成了从应用数据的变更到 UI 状态的同步更新,这便是 Angular 最基本的变更检测过程了。
因篇幅限制,本文所举示例比较简单,但 Angular 的变更检测还有很多没有讲到。比如,如果应用是由若干个组件组成的,父子组件间的变更检测如何进行,以及如何通过策略优化变更检测等等。如果有对这方面感兴趣的朋友,欢迎关注我的个人公众号【朱玉洁的博客】,后续将在那里分享更多前端知识。
更多编程相关知识,请访问:编程学习!!
The above is the detailed content of Let's talk about change detection in Angular through examples. For more information, please follow other related articles on the PHP Chinese website!