Angular 生命周期钩子

2025年3月17日 | 阅读13分钟

组件实例有一个生命周期,从 Angular 实例化组件类并渲染组件视图及其子视图开始。生命周期随着变更检测继续,Angular 在数据绑定属性发生更改时进行检查,并根据需要更新视图和组件实例。

当 Angular 销毁组件实例并从 DOM 中移除其渲染的模板时,生命周期结束。指令也有类似的生命周期,因为 Angular 在执行中创建、更新和销毁实例。

您的应用程序可以使用生命周期钩子方法来捕获组件生命周期中的关键事件,或指示新实例开始、在需要时开始检测更改、在部署期间进行更新,并在删除实例之前进行清理。

前提条件

在开始使用生命周期钩子之前,您应该对以下内容有基本的了解

响应生命周期事件

您可以通过实现 Angular 核心库中的一个或多个生命周期钩子接口来响应组件或指令生命周期中的事件。钩子允许您在 Angular 创建、更新或销毁组件或指令实例时,根据需要对其进行操作。

每个接口都定义了一个钩子方法的原型,其名称是接口名称,前面加上 ng。例如,OnInit 接口有一个名为 ngOnInit() 的钩子方法。假设您在组件或指令类中实现了此方法。在这种情况下,Angular 会在第一次检查该组件或指令的输入属性后调用它。

您不需要实现所有生命周期钩子,只需要实现您需要的即可。

生命周期事件顺序

在您的应用程序通过调用构造函数实例化组件或指令后,Angular 会在其生命周期的适当时间点调用您已调用的钩子方法。

Angular 按以下顺序执行钩子方法。您可以使用它来执行以下类型的任务。

钩子方法目的时间
ngOnchange()当 Angular 设置或重置数据绑定输入属性时作出响应。该方法接收一个简单的更改对象,其中包含当前和之前的属性值。
请注意,这种情况发生得非常频繁,因此您在此处执行的任何操作都会对性能产生重大影响。有关详细信息,请参阅本文档中的“使用变更检测钩子”。
ngOnInit()(如果在组件中绑定了输入)以及每当一个或多个数据绑定输入属性发生更改时。
请注意,如果您的组件没有输入或在使用时不提供任何输入,则框架不会调用 ngOnChanges()。
ngOnInit()在 Angular 首次显示数据绑定属性并设置指令或组件的输入属性时,初始化指令或组件。请参阅本文档中的“启动组件或指令”以获取详细信息。在第一次调用 ngOnInit() 后调用一次。ngOnInit() 即使在 ngOnChanges() 未调用时也会调用(当没有模板绑定输入时就是这种情况)。
ngdocheck()检测并响应 Angular 自身可能检测到或未检测到的更改。有关详细信息和示例,请参阅本文档中的“定义自定义变更检测”。在每次变更检测运行时立即在 ngOnChanges() 之后调用,并在第一次运行时立即在 ngOnInit() 之后调用。
ngAfterContentInit()在 Angular 将外部内容投射到组件的视图中,或者在具有指令的视图中作出响应。
有关详细信息和示例,请参阅本文档中的“响应内容更改”。
在第一次调用 ngDoCheck() 后调用一次。
ngOnDestroy()在 Angular 销毁指令或组件之前进行清理。为避免内存泄漏,请取消订阅可观察对象并分离事件处理程序。有关详细信息,请参阅本文档中的“在实例销毁时进行清理”。在 Angular 销毁指令或组件之前立即调用。

生命周期示例集

实时示例通过一系列练习演示了生命周期钩子的使用,这些练习以根组件 AppComponent 控制下的组件形式呈现。在每种情况下,父组件都充当子组件的测试平台,该子组件公开一个或多个生命周期钩子方法。

下表列出了练习及其简要说明。后续章节还使用示例代码来演示特定功能。

组件描述
捉迷藏显示每个生命周期钩子。每个钩子方法都会写入屏幕日志。
间谍展示了如何将生命周期钩子与自定义指令结合使用。SpyDirective 实现 ngOnInit() 和 ngOnDestroy() 钩子,并使用它们来查看元素何时进入或离开当前视图并报告。
OnChanges演示了 Angular 在组件输入属性发生更改时调用 ngOnChanges() 钩子的方式,并显示了如何解释传递给钩子方法的对象。
DoCheck使用自定义变更检测实现 ngDoCheck() 方法。要查看 Angular 调用此钩子的次数,请查看钩子发布的日志中的更改。
AfterView展示了 Angular 对视图的含义。显示 ngAfterViewInit() 和 ngAfterViewChecked() 钩子。
AfterContent展示了如何将外部内容投射到组件中,并将投射的内容与组件的可见子项分开。演示了 ngAfterContentInit() 和 ngAfterContentChecked() 钩子。
Counter演示了组件和指令的组合,每个都带有自己的钩子。

初始化组件

使用 ngOnInit() 方法执行以下初始化任务。

