Angular Lifecycle Practice
The meaning of lifecycle hooks
In Angular, implementing the hook interface method of the component's life cycle is the point of time to use the Angular framework to implement the plug-in business.
What are the contents of the life cycle in the official documentation
In the official documentation of Angular, there is a chapter Lifecycle hooks to explain the mechanism of the life cycle and how to observe the component life cycle.
From the official chapter, we can understand all aspects of the life cycle hook method and there is basically no difference.
I list a few things I got from the official:
- is written in the component ; to implement
OnInit
method, add thengOnInit
method to the component, note that there are two more letters, each hook method is the same. - Lifecycle event sequence ;
- how to observe life cycle events ;
- uses ngOnInit as the business insertion point instead of the business code in the constructor;
- Execution timing of
- trigger sequence of all life cycle events
- Execution mechanism of
- ...etc
This basically covers all aspects of the component life cycle;
The main content of this article
Official chapters are based on life cycle itself to speak of, and during the execution of the actual use of the process life cycle will the dependency injection timing , data streaming , change detection , father and component data changes , component inherits and many other features are used in combination. After the combination, it is a little difficult for us to understand the execution order. Only after a lot of thinking and practice can we know and summarize the rules.
Different from the official entry point, this article hopes to summarize the rules from practice.
In this paper, the following scenarios are selected for practical demonstration and try to draw the rules:
- Basic life cycle introduction
- The timing of the data flow transmission is which specific event is executed.
- How the life cycle of the parent-child component is executed in the parent-child component.
- How is the life cycle executed in the inherited component.
- Does the variable bound in the template have anything to do with these lifecycles when getting the value?
I hope that through reading this article, I can help you lift some veils, so that you can follow me to further master the characteristics of Angular, think about the life cycle settings in Angular, and spy on the operating mechanism of Angular; understand Angular's ideas, thoughts and its actions Thinking patterns shaped by developers.
Only we can think in the Angular way. When we encounter complex business involving complex data flow, complex component relationship, and complex class relationship, we can quickly understand knowledge blind spots through thinking, and quickly organize and acquire relevant knowledge keywords. In this way, you will become like a duck to water in the vast ocean of Angular concepts and new knowledge, and will become the sharpest spear when we develop business code, optimize refactoring, and solve problems.
lifecycle-hook-basic
Since the order of the life cycle has been introduced and demonstrated in the official documentation, we will make a simple verification and summary here as a review.
First look at an actual execution effect diagram
Note: Careful friends may notice that the ngOnChanges event is not seen in this picture. It's not that I forgot The root component will not trigger the ngOnChanges event , because there is no @Input variable with the component;
The source code is as follows
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
name = 'Angular ' + VERSION.major;
messages = [];
subject = window['subject'];
constructor() {
this.subject.subscribe((item) => {
this.messages.push(item);
});
this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance' });
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnChanges' });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnInit' });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngDoCheck' });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentInit' });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentChecked' });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewInit' });
}
ngAfterViewChecked() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewChecked' });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnDestroy' });
}
}
The image below is a normal component with the @Input attribute
The source code is as follows
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
name = 'Angular ' + VERSION.major;
messages = [];
subject = window['subject'];
constructor() {
this.subject.subscribe((item) => {
this.messages.push(item);
});
this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance' });
}
}
// HelloComponent 是AppComponent的子视图组件
...
@Component({
selector: 'hello',
template: `<h1>Hi, {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
_name: string = '';
subject = window['subject'];
@Input() set name(n: string) {
this.subject.next({ type: '@input', content: 'set name update' });
this._name = n;
}
get name() {
// this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用
return this._name;
}
messages = [];
constructor() {
this.subject.next({ type: 'constructor exec', content: 'class instance, 访问@input属性name=' + this.name });
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 访问@input属性name=' + this.name });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 访问@input属性name=' + this.name });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 访问@input属性name=' + this.name });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 访问@input属性name=' + this.name });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 访问@input属性name=' + this.name });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 访问@input属性name=' + this.name });
}
ngAfterViewChecked() {
this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 访问@input属性name=' + this.name });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 访问@input属性name=' + this.name });
}
}
explain and summarize
Combining the above sequence, we can first draw a rough picture as follows:
Explain the meaning of each hook in this diagram:
Notice:
- The events with the light gray names will only be triggered once in the life cycle of the component, while the green events will trigger multiple times with the corresponding logic changes.
- Here I also added the execution of the component constructor to the observation sequence, because in business, there are often small partners who will insert business code in the constructor.
All methods are executed in the following order:
Construction, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy
Construction
When the constructor is executed, it is executed once.
OnChanges
Called when any of the bindable properties of the directive (the component's implementation inherits the directive) changes.
Key Points:
- Judging from the execution effect of the above root component, this hook will not necessarily be called, it will only be triggered if the @Input property changes;
- The number of changes of the @Input property is not required, so there is no limit to the number of callbacks.
- This block should pay special attention to the situation where the parent changes the passed value, which will cause ngOnChange to be called again at any point in the life cycle.
- The parameter is a change handler for an input property that will contain the old and new values of all changed input properties;
- If at least one change has occurred, this callback method is called after the default change detector has checked the bindable properties and before the view children and content children have checked.
- Property setters can be used instead to execute logic in this hook.
OnInit
Called after Angular has initialized all of the directive's data-bound properties;
Key Points:
- After the default change detector first checks all of the directive's data-bound properties, but before any subviews or shadows have been checked.
- It will and will only call once at instruction initialization
- Define
ngOnInit()
method to handle all additional initialization tasks .
DoCheck
During change detection, the default change detection algorithm compares bindable properties against references to find differences. You can use this hook to check for and respond to changes in other ways.
Key Points:
- In addition to performing checks using the default change checker, a custom change detection function is executed for the directive
- When the default change detector checks for changes, it triggers the execution of OnChanges() (if any), regardless of whether you do additional change detection.
- DoCheck and OnChanges should not be used together in response to changes that occur on the same input.
- The default change detector is called after execution and performs change detection.
- See
KeyValueDiffers
andIterableDiffers
to implement custom change detection logic for collection objects. - In DoCheck, you can monitor those changes that OnChanges cannot capture, and the detection logic needs to be implemented by yourself.
- Since DoCheck can monitor when specific variables have changed, it is very expensive. Angular renders irrelevant data elsewhere on the page also triggers this hook, so your implementation must ensure the user experience by itself.
AfterContentInit
It will be called immediately after Angular has initialized everything this directive.
Key Points:
- It will only be called once after instruction initialization is complete.
- Can be used to handle some initialization tasks
AfterContentChecked
Called immediately after the default change detector has completed change detection for everything under this directive.
AfterViewInit
- Called after Angular has fully initialized the component's view . Define a
ngAfterViewInit()
method to handle some additional initialization tasks .
AfterViewChecked
Called immediately after the default change detector has completed one round of change detection cycles for the component view.
OnDestroy
Called when a directive, pipe, or service is destroyed. Used to execute some custom cleanup code when the instance is destroyed.
Further explanation:
Because what we care about is when to use these hooks, you can go back to the definition of these callback hooks above and carefully observe the content of the hook with Init, you can see OnInit, AfterContentInit, AfterViewInit these three, they are only executed once, all can Do some initialization tasks, the difference between these three hooks is like the description in the definition.
you need it, you can leave a message below the article to see if the actual situation needs to be carefully explained, and the differentiated scenarios that these three hooks are applicable to.
has not carefully studied the three different partners, and is often confused about which hook to put the asynchronous initialization business that he wants to implement, so choose one at random, either on OnInit, or AfterViewInit, if you find that you put it in one No, just change to another one until the problem is solved or it takes a long time to solve the problem or leave occasional bugs (the reason is occasional because asynchronous sequential execution is not technically guaranteed), and it is quite laborious to check again.
Therefore, these three Init are very worthy of the attention of small partners who write more asynchronous business.
From the first scenario, we reviewed what each lifecycle hook has.
Next we look at the scene with @Input:
lifecycle-hook&Input
The source code is as follows
//AppComponent html
<h1>Hi, Angular 13!</h1>
<h3>- 演示生命周期钩子函数调用顺序<br /></h3>
<p>Start editing to see some magic happen :)</p>
<ul>
<li *ngFor="let message of messages">
<span class="message-type">{{ message.type }}</span>
=>
<span class="message-content">{{ message.content }}</span>
</li>
</ul>
<hello [name]="name"></hello>
// AppComponent
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent{
name = 'Angular ' + VERSION.major;
messages = [];
subject = window['subject'];
constructor() {
this.subject.subscribe((item) => { this.messages.push(item); });
this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance, 访问@input属性name=' + this.name });
}
}
// HelloComponent 是AppComponent的子视图组件
...
@Component({
selector: 'hello',
template: `<h1>Hi, {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
_name: string = '';
subject = window['subject'];
@Input() set name(n: string) {
this.subject.next({ type: '@input', content: 'set name update' });
this._name = n;
}
get name() {
// this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用
return this._name;
}
messages = [];
constructor() {
this.subject.next({ type: 'constructor exec', content: 'class instance, 访问@input属性name=' + this.name });
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 访问@input属性name=' + this.name });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 访问@input属性name=' + this.name });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 访问@input属性name=' + this.name });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 访问@input属性name=' + this.name });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 访问@input属性name=' + this.name });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 访问@input属性name=' + this.name });
}
ngAfterViewChecked() {
this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 访问@input属性name=' + this.name });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 访问@input属性name=' + this.name });
}
}
Results of the
explain and summarize
We add an input attribute to the Hello component. When the parent component AppComponent is initialized, the initial value is assigned to the name. Only under angular processing, the binding of the input attribute occurs before the Hello component is initialized (of course, during the Hello component lifecycle call process) , the parent component may change the name at any time).
- Note that after the first OnChanges is triggered, that is, after the initial value of the variable is passed, in some cases, we will adjust the value of the passed variable in the parent component through logic, then the OnChanges callback will be triggered again immediately, and this callback The callbacks to OnInit, AfterContentInit, etc. of the HelloComponent component are called in chronological order. That is, the triggering of OnChanges has nothing to do with AfterContentInit, whether AfterViewInit has completed an execution.
We also added the execution of the life cycle to the AppComponent component just now. What will be the result?
lifecycle-hook&child&parent&Input
Change the source code as follows
export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
name = 'Angular ' + VERSION.major;
messages = [];
subject = window['subject'];
constructor() {
this.subject.subscribe((item) => { this.messages.push(item); });
this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance, 访问@input属性name=' + this.name });
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnChanges' });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnInit' });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngDoCheck' });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentInit' });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentChecked' });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewInit' });
}
ngAfterViewChecked() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewChecked' });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnDestroy' });
}
}
Results of the
explain and summarize
Phenomenon:
- The lifecycle callback of the parent component AppComponent is divided into two parts by the child component.
- The execution order of the constructor is the parent component first, then the child component, and then the other function callbacks of the life cycle.
- The parent component's ngOnChanges (if any, the first time), ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked will be executed earlier.
- The parent component name value is passed to the child component, triggering the OnChanges of the child component.
- The life cycle of the child component is executed, and then the ngAfterViewInit and ngAfterViewChecked of the parent component are executed.
Key Points:
parent component passing the binding value to the child component is triggered when the parent component's life cycle executes to ngAfterContentChecked, which is very important
- This means that if there is logic in the life cycle of the child component (for example: OnInit) to handle dependent passed variables, the latest passed values may not be available. (Because of this, friends are often confused, which is also related to not understanding the applicable scenarios of the Init hook)
- In parent-child components, AfterViewInit will wait until the life cycle of all child components is completed before executing, (this feature should be fully exploited and utilized).
Next, let's see how Angular handles lifecycle callbacks in the presence of inherited components.
life-hook&child&parent&inheritComponent&input
Change the source code as follows:
...
// 这一次我们添加了一个BaseComponent作为Hello组件的基类,在Angular中是以Directive来装饰的
// 使用Directive的好处
// Angular组件继承不会继承元数据,可以使用directive装饰器元数据可配置空来避免配置多余的元数据
// Directive是Component装饰器的基类,基本无缝替换
@Directive()
export class BaseComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked,
AfterViewInit, AfterViewChecked, OnDestroy {
subject = window['subject'];
constructor() {
this.subject.next({ type: 'constructor exec', content: 'BaseComponent class instance' });
}
_name: string = '';
@Input() set name(n: string) {
this.subject.next({ type: '@input', content: 'set base name update' });
this._name = n;
}
get name() {
// 非必要不定义getter或者不放逻辑,访问次数非常多
// this.subject.next({ type: 'tpl binding variable get', content: 'get name update' });
return this._name;
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnChanges' });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnInit' });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngDoCheck' });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterContentInit' });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterContentChecked' });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterViewInit' });
}
ngAfterViewChecked() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterViewChecked' });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnDestroy' });
}
}
// HelloComponent 是AppComponent的子视图组件, 同时也是BaseComponent的子类
...
@Component({
selector: 'hello',
template: `<h1>Hi, {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent extends BaseComponent {
_name: string = '';
subject = window['subject'];
@Input() set name(n: string) {
this.subject.next({ type: '@input', content: 'set name update' });
this._name = n;
}
get name() {
// this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用
return this._name;
}
messages = [];
constructor() {
super();
this.subject.next({ type: 'constructor exec', content: 'class instance, 访问@input属性name=' + this.name });
}
}
Results of the
explain the induction
Phenomenon and Realization
- The life cycle removes the implementation in HelloComponent of the inheritance system and implements it in the base class BaseComponent
- In any case, the constructor is executed first and depends on the order of execution
- The life cycle in BaseComponent is executed (we know that after inheritance, these life cycle methods can also be called in the Hello component, so does Angular call the subclass or the base class? Please continue to read)
Continue to modify the source code:
// HelloComponent 是AppComponent的子视图组件, 同时也是BaseComponent的子类
...
@Component({
selector: 'hello',
template: `<h1>Hi, {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent extends BaseComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy{
_name: string = '';
subject = window['subject'];
@Input() set name(n: string) {
this.subject.next({ type: '@input', content: 'set name update' });
this._name = n;
}
get name() {
// this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用
return this._name;
}
messages = [];
constructor() {
super();
this.subject.next({ type: 'constructor exec', content: 'class instance, 访问@input属性name=' + this.name });
}
ngOnChanges() {
this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 访问@input属性name=' + this.name });
}
ngOnInit() {
this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 访问@input属性name=' + this.name });
}
ngDoCheck() {
this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 访问@input属性name=' + this.name });
}
ngAfterContentInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 访问@input属性name=' + this.name });
}
ngAfterContentChecked() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 访问@input属性name=' + this.name });
}
ngAfterViewInit() {
this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 访问@input属性name=' + this.name });
}
ngAfterViewChecked() {
this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 访问@input属性name=' + this.name });
}
ngOnDestroy() {
this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 访问@input属性name=' + this.name });
}
}
We also implement these life cycles for subclasses to see if Angualr executes the subclass or the parent class or both, and what is the order of execution?
Results of the
In conclusion:
The phenomenon is that the subclass is executed, but the parent class is not executed
- Explain that the life cycle method, the life cycle method between the subclass and the parent class is also in line with the inheritance principle, there is an overriding situation, the life cycle method in the subclass
The case of template binding variable acquisition
Finally, take a look at the situation that the variable Angular binds to the template is obtained, as shown below
get name() { // 只需要把name的getter中的注释去掉即可
this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用
return this._name;
}
// 另一个,需要把生命周期钩子中打印name字段读取去掉,这样我们就知道name被Angular读取了几次,并在什么时候读取。(有时候,我们会在getter中写一些简单的逻辑,把变量作为计算属性,了解这个对我们知晓name被读取的数量,有很大用处)
explain and summarize
- After the parent component passes the name attribute to the Hello component (executed by @input), the Hello component obtains the name after its own content ngAfterContentChecked, and reads the value of the name after the App component ngAfterViewChecked. The whole process is read twice.
Finally, attach a diagram about the operation and life cycle execution of the Angular project combining the above three components
Finally, if you have doubts or doubts about the above statements or conclusions, you can leave a message below.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。