06_核心语法:生命周期钩子
核心语法:完整生命周期钩子详解
Angular 组件从创建到销毁会经历一系列生命周期阶段,每个阶段都对应一个钩子方法。这些钩子为开发者提供了在特定时机执行代码的能力,完整掌握它们对于构建高效、可维护的组件至关重要。
生命周期钩子总览
| 钩子方法 | 调用时机 | 调用频率 | 主要用途 |
|---|---|---|---|
| ngOnChanges | 输入属性(@Input)初始化或变化时 | 多次(输入属性变化时) | 响应输入属性变化,处理初始值和变化值 |
| ngOnInit | 组件初始化完成后(首次 ngOnChanges 后) | 一次 | 执行初始化逻辑(加载数据、订阅事件) |
| ngDoCheck | 每次变更检测运行时 | 多次(高频) | 实现自定义变更检测逻辑 |
| ngAfterContentInit | 内容投影(ng-content)完成初始化后 | 一次 | 访问投影内容的 DOM 元素 |
| ngAfterContentChecked | 内容投影部分完成变更检测后 | 多次 | 对投影内容进行后续处理 |
| ngAfterViewInit | 组件视图及其子视图渲染完成后 | 一次 | 访问组件视图的 DOM 元素 |
| ngAfterViewChecked | 组件视图及其子视图完成变更检测后 | 多次 | 对视图进行后续处理或调整 |
| ngOnDestroy | 组件销毁前 | 一次 | 清理资源(取消订阅、移除事件监听) |
钩子方法详解与实战
(1)ngOnChanges:响应输入属性变化
当组件的输入属性(通过@Input()装饰器定义)发生变化时触发,是处理输入属性的主要钩子。
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-product-card',
standalone: true,
template: \`
\<h3>{{ productName }}\</h3>
\<p>价格:{{ price | currency }}\</p>
\<p>折扣:{{ discountPercent }}%\</p>
\`
})
export class ProductCardComponent implements OnChanges {
@Input() productId?: number;
@Input() discountPercent = 0;
productName = '';
price = 0;
// 输入属性变化时调用
ngOnChanges(changes: SimpleChanges) {
// 检查productId是否有变化
if (changes\['productId']) {
const newId = changes\['productId'].currentValue;
const oldId = changes\['productId'].previousValue;
if (newId !== oldId) {
this.loadProductData(newId); // 加载新产品数据
}
}
// 检查折扣变化
if (changes\['discountPercent'] && !changes\['discountPercent'].firstChange) {
console.log(\`折扣从\${changes\['discountPercent'].previousValue}%变为\${this.discountPercent}%\`);
}
}
private loadProductData(productId: number) {
// 模拟API请求
setTimeout(() => {
this.productName = \`商品\${productId}\`;
this.price = 100 \* productId;
}, 300);
}
}
SimpleChanges对象包含输入属性的变化信息,常用属性:
-
currentValue:当前值 -
previousValue:先前值(首次变化时为undefined) -
firstChange:是否为首次变化
(2)ngOnInit:组件初始化
在组件初始化完成后调用,此时所有输入属性已设置完毕,适合执行初始化逻辑。
import { Component, OnInit, Input } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-user-details',
standalone: true,
imports: \[],
template: \`
\<p>姓名:{{ user?.name }}\</p>
\<p>邮箱:{{ user?.email }}\</p>
\`
})
export class UserDetailsComponent implements OnInit {
@Input() userId!: number;
user: any;
constructor(private http: HttpClient) {}
ngOnInit() {
// 初始化时加载用户数据
this.http.get(\`/api/users/\${this.userId}\`)
.subscribe(data => {
this.user = data;
});
}
}
注意:避免在构造函数中执行复杂逻辑,构造函数应仅用于依赖注入,初始化逻辑应放在 ngOnInit 中。
(3)ngDoCheck:自定义变更检测
在每次 Angular 变更检测周期中调用,可用于检测 Angular 默认机制无法捕获的变化。
import { Component, DoCheck } from '@angular/core';
@Component({
selector: 'app-custom-check',
standalone: true,
template: \`
\<p>数组长度:{{ items.length }}\</p>
\<p>最后修改:{{ lastChange }}\</p>
\`
})
export class CustomCheckComponent implements DoCheck {
items: number\[] = \[1, 2, 3];
lastChange = '从未修改';
previousLength = 3;
// 每次变更检测时调用
ngDoCheck() {
// 检测数组长度变化
if (this.items.length !== this.previousLength) {
this.lastChange = new Date().toLocaleTimeString();
this.previousLength = this.items.length;
}
}
addItem() {
this.items.push(Math.random());
}
}
注意:ngDoCheck 调用频率极高(每次用户交互、定时器触发等都会调用),应避免在其中执行耗时操作。
(4)ngAfterContentInit 与 ngAfterContentChecked:内容投影处理
用于处理通过ng-content投影到组件中的内容:
import { Component, AfterContentInit, AfterContentChecked, ContentChild } from '@angular/core';
import { ElementRef } from '@angular/platform-browser';
@Component({
selector: 'app-content-container',
standalone: true,
template: \`
\<div class="container">
\<ng-content select="\[content-header]">\</ng-content>
\<ng-content select="\[content-body]">\</ng-content>
\</div>
\`
})
export class ContentContainerComponent implements AfterContentInit, AfterContentChecked {
// 获取投影内容中的元素
@ContentChild('headerRef') headerElement?: ElementRef;
// 内容投影初始化完成后调用(一次)
ngAfterContentInit() {
console.log('投影内容初始化完成');
this.applyHeaderStyle();
}
// 投影内容变更检测完成后调用(多次)
ngAfterContentChecked() {
// 每次投影内容变化后重新应用样式
this.applyHeaderStyle();
}
private applyHeaderStyle() {
if (this.headerElement) {
this.headerElement.nativeElement.style.color = 'blue';
}
}
}
使用该组件时的投影内容:
\<app-content-container>
\<h2 #headerRef content-header>这是标题\</h2>
\<p content-body>这是内容\</p>
\</app-content-container>
(5)ngAfterViewInit 与 ngAfterViewChecked:视图处理
用于处理组件自身视图及子组件视图的初始化和变更:
import { Component, AfterViewInit, AfterViewChecked, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-chart-container',
standalone: true,
template: \`
\<div #chartContainer class="chart" style="width: 100%; height: 300px;">\</div>
\`
})
export class ChartContainerComponent implements AfterViewInit, AfterViewChecked {
@ViewChild('chartContainer') chartContainer?: ElementRef;
chart?: any;
containerWidth = 0;
// 视图初始化完成后调用(一次)
ngAfterViewInit() {
if (this.chartContainer) {
this.containerWidth = this.chartContainer.nativeElement.offsetWidth;
this.initializeChart(); // 初始化图表
}
}
// 视图变更检测完成后调用(多次)
ngAfterViewChecked() {
if (this.chartContainer) {
const newWidth = this.chartContainer.nativeElement.offsetWidth;
// 检测容器宽度变化,更新图表
if (Math.abs(newWidth - this.containerWidth) > 10) {
this.containerWidth = newWidth;
this.resizeChart();
}
}
}
private initializeChart() {
// 模拟初始化图表库
this.chart = {
render: (element: any) => console.log('图表渲染到', element),
resize: () => console.log('图表已调整大小')
};
this.chart.render(this.chartContainer?.nativeElement);
}
private resizeChart() {
this.chart?.resize();
}
}
注意:在视图钩子中修改数据可能导致 "表达式已检查过" 错误,如需修改可使用
ChangeDetectorRef的
detectChanges()方法。
(6)ngOnDestroy:组件清理
在组件被销毁前调用,用于释放资源,防止内存泄漏:
import { Component, OnDestroy, OnInit } from '@angular/core';
import { interval, Subscription } from 'rxjs';
import { DocumentVisibilityService } from './document-visibility.service';
@Component({
selector: 'app-resource-intensive',
standalone: true,
template: \`\<p>数据点:{{ dataPoints }}\</p>\`
})
export class ResourceIntensiveComponent implements OnInit, OnDestroy {
dataPoints = 0;
private dataSubscription?: Subscription;
private visibilitySubscription?: Subscription;
constructor(private visibilityService: DocumentVisibilityService) {}
ngOnInit() {
// 模拟密集型数据处理
this.dataSubscription = interval(100).subscribe(() => {
this.dataPoints++;
});
// 监听页面可见性变化
this.visibilitySubscription = this.visibilityService.visibilityChange
.subscribe(isVisible => {
console.log('页面可见性变化:', isVisible);
});
}
ngOnDestroy() {
// 取消订阅,释放资源
this.dataSubscription?.unsubscribe();
this.visibilitySubscription?.unsubscribe();
// 移除全局事件监听
window.removeEventListener('resize', this.handleResize);
console.log('组件已销毁');
}
private handleResize = () => {
// 处理窗口大小变化
};
}
生命周期钩子执行顺序
单个组件的钩子执行顺序:
-
首次渲染:
ngOnChanges→ngOnInit→ngDoCheck→ngAfterContentInit→ngAfterContentChecked→ngAfterViewInit→ngAfterViewChecked -
输入属性变化:
ngOnChanges→ngDoCheck→ngAfterContentChecked→ngAfterViewChecked -
组件销毁:
ngOnDestroy
与独立组件和 Signals 的协同
在 Angular v20 的独立组件中,生命周期钩子的行为保持一致,但结合 Signals 时需要注意:
-
依赖 Signals 的计算应放在
computed中,而非ngDoCheck -
ngOnDestroy中仍需清理手动创建的订阅(如effect中使用的外部资源) -
独立组件中无需考虑模块生命周期,钩子仅与组件自身相关
import { Component, OnDestroy, effect } from '@angular/core';
import { signal } from '@angular/core';
@Component({
selector: 'app-signal-lifecycle',
standalone: true,
template: \`\<p>计数:{{ count() }}\</p>\`
})
export class SignalLifecycleComponent implements OnDestroy {
count = signal(0);
private intervalId?: number;
constructor() {
// 使用effect响应Signal变化
effect(() => {
console.log('当前计数:', this.count());
});
this.intervalId = window.setInterval(() => {
this.count.update(c => c + 1);
}, 1000);
}
ngOnDestroy() {
// 清理定时器
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
通过完整掌握这些生命周期钩子,你可以精确控制组件在不同阶段的行为,优化资源使用,避免内存泄漏,构建更健壮的 Angular 应用。

浙公网安备 33010602011771号