在构造函数外部执行复杂的初始化。组件的制造应该廉价且安全的。最好不要担心新组件会在您决定显示它或您在测试之前就联系远程服务器。

ngOnInit() 是组件获取其初始数据的不错选择。在 Angular 设置输入属性后设置组件。构造函数除了将本地变量初始化为简单值外,不应执行其他任何操作。

请记住,指令的数据绑定输入属性在构造之后才设置。如果您需要基于这些属性初始化指令,请在 ngOnInit() 运行时设置它们。

ngOnChanges() 方法是您首次访问这些属性的方式。Angular 在 ngOnInit() 之前调用 ngOnChanges(),但之后会多次调用。它只调用 ngOnInit() 一次。

在实例销毁时进行清理

将清理逻辑放在 ngOnDestroy() 中,该逻辑应在 Angular 销毁指令之前运行。

这是释放不会自动垃圾回收的资源的地方。如果忽略这样做,您将面临内存泄漏的风险。

  • 取消订阅可观察对象和 DOM 事件。
  • 停止间隔计时器。
  • 指令注销已向全局或应用程序服务注册的所有回调。

ngOnDestroy() 方法也有时间通知应用程序的另一部分组件正在消失。

一般示例

以下示例演示了各种生命周期事件的调用顺序和相对频率,以及钩子如何单独或一起用于组件和指令。

所有生命周期事件的顺序和频率

为了展示 Angular 如何按照预期顺序调用钩子,PeekABooComponent 会在组件中显示所有钩子。

下面的快照显示了在单击“创建”按钮和“销毁”按钮后日志的状态。

Angular Lifecycle hooks

日志消息的顺序遵循定义的钩子调用顺序:onchange、oninit、doCheck (3x)、afterContentInit、aftercontentChecked (3x)、afterviewInit、afterviewChecked (3x) 和 onDestroy。

请注意,日志确认输入属性(name 属性)在创建时没有指定值。onInit() 方法可以访问输入属性以进行进一步的初始化。

用户单击“更新英雄”按钮,日志将显示另一个

OnChange 和另外三个 DoCheck、AfterContentCheck 和 AfterViewCheck 的三倍。请注意,这三个钩子经常触发,因此保持它们的逻辑尽可能精简非常重要。

使用指令观察 DOM。

spy 示例演示了如何将组件的钩子方法用于指令。SpyDirective 实现两个钩子 ngOnInit() 和 ngOnDestroy() 来检测当前视图中可见的元素。

此模板在由原生 SpyComponent 管理的 ngFor 英雄重复器中的 <div> 上实现了 SpyDirective。

该实例不执行任何初始化或清理。它通过记录指令的实例化和销毁时间来跟踪场景中元素的出现和消失。

这样的 spy 指令可以提供对您无法直接更改的 DOM 对象的洞察。您可能无法修改原始 <div> 的实现或修改任何第三方组件。但是,您可以使用指令查看这些元素。

该指令定义了 ngOnInit() 和 ngOnDestroy() 钩子,它们通过注入的 LoggerService 将消息记录到父级。

您可以将 spy 应用于任何父级或组件元素,并看到它与该元素同时被初始化和销毁。这里,它被附加到重复的英雄 <div>

src/app/spy.component.html

每个 spy 的构建和销毁钩子都通过日志条目表示附加的英雄 <div> 的出现和消失。添加一个英雄会创建一个新的英雄 <div>。spy 的 ngOnInit() 会记录该事件。

重置按钮会清除英雄列表。Angular 会从 DOM 中移除所有英雄 <div> 元素并立即销毁它们的 spy 指令。spy 的 ngOnDestroy() 方法会报告其最后的时刻。

同时使用组件和指令钩子

在此示例中,CounterComponent 使用 ngOnChanges() 方法来记录每次父组件增加其 InputCount 属性时发生的更改。

此示例将 SpyDirective 应用于上一个示例中的 CounterComponent 日志,以查看日志条目的创建和销毁。

使用变更检测钩子

当 Angular 检测到输入属性发生更改时,它会调用组件或指令的 ngOnChanges() 方法。onchange 示例通过监视 onchange() 钩子来演示这一点。

ngOnChanges() 方法接受一个对象,该对象将每个已更改的属性名称映射到一个简单的更改对象,该对象包含当前和之前的属性值。此钩子会迭代已更改的属性并记录它们。

示例组件 OnChangesComponent 具有两个输入属性:hero 和 power。src/app/on-changes.component.ts

以下是用户进行更改时的示例。

Angular Lifecycle hooks

当 power 属性的字符串值发生变化时,会显示日志条目。但是,请注意,ngOnChanges() 方法不会捕获 hero 的更改。这是因为 Angular 只在输入属性的值发生更改时调用钩子。在这种情况下,hero 是输入属性,hero 属性的值是对 hero 对象的引用。当其 name 属性的值发生更改时,对象引用没有改变。

