Angular data detection and one-way data flow-bacbcc94613b-

Angular data detection and one-way data flow-bacbcc94613b-

The study of this article is summarized in

data

In Angular, what we call data is the data model held by the component.

import { Component } from '@angular/core';
import {Course} from "./course";

export interface Course {
    id:number;
    description:string;
}

@Component({
  selector: 'app-root',
  template: `
    <div class="course">
        <span class="description">{{course.description}}</span>
    </div>
`})
export class AppComponent {


    course: Course = {
        id: 1,
        description: "Angular For Beginners"
    };
    
}
 

Angular uses templates to transform the data model into a view.

The use of templates in Angular

In Angular applications, the template refers to the HTML page pointed to by the template or templateUrl of the @Component decorator.
For example:

@Component({
  selector: 'app-root',
  template: `
    <div class="course">
        <span class="description">{{course.description}}</span>
    </div>
`})
export class AppComponent {


    course: Course = {
        id: 1,
        description: "Angular For Beginners"
    };
    
}
 

When there is a problem with the template, such as a missing closing tag, we will receive a useful error message in the console. So it is obvious that Angular does not simply use a string to process templates. So how does this work?
In Angular, Angular does not replace some variables with data to create actual HTML based on the template and then pass this HTML to the browser. The browser parses the HTML and generates the DOM data structure. The browser rendering engine then renders the view on the screen.

Angular does not generate HTML strings, it directly generates DOM data structures.

In fact, Angular applies the data model in the component class to a function (DOM component renderer). The output of this function is the DOM data structure corresponding to this HTML template.

The definition of this function is roughly as follows:

View_AppComponent_0.prototype.createInternal = function(rootSelector) {
        var self = this;
        
        var parentRenderNode = self.renderer.createViewRoot(self.parentElement);
        
        self._text_0 = self.renderer.createText(parentRenderNode,'\n',self.debug(0,0,0));
        
        self._el_1 = jit_createRenderElement5(self.renderer,parentRenderNode,'div',
               new jit_InlineArray26(2,'class','course'),self.debug(1,1,0));
        
        self._text_2 = self.renderer.createText(self._el_1,'\n\n    ',self.debug(2,1,20));
        
        self._el_3 = jit_createRenderElement5(self.renderer,self._el_1,'span',
              new jit_InlineArray26(2,'class','description'),self.debug(3,3,4));
        
        self._text_4 = self.renderer.createText(self._el_3,'',self.debug(4,3,30));
        
        self._text_5 = self.renderer.createText(self._el_1,'\n\n',self.debug(5,3,59));
        
        self._text_6 = self.renderer.createText(parentRenderNode,'\n',self.debug(6,5,6));
        
        self.init(null,(self.renderer.directRenderer? null: [
                self._text_0,
                self._el_1,
                self._text_2,
                self._el_3,
                self._text_4,
                self._text_5,
                self._text_6
            ]
        ),null);
        return null;
    };
 

From the naming of some methods of createViewRoot and createText and parentElement and parentRenderNode, we can roughly know that the function is creating a DOM data.

Once the data state changes, the Angular data detector detects it and will call
the DOM component renderer again.

How to check the DOM component renderer of your own component and the generation time of this function, please refer toAngular-What is Unidirectional Data Flow? Learn How the Angular Development Mode Works, why it's important to use it and how to Troubleshoot it in the Where can I find this function for my components? and When is this code generated? chapters.

Sources of changes in the data model

Once the data model changes, the view will change accordingly. This is also the popular Model Driven View. Then as far as the client (browser) is concerned, the event sources that cause the data model to change are:

  • Events: click, mouseover, keyup ...
  • Timers: setInterval, setTimeout
  • XHRs: Ajax (GET, POST...)

These event sources have a common feature, that is, they are all asynchronous operations. Then we can think that all asynchronous operations may cause model changes.

Change detection and one-way data flow rules

Every asynchronous operation may cause changes in the data state. Angular encapsulates Zone to intercept and track asynchronous (Zone is not explained here, please refer to it yourself).

  • Angular will perform a data check after each asynchronous operation, traversing each leaf component from the root component, the process is one-way.


    Angular change detection
  • Once the component data state change is detected, the DOM ompoent render is re-adjusted to convert the data model into the DOM data structure. The data flow is one-way.

In Angular, the one-way data flow rule means that when the data model changes, Angular initiates change detection and calls the DOM ompoent render to convert the data model into a DOM data structure. The data in the application will only turn into a DOM data structure in one direction. The direction of other changes.

One-way data flow rules

Note: Angular's entire rendering and scanning process from the top to the bottom of the component tree is also one-way.

Why does Angular follow the one-way data flow rule?

Why a one-way data flow?

In AngularJS, the flow of data is bidirectional. In a slightly complicated situation, this flow will become unpredictable, which may cause the entire application to fall into "infinite shock".

We want to make sure that during the process of converting the data into a view, the data is not further modified. Data flows from the component classes to the DOM data structure that represents them, and the act of generating these DOM data structures does not modify the data further. But in the change detection cycle of Angular, the lifecycle hook of the component will be called, which means that the code we write is called in the process, and the code may cause the data state to change.

Angular component life cycle

E.g

import {Component, AfterViewChecked} from '@angular/core';
import {Course} from "./course";

@Component({
    selector: 'app-root',
    template: `
    <div class="course">
        <span class="description">{{course.description}}</span>
    </div>
`})
export class AppComponent implements AfterViewChecked {