响应视图更改

当 Angular 在变更检测期间遍历视图层次结构时,它需要确保一个子项的更改不会试图引起其父项的更改。由于单向数据流的工作方式,此类更改将无法正确完成。

如果您需要进行一项反转预期数据流的更改,则必须触发新的变更检测周期以提交该更改。示例显示了如何安全地进行此类更改。

Afterview 示例探索了 afterviewInit() 和 afterviewChecked() 钩子,Angular 在创建组件的子视图后会调用它们。这是一个显示 <input> 中英雄姓名的子视图

ChildViewComponent

以下钩子会根据子视图中值的变化而采取行动,只能通过使用 @ViewChild 属性查询子视图来访问。

AfterViewComponent(类摘录)

响应估算内容更改

内容投影是一种将外部 HTML 内容导入组件,并将该内容插入到组件模板中的指定位置。您可以通过查看以下结构来识别模板中的内容投影。

  • 组件元素标签之间的 HTML。
  • 组件模板中存在 <ng-content> 标签。

AngularJS 开发者将此技术称为“transclusion”(指令)。

AfterContent 示例探索了 afterContentInit() 和 afterContentChecked() 钩子,Angular 在将外部内容投射到组件后会调用它们。

考虑前面 Afterview 示例的这种变体。这次,它没有将子视图包含在模板中,而是从 AfterContent 组件的父级导入内容。以下是基本模板。

请注意,<app-child> 标签位于 <after-content> 标签之间。除非您打算将内容投射到组件中,否则不要在组件的元素标签之间放置内容。

现在,让我们看一下组件的模板。

<ng-content> 标签是外部内容的占位符。它告诉 Angular 在何处插入该内容。在这种情况下,推断的内容是父级中的 <app-child>。

Angular Lifecycle hooks

使用 material 后的钩子

after material 钩子与 after-view 钩子类似。主要区别在于子组件。

  • Afterview 钩子与 ViewChildren 相关,其元素标签出现在组件的模板中。
  • aftercontent 钩子与 content children 相关,content children 是 Angular 投射到组件中的子组件。

以下 AfterContent 钩子会根据 ContentChild 中的值变化采取行动,可以通过使用 @ContentChild 装饰的属性对其进行查询来访问。

定义自定义变更检测

要监视 ngOnChanges() 无法捕获的更改,您可以实现自己的更改检查,如 DoCheck 示例所示。此示例显示了如何使用 ngDoCheck() 钩子来检测并响应 Angular 自身未捕获的更改。

DoCheck 采样器扩展了 OnChanges 采样器,并带有以下 ngDoCheck() 钩子

此代码检查一些感兴趣的值,获取它们当前的相对位置并与先前的值进行比较。当 hero 或 power 没有实际更改时,它会向日志写入一条特殊消息,以便您可以看到 DoCheck() 调用了多少次。结果很说明问题。

Angular Lifecycle hooks

虽然 ngDoCheck() 钩子可以检测到 hero 的名称何时发生更改,但它非常昂贵。此钩子被调用得非常频繁——在每次变更检测周期之后,无论更改发生在何处。在此示例中,在用户可以执行任何操作之前,它已被调用了二十多次。

这些初始检查中的大多数是由 Angular 对页面上其他不相关数据的首次渲染触发的。只需将光标移到另一个 <input> 就会触发调用。相对较少的调用会揭示相关数据的实际更改。如果您使用此钩子,您的实现必须非常轻量级,否则用户体验会受到影响。

Angular 2 应用程序从开始到结束会经历一套完整的流程或生命周期。

下图显示了 Angular Two 应用程序生命周期的整个过程。

Angular Lifecycle hooks

以下是每个生命周期钩子的描述。

ngOnChanges - 当数据绑定属性的值发生更改时,会调用此方法。

ngOnInit - 在 Angular 显示数据绑定属性后,指令/组件初始化时调用此方法。

ngDoCheck - 用于检测并响应 Angular 自动或手动检测到的更改。

ngAfterContentInit - 在 Angular 将外部内容投射到组件的视图后,在 React 中调用此方法。

ngAfterContentChecked - 在 Angular 检查组件中的估算内容后,在 React 中调用此方法。

ngAfterViewInit - 在 Angular 初始化了组件视图及其子视图后,在 React 中调用此方法。

ngAfterViewChecked - 在 Angular 检查了组件视图及其子视图后,在 React 中调用此方法。

ngOnDestroy - 这是在 Angular 销毁指令/组件之前的清理步骤。

以下是实现生命周期钩子的一个示例。在 App.component.ts 文件中,输入以下代码。

在上面的程序中,我们专门调用了 ngOnInit 生命周期钩子来引用它的值。value 参数必须设置为“hello”。

保存所有代码更改并刷新浏览器后,您将获得以下输出。

Angular Lifecycle hooks
下一主题Angular RouterLink