    course: Course = {
        id: 1,
        description: "Angular For Beginners"
    };

    ngAfterViewChecked() {
        this.course.description += Math.random();
    }

}
 

The above code will cause an error in the Angular change detection cycle. We modified the data status in the ngAfterViewChecked() method of this component. After the view is rendered, the data is inconsistent with the view state.

solve:

ngAfterViewChecked() {
    setTimeout(() => {
        this.course.description += Math.random();
    });
}
 

We can use setTimeout to delay data modification to the next change cycle.

In addition to component life cycle callback hooks that may trigger data state changes, there are others,
such as

import { Component } from '@angular/core';
import {Course} from "./course";

@Component({
    selector: 'app-root',
    template: `
    <div class="course">
        <span class="description">{{description}}</span>
    </div>
`})
export class AppComponent {

    course: Course = {
        id: 1,
        description: "Angular For Beginners"
    };

    get description() {
        return this.course.description + Math.random();
    }
}
 

Every time Angular checks the description, it returns a different value.

In the Angular change detection cycle, any behavior that changes the data state will throw an exception and terminate.

If Angular does not stop this behavior, the data and the view will remain in an inconsistent state, where the view after the rendering process is completed does not reflect the actual state of the data. Or repeat the test until the data is stable, which may cause performance problems.

Importance of one-way data flow

  • The first is because it helps to get good performance from the rendering process.

  • It ensures that when our event handler returns and the framework takes over the rendering results, nothing unpredictable happens.

  • Prevent data vs. view inconsistent errors.

Change detection performance optimization

Before change detection:

Before change detection

During change detection:

During change detection

Each change detection starts from the root component, executes from top to bottom, and traverses each component.
Since the framework has automatically made a lot of optimizations for the code generated by the template, even if the optimized model is not used, it can reach 3-10 times the performance of ng 1 dirty detection (the same number of bindings, the same detection frequency). This is mentioned in the video because when ng 2 generates the template code, it will dynamically generate code that makes the js engine easy to optimize. The general principle is to keep the object "shape" consistent before and after each check change. And if there is a bottleneck in performance, you can use the following two ways to perform high-level optimization:

  • OnPush change detection strategy + Immutable
  • OnPush change detection strategy + Observable

OnPush change detection strategy

OnPush strategy: If the input attribute does not change, the change detection of the component will be skipped

OnPush change detection strategy + Immutable

Angular's detection of complex data types, namely objects, is only to detect whether the reference to the object has changed

When the value of an object property changes, but its reference does not change, Angular will tacitly change the data without changing.

For practical examples, please refer to: Angular 2 Change Detection-2 's OnPush strategy chapter.

Therefore, when we use the OnPush strategy, we need to use the Immutable data structure (Immutable means immutable, meaning that when the data model changes, we will not modify the original data model, but create a new data model), In order to ensure the normal operation of the program.

In order to improve the performance of change detection, we should use the OnPush strategy in the component as much as possible. For this reason, the data required in our component should only depend on the input attributes .

OnPush change detection strategy + Observable

When immutable is used, the change detection cycle still starts from the root component and goes down and detects sequentially.

Each component in the above figure uses an immutable model. The white part is the changed part. Only the white part will be rechecked & rendered in a change detection cycle, thus greatly reducing the cost of processing changes.

When using the OnPush change detection strategy and Observable, the situation is different. Its changes are likely to happen from a very lower-level subcomponent, such as:

In the figure, a child component observes a data change through observable. At this time, we need to inform Angular that this part has changed. It will mark this component and its parent component up to the root component, and detect this part of the change separately:

ChangeDetectorRef

ChangeDetectorRef is a reference to the change detector of the component. We can obtain the object in the component through dependency injection to manually control the change detection behavior of the component:

The main methods in the ChangeDetectorRef change detection class are as follows:

export abstract class ChangeDetectorRef {
  abstract markForCheck(): void;
  abstract detach(): void;
  abstract detectChanges(): void;
  abstract reattach(): void;
}
 

The functions of each method are introduced as follows:

  • markForCheck()-If the changeDetection: ChangeDetectionStrategy.OnPush condition is set in the metadata of the component, then the change detection will not be executed again unless the method is manually called.
  • detach()-Detach the change detector from the change detection tree. The change detector of this component will no longer perform change detection unless the reattach() method is manually called.
  • reattach()-re-add the separated change detector so that the component and its sub-components can perform change detection
  • detectChanges()-Perform a change detection from the component to each subcomponent
import { Component, Input, OnInit, ChangeDetectionStrategy, 
         ChangeDetectorRef } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Component({
    selector: 'exe-counter',
    template: `
      <p> : {{ counter }}</p>
    `,
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent implements OnInit {
    counter: number = 0;

    @Input() addStream: Observable<any>;

    constructor(private cdRef: ChangeDetectorRef) { }

    ngOnInit() {
        this.addStream.subscribe(() => {
            this.counter++;
            this.cdRef.markForCheck();
        });
    }
}
 

summary

  • Angular change detection cycle, data from component class to DOM, follow the rules of one-way data flow.

  • Angular is a directed tree. By default, the change detection system will traverse the entire tree, but we can use the OnPush change detection strategy. Immutable can solve most of the problems, and the Observables object is used, and then the ChangeDetectorRef instance provides The method allows you to control the behavior of the detector more flexibly, and ultimately improve the overall performance of the system.

Here, thank you semlinker for your help!