933 lines
42 KiB
Plaintext
933 lines
42 KiB
Plaintext
block includes
|
||
include ../_util-fns
|
||
|
||
- var top="vertical-align:top"
|
||
|
||
:marked
|
||
# Component Lifecycle
|
||
|
||
# 组件生命周期
|
||
|
||
A Component has a lifecycle managed by Angular itself. Angular creates it, renders it, creates and renders its children,
|
||
checks it when its data-bound properties change, and destroys it before removing it from the DOM.
|
||
|
||
每个组件都有一个被Angular管理的生命周期。Angular创建它,渲染它,创建并渲染它的子组件,在它被绑定的属性发生变化时检查它,并在它从DOM中被移除前销毁它。
|
||
|
||
Angular offers **component lifecycle hooks**
|
||
that give us visibility into these key moments and the ability to act when they occur.
|
||
|
||
Angular提供了**组件生命周期钩子**,把这些关键时刻暴露出来,赋予我们在它们发生时采取行动的能力。
|
||
|
||
We cover these hooks in this chapter and demonstrate how they work in code.
|
||
|
||
在本章中,我们将全面讲解这些钩子,在代码层面演示它们是如何工作的。
|
||
|
||
* [The lifecycle hooks](#hooks-overview)
|
||
|
||
* [生命周期钩子概览](#hooks-overview)
|
||
|
||
* [The hook-call sequence](#hook-sequence)
|
||
|
||
* [钩子的调用顺序](#hook-sequence)
|
||
|
||
* [Other Angular lifecycle hooks](#other-lifecycles)
|
||
|
||
* [其它Angular生命周期钩子](#other-lifecycles)
|
||
|
||
* [The lifecycle sample](#the-sample)
|
||
|
||
* [范例](#the-sample)
|
||
|
||
* [All](#peek-a-boo)
|
||
|
||
* [全部](#peek-a-boo)
|
||
|
||
* [Spying OnInit and OnDestroy](#spy)
|
||
|
||
* [窥测OnInit和OnDestroy](#spy)
|
||
|
||
* [OnChanges](#onchanges)
|
||
|
||
* [OnChanges](#onchanges)
|
||
|
||
* [DoCheck](#docheck)
|
||
|
||
* [DoCheck](#docheck)
|
||
|
||
* [AfterViewInit and AfterViewChecked](#afterview)
|
||
|
||
* [AfterViewInit和AfterViewChecked](#afterview)
|
||
|
||
* [AfterContentInit and AfterContentChecked](#aftercontent)
|
||
|
||
* [AfterContentInit和AfterContentChecked](#aftercontent)
|
||
|
||
p Try the #[+liveExampleLink2()].
|
||
|
||
p 试一试#[+liveExampleLink2('在线例子')]。
|
||
|
||
a#hooks-overview
|
||
.l-main-section
|
||
:marked
|
||
## Component lifecycle Hooks
|
||
|
||
## 组件生命周期钩子
|
||
|
||
Directive and component instances have a lifecycle
|
||
as Angular creates, updates, and destroys them.
|
||
|
||
指令和组件的实例有一个生命周期:新建、更新和销毁。
|
||
|
||
Developers can tap into key moments in that lifecycle by implementing
|
||
one or more of the *Lifecycle Hook* interfaces in the Angular `core` library.
|
||
|
||
通过实现一个或多个Angular `core`库里定义的*生命周期钩子*接口,开发者可以介入该生命周期中的这些关键时刻。
|
||
|
||
Each interface has a single hook method whose name is the interface name prefixed with `ng`.
|
||
For example, the `OnInit` interface has a hook method named `ngOnInit`.
|
||
We might implement it in a component class like this:
|
||
|
||
每个接口都有唯一的一个钩子方法,它们的名字是由接口名再加上`ng`前缀构成的。比如,`OnInit`接口的钩子方法叫做`ngOnInit`。
|
||
我们可以在一个组件类中实现它,就像这样:
|
||
+makeExample('lifecycle-hooks/ts/app/peek-a-boo.component.ts', 'ngOnInit', 'peek-a-boo.component.ts (excerpt)')(format='.')
|
||
:marked
|
||
No directive or component will implement all of them and some of the hooks only make sense for components.
|
||
Angular only calls a directive/component hook method *if it is defined*.
|
||
|
||
没有指令或者组件会实现所有这些接口,并且有些钩子只对组件有意义。只有在指令/组件中*定义过的*那些钩子方法才会被Angular调用。
|
||
block optional-interfaces
|
||
.l-sub-section
|
||
:marked
|
||
### Interface optional?
|
||
|
||
### 接口是可选的?
|
||
|
||
The interfaces are optional for JavaScript and Typescript developers from a purely technical perspective.
|
||
The JavaScript language doesn't have interfaces.
|
||
Angular can't see TypeScript interfaces at runtime because they disappear from the transpiled JavaScript.
|
||
|
||
从纯技术的角度讲,接口对JavaScript和TypeScript的开发者都是可选的。JavaScript语言本身没有接口。
|
||
Angular在运行时看不到TypeScript接口,因为它们在编译为JavaScript的时候已经消失了。
|
||
|
||
Fortunately, they aren't necessary.
|
||
We don't have to add the lifecycle hook interfaces to our directives and components to benefit from the hooks themselves.
|
||
|
||
幸运的是,它们也不是必须的。我们不需要在指令和组件上添加生命周期钩子接口就能获得钩子带来的好处。
|
||
|
||
Angular instead inspects our directive and component classes and calls the hook methods *if they are defined*.
|
||
Angular will find and call methods like `ngOnInit()`, with or without the interfaces.
|
||
|
||
Angular会去检测我们的指令和组件的类,一旦发现钩子方法被定义了,就调用它们。
|
||
Agnular会找到并调用像`ngOnInit()`这样的钩子方法,有没有接口无所谓。
|
||
|
||
Nonetheless, we strongly recommend adding interfaces to TypeScript directive classes
|
||
in order to benefit from strong typing and editor tooling.
|
||
|
||
虽然如此,我们还是强烈建议你在TypeScript指令类中添加接口,以获得强类型和IDE等编辑器带来的好处。
|
||
|
||
:marked
|
||
Here are the component lifecycle hook methods:
|
||
|
||
这里是组件生命周期钩子的方法列表:
|
||
|
||
### Directives and Components
|
||
|
||
### 指令和组件
|
||
|
||
table(width="100%")
|
||
col(width="20%")
|
||
col(width="80%")
|
||
tr
|
||
th
|
||
p Hook
|
||
p 钩子
|
||
th
|
||
p Purpose
|
||
p 用途
|
||
tr(style=top)
|
||
td ngOnInit
|
||
td
|
||
:marked
|
||
Initialize the directive/component after Angular initializes the data-bound input properties.
|
||
|
||
当Angular初始化完数据绑定的输入属性后,用来初始化指令或组件。
|
||
tr(style=top)
|
||
td ngOnChanges
|
||
td
|
||
:marked
|
||
Respond after Angular sets a data-bound input property.
|
||
The method receives a `changes` object of current and previous values.
|
||
|
||
当Angular设置了一个被绑定的输入属性后触发。该回调方法会收到一个包含当前值和原值的`changes`对象。
|
||
tr(style=top)
|
||
td ngDoCheck
|
||
td
|
||
:marked
|
||
Detect and act upon changes that Angular can or won't
|
||
detect on its own. Called every change detection run.
|
||
|
||
用来监测所有变化(无论是Angular本身能检测的还是无法检测的),并作出相应行动。在每次执行“变更检测”时被调用。
|
||
tr(style=top)
|
||
td ngOnDestroy
|
||
td
|
||
:marked
|
||
Cleanup just before Angular destroys the directive/component.
|
||
Unsubscribe observables and detach event handlers to avoid memory leaks.
|
||
|
||
在Angular销毁指令或组件之前做一些清理工作,比如退订可观察对象和移除事件处理器,以免导致内存泄露。
|
||
|
||
:marked
|
||
### Components only
|
||
|
||
### 只适用于组件
|
||
|
||
table(width="100%")
|
||
col(width="20%")
|
||
col(width="80%")
|
||
tr
|
||
th
|
||
p Hook
|
||
p 钩子
|
||
th
|
||
p Purpose
|
||
p 用途
|
||
tr(style=top)
|
||
td ngAfterContentInit
|
||
td
|
||
:marked
|
||
After Angular projects external content into its view.
|
||
|
||
当Angular把外来内容投影进自己的视图之后调用。
|
||
tr(style=top)
|
||
td ngAfterContentChecked
|
||
td
|
||
:marked
|
||
After Angular checks the bindings of the external content that it projected into its view.#
|
||
|
||
当Angular检查完那些投影到自己视图中的外来内容的数据绑定之后调用。
|
||
tr(style=top)
|
||
td ngAfterViewInit
|
||
td
|
||
:marked
|
||
After Angular creates the component's view(s).
|
||
|
||
在Angular创建完组件的视图后调用。
|
||
tr(style=top)
|
||
td ngAfterViewChecked
|
||
td
|
||
:marked
|
||
After Angular checks the bindings of the component's view(s).
|
||
|
||
在Angular检查完组件视图中的绑定后调用。
|
||
:marked
|
||
Angular does not call the hook methods in this order.
|
||
|
||
Angular并不会按照表中所列的顺序调用这些钩子方法。
|
||
|
||
a(id="hook-sequence")
|
||
.l-main-section
|
||
:marked
|
||
## Lifecycle sequence
|
||
|
||
## 生命周期的顺序
|
||
|
||
*After* Angular creates a component/directive by `new`-ing its constructor,
|
||
it calls the lifecycle hook methods in the following sequence at specific moments:
|
||
|
||
当Angular使用构造函数新建一个组件或指令后,就会按下面的顺序在特定时刻调用这些生命周期钩子方法:
|
||
table(width="100%")
|
||
col(width="20%")
|
||
col(width="80%")
|
||
tr
|
||
th
|
||
p Hook
|
||
p 钩子
|
||
th
|
||
p Timing
|
||
p 调用时机
|
||
tr(style=top)
|
||
td ngOnChanges
|
||
td
|
||
:marked
|
||
before `ngOnInit` and when a data-bound input property value changes.
|
||
|
||
当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在`ngOnInit`之前。
|
||
tr(style=top)
|
||
td ngOnInit
|
||
td
|
||
:marked
|
||
after the first `ngOnChanges`.
|
||
|
||
在第一轮`ngOnChanges`完成之后调用。
|
||
(译注:也就是说当每个输入属性的值都被触发了一次ngOnChanges之后才会调用ngOnInit,此时所有输入属性都已经有了正确的初始绑定值)
|
||
tr(style=top)
|
||
td ngDoCheck
|
||
td
|
||
:marked
|
||
during every Angular change detection cycle.
|
||
|
||
在每个Angular变更检测周期中调用。
|
||
tr(style=top)
|
||
td ngAfterContentInit
|
||
td
|
||
:marked
|
||
after projecting content into the component.
|
||
|
||
当把内容投影进组件之后调用。
|
||
tr(style=top)
|
||
td ngAfterContentChecked
|
||
td
|
||
:marked
|
||
after every check of projected component content.
|
||
|
||
每次完成被投影组件内容的变更检测之后调用。
|
||
tr(style=top)
|
||
td ngAfterViewInit
|
||
td
|
||
:marked
|
||
after initializing the component's views and child views.
|
||
|
||
初始化完组件视图及其子视图之后调用。
|
||
tr(style=top)
|
||
td ngAfterViewChecked
|
||
td
|
||
:marked
|
||
after every check of the component's views and child views.
|
||
|
||
每次做完组件视图和子视图的变更检测之后调用。
|
||
tr(style=top)
|
||
td ngOnDestroy
|
||
td
|
||
:marked
|
||
just before Angular destroys the directive/component.
|
||
|
||
当Angular每次销毁指令/组件之前调用。
|
||
|
||
a(id="other-lifecycles")
|
||
.l-main-section
|
||
:marked
|
||
## Other lifecycle hooks
|
||
|
||
## 其他生命周期钩子
|
||
|
||
Other Angular sub-systems may have their own lifecycle hooks apart from the component hooks we've listed.
|
||
The router, for instance, also has it's own [router lifecycle hooks](router.html#router-lifecycle-hooks)
|
||
that allow us to tap into specific moments in route navigation.
|
||
|
||
Angular的其它子系统除了有我们已经列出来的这些组件钩子外,还可能有它们自己的生命周期钩子。比如路由器,它就有自己的[路由器生命周期钩子](router.html#router-lifecycle-hooks),
|
||
为我们提供了在路由导航中的特定时机进行介入的能力。
|
||
|
||
A parallel can be drawn between `ngOnInit` and `routerOnActivate`.
|
||
Both are prefixed so as to avoid collision, and both run right when a component is 'booting' up.
|
||
|
||
`ngOnInit`和`routerOnActivate`是并行执行的。它们都用前缀来防止冲突,而且都是在一个组件刚刚“启动”时被调用的。
|
||
|
||
3rd party libraries might implement their hooks as well in order to give us, the developers, more
|
||
control over how these libraries are used.
|
||
|
||
第三方库也可能会实现它们自己的钩子,以便让我们这些开发者在使用时能做更多的控制。
|
||
|
||
a#the-sample
|
||
.l-main-section
|
||
h2 Lifecycle exercises
|
||
h2 生命周期练习
|
||
p.
|
||
The #[+liveExampleLink()]
|
||
demonstrates the lifecycle hooks in action through a series of exercises
|
||
presented as components under the control of the root `AppComponent`.
|
||
|
||
p #[+liveExampleLink('运行在线范例')]通过在受控于根组件`AppComponent`的一些组件上进行的一系列练习,演示了生命周期钩子的运作方式。
|
||
|
||
:marked
|
||
They follow a common pattern: a *parent* component serves as a test rig for
|
||
a *child* component that illustrates one or more of the lifecycle hook methods.
|
||
|
||
它们遵循了一个常用的模式:用*子组件*演示一个或多个生命周期钩子方法,而*父组件*被当作该*子组件*的测试台。
|
||
|
||
Here's a brief description of each exercise:
|
||
|
||
下面是每个练习简短的描述:
|
||
|
||
table(width="100%")
|
||
col(width="20%")
|
||
col(width="80%")
|
||
tr
|
||
th
|
||
p Component
|
||
p 组件
|
||
th
|
||
p Description
|
||
p 描述
|
||
tr(style=top)
|
||
td <a href="#peek-a-boo">Peek-a-boo</a>
|
||
td
|
||
:marked
|
||
Demonstrates every lifecycle hook.
|
||
Each hook method writes to the on-screen log.
|
||
|
||
展示每个生命周期钩子,每个钩子方法都会在屏幕上显示一条日志。
|
||
tr(style=top)
|
||
td <a href="#spy">Spy</a>
|
||
td
|
||
:marked
|
||
Directives have lifecycle hooks too.
|
||
We create a `SpyDirective` that logs when the element it spies upon is
|
||
created or destroyed using the `ngOnInit` and `ngOnDestroy` hooks.
|
||
|
||
指令也同样有生命周期钩子。我们新建了一个`SpyDirective`,利用`ngOnInit`和`ngOnDestroy`钩子,在它所监视的每个元素被创建或销毁时输出日志。
|
||
|
||
We apply the `SpyDirective` to a `<div>` in an `ngFor` *hero* repeater
|
||
managed by the parent `SpyComponent`.
|
||
|
||
我们把`SpyDirective`应用到父组件里的`ngFor`*英雄*重复器(repeater)的`<div>`里面。
|
||
tr(style=top)
|
||
td <a href="#onchanges">OnChanges</a>
|
||
td
|
||
:marked
|
||
See how Angular calls the `ngOnChanges` hook with a `changes` object
|
||
every time one of the component input properties changes.
|
||
Shows how to interpret the `changes` object.
|
||
|
||
这里将会看到:每当组件的输入属性发生变化时,Angular会如何以`changes`对象作为参数去调用`ngOnChanges`钩子。
|
||
展示了该如何理解和使用`changes`对象。
|
||
tr(style=top)
|
||
td <a href="#docheck">DoCheck</a>
|
||
td
|
||
:marked
|
||
Implements an `ngDoCheck` method with custom change detection.
|
||
See how often Angular calls this hook and watch it post changes to a log.
|
||
|
||
实现了一个`ngDoCheck`方法,通过它可以自定义变更检测逻辑。
|
||
这里将会看到:Angular会用什么频度调用这个钩子,监视它的变化,并把这些变化输出成一条日志。
|
||
tr(style=top)
|
||
td <a href="#afterview">AfterView</a>
|
||
td
|
||
:marked
|
||
Shows what Angular means by a *view*.
|
||
Demonstrates the `ngAfterViewInit` and `ngAfterViewChecked` hooks.
|
||
|
||
显示Angular中的*视图*所指的是什么。
|
||
演示了`ngAfterViewInit`和`ngAfterViewChecked`钩子。
|
||
tr(style=top)
|
||
td <a href="#aftercontent">AfterContent</a>
|
||
td
|
||
:marked
|
||
Shows how to project external content into a component and
|
||
how to distinguish projected content from a component's view children.
|
||
Demonstrates the `ngAfterContentInit` and `ngAfterContentChecked` hooks.
|
||
|
||
展示如何把外部内容投影进组件中,以及如何区分“投影进来的内容”和“组件的子视图”。
|
||
演示了`ngAfterContentInit`和`ngAfterContentChecked`钩子。
|
||
tr(style=top)
|
||
td
|
||
p Counter
|
||
p 计数器
|
||
td
|
||
:marked
|
||
Demonstrates a combination of a component and a directive
|
||
each with its own hooks.
|
||
|
||
演示了组件和指令的组合,它们各自有自己的钩子。
|
||
|
||
In this example, a `CounterComponent` logs a change (via `ngOnChanges`)
|
||
every time the parent component increments its input counter property.
|
||
Meanwhile, we apply the `SpyDirective` from the previous example
|
||
to the `CounterComponent` log and watch log entries be created and destroyed.
|
||
|
||
在这个例子中,每当父组件递增它的输入属性`counter`时,`CounterComponent`就会通过`ngOnChanges`记录一条变更。
|
||
同时,我们还把前一个例子中的`SpyDirective`用在`CounterComponent`上,来提供日志,可以同时观察到日志的创建和销毁过程。
|
||
|
||
:marked
|
||
We discuss the exercises in further detail over this chapter as we learn more about the lifecycle hooks.
|
||
|
||
接下来,我们将详细讨论这些练习,以学习更多关于生命周期钩子的知识。
|
||
|
||
a(id="peek-a-boo")
|
||
.l-main-section
|
||
:marked
|
||
## Peek-a-boo: all hooks
|
||
## Peek-a-boo:全部钩子
|
||
The `PeekABooComponent` demonstrates all of the hooks in one component.
|
||
|
||
`PeekABooComponent`组件演示了组件中所有可能存在的钩子。
|
||
|
||
In real life, we'd rarely if ever implement all of the interfaces like this.
|
||
We do so in peek-a-boo in order to watch Angular call the hooks in the expected order.
|
||
|
||
现实中,我们几乎永远不会像这里一样实现所有这些接口。
|
||
我们之所以在peek-a-boo中这么做,只是为了观看Angular是如何按照期望的顺序调用这些钩子的。
|
||
|
||
In this snapshot, we clicked the *Create...* button and then the *Destroy...* button.
|
||
|
||
在下面这个快照中,我们先点击了*Create...*按钮,然后点击了*Destroy...*按钮。
|
||
figure.image-display
|
||
img(src="/resources/images/devguide/lifecycle-hooks/peek-a-boo.png" alt="Peek-a-boo")
|
||
:marked
|
||
The sequence of log messages follows the prescribed hook calling order:
|
||
`OnChanges`, `OnInit`, `DoCheck` (3x), `AfterContentInit`, `AfterContentChecked` (3x),
|
||
`AfterViewInit`, `AfterViewChecked` (3x), and `OnDestroy`.
|
||
|
||
日志信息的日志和所规定的钩子调用顺序是一致的:
|
||
`OnChanges`、`OnInit`、`DoCheck` (3x)、`AfterContentInit`、`AfterContentChecked` (3x)、
|
||
`AfterViewInit`、`AfterViewChecked` (3x)和`OnDestroy`
|
||
.l-sub-section
|
||
:marked
|
||
The constructor isn't an Angular hook *per se*.
|
||
We log in it to confirm that input properties (the `name` property in this case) have no assigned values at construction.
|
||
|
||
构造函数本质上不应该算作Angular的钩子。
|
||
我们把它记录在这里只是为了确认在创建期间那些输入属性(这里是`name`属性)没有被赋值。
|
||
:marked
|
||
Had we clicked the *Update Hero* button, we'd have seen another `OnChanges` and two more triplets of
|
||
`DoCheck`, `AfterContentChecked` and `AfterViewChecked`.
|
||
Clearly these three hooks fire a *lot* and we must keep the logic we put in these hooks
|
||
as lean as possible!
|
||
|
||
如果我们点击*Update Hero*按钮,就会看到另一个`OnChanges`和至少两组`DoCheck`、`AfterContentChecked`和`AfterViewChecked`钩子。
|
||
显然,这三种钩子被触发了*很多次*,所以我们必须让这三种钩子里的逻辑尽可能的精简!
|
||
|
||
Our next examples focus on hook details.
|
||
|
||
我们的下一个例子就聚焦于这些钩子的细节上。
|
||
|
||
.a(id="spy")
|
||
.l-main-section
|
||
:marked
|
||
## Spying *OnInit* and *OnDestroy*
|
||
## 窥探*OnInit*和*OnDestroy*
|
||
|
||
We're going undercover for these two hooks. We want to know when an element is initialized or destroyed,
|
||
but we don't want *it* to know we're watching.
|
||
|
||
我们将揭开这两个钩子的奥秘。我们想知道一个元素是什么时候被初始化或销毁的,但我们不希望*它*知道我们正在监视它。
|
||
|
||
This is the perfect infiltration job for a directive.
|
||
Our heroes will never know it's there.
|
||
|
||
指令是一种完美的渗透方式,我们的英雄永远不会知道该指令的存在。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Kidding aside, we're emphasizing two key points:
|
||
|
||
不开玩笑了,我们要强调的是两点:
|
||
|
||
1. Angular calls hook methods for *directives* as well as components.
|
||
|
||
1. 就像对组件一样,Angular也会对*指令*调用这些钩子方法。
|
||
|
||
2. A spy directive can gives us insight into a DOM object that we cannot change directly.
|
||
Obviously we can't change the implementation of a native `div`.
|
||
We can't modify a third party component either.
|
||
But we can watch both with a directive.
|
||
|
||
2. 一个侦探(spy)指令可以让我们在无法直接修改DOM对象实现代码的情况下,透视其内部细节。
|
||
显然,我们不能修改一个原生`div`元素的实现代码。
|
||
我们同样不能修改第三方组件。
|
||
但我们用一个指令就能监视它们了。
|
||
|
||
:marked
|
||
Our sneaky spy directive is simple, consisting almost entirely of `ngOnInit` and `ngOnDestroy` hooks
|
||
that log messages to the parent via an injected `LoggerService`.
|
||
|
||
我们这个鬼鬼祟祟的侦探指令很简单,几乎完全由`ngOnInit`和`ngOnDestroy`钩子组成,它通过一个注入进来的`LoggerService`来把消息记录到父组件中去。
|
||
|
||
+makeExample('lifecycle-hooks/ts/app/spy.directive.ts', 'spy-directive')(format=".")
|
||
|
||
:marked
|
||
We can apply the spy to any native or component element and it'll be initialized and destroyed
|
||
at the same time as that element.
|
||
Here we attach it to the repeated hero `<div>`
|
||
|
||
我们可以把这个侦探指令写到任何原生元素或组件元素上,它将与所在的组件同时初始化和销毁。
|
||
这里我们把它附加到用来重复显示英雄数据的这个`<div>`上。
|
||
+makeExample('lifecycle-hooks/ts/app/spy.component.html', 'template')(format=".")
|
||
|
||
:marked
|
||
Each spy's birth and death marks the birth and death of the attached hero `<div>`
|
||
with an entry in the *Hook Log* as we see here:
|
||
|
||
每个“侦探”的出生和死亡也同时标记出了存放英雄的那个`<div>`的出生和死亡。*钩子记录*中的结构看起来是这样的:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/lifecycle-hooks/spy-directive.gif' alt="Spy Directive")
|
||
|
||
:marked
|
||
Adding a hero results in a new hero `<div>`. The spy's `ngOnInit` logs that event.
|
||
We see a new entry for each hero.
|
||
|
||
添加一个英雄就会产生一个新的英雄`<div>`。侦探的`ngOnInit`记录下了这个事件。
|
||
我们看到每添加一个英雄都产生了一条新的日志结构。
|
||
|
||
The *Reset* button clears the `heroes` list.
|
||
Angular removes all hero divs from the DOM and destroys their spy directives at the same time.
|
||
The spy's `ngOnDestroy` method reports its last moments.
|
||
|
||
*Reset*按钮清除了这个`heroes`列表。
|
||
Angular从DOM中移除了所有英雄的div,并且同时销毁了附加在这些div上的侦探指令。
|
||
侦探的`ngOnDestroy`方法汇报了它自己的临终时刻。
|
||
|
||
The `ngOnInit` and `ngOnDestroy` methods have more vital roles to play in real applications.
|
||
Let's see why we need them.
|
||
|
||
在真实的应用程序中,`ngOnInit`和`ngOnDestroy`方法扮演着更重要的角色。
|
||
来看看我们为什么需要它们。
|
||
|
||
### OnInit
|
||
|
||
We turn to `ngOnInit` for two main reasons:
|
||
|
||
我们会因为两个主要理由而求助于`ngOnInit`:
|
||
1. To perform complex initializations shortly after construction
|
||
1. 在构造函数之后马上执行复杂的初始化逻辑
|
||
1. To set up the component after Angular sets the input properties
|
||
1. 在Angular设置完输入属性之后,对该组件进行准备。
|
||
|
||
An `ngOnInit` often fetches data for the component as shown in the
|
||
[Tutorial](../tutorial/toh-pt4.html#oninit) and [HTTP](server-communication.html#oninit) chapters.
|
||
|
||
`ngOnInit`通常用来为组件获取数据,就像在[教程](../tutorial/toh-pt4.html#oninit)和[HTTP](server-communication.html#oninit)中所展示的那样。
|
||
|
||
We don't fetch data in a component constructor. Why?
|
||
Because experienced developers agree that components should be cheap and safe to construct.
|
||
We shouldn't worry that a new component will try to contact a remote server when
|
||
created under test or before we decide to display it.
|
||
Constructors should do no more than set the initial local variables to simple values.
|
||
|
||
我们没有在组件的构造函数中获取数据。为什么呢?
|
||
因为有经验的开发者都同意,组件应该能被“便宜”而且安全的被构造出来。
|
||
在测试环境下新建组件时或在我们决定显示它之前,我们不应该担心它会尝试联系远程服务器。
|
||
构造函数中除了使用简单的值对局部变量进行初始化之外,什么都不应该做。
|
||
|
||
When a component must start working _soon_ after creation,
|
||
we can count on Angular to call the `ngOnInit` method to jumpstart it.
|
||
That's where the heavy initialization logic belongs.
|
||
|
||
如果组件必须在创建之后_很快_就开始工作,我们可以等Angular调用`ngOnInit`方法来启动它。
|
||
该方法中才是放重量级初始化逻辑的地方。
|
||
|
||
Remember also that a directive's data-bound input properties are not set until _after construction_.
|
||
That's a problem if we need to initialize the directive based on those properties.
|
||
They'll have been set when our `ngOninit` runs.
|
||
|
||
另外还要记住,在指令的_构造函数完成之前_,那些被绑定的输入属性还都没有值。
|
||
如果我们需要基于这些属性的值来初始化这个指令,这种情况就会出问题。
|
||
而当`ngOnInit`执行的时候,这些属性都已经被正确的赋值过了。
|
||
.l-sub-section
|
||
:marked
|
||
Our first opportunity to access those properties is the `ngOnChanges` method which
|
||
Angular calls before `ngOnit`. But Angular calls `ngOnChanges` many times after that.
|
||
It only calls `ngOnit` once.
|
||
|
||
我们访问这些属性的第一次机会,实际上是`ngOnChanges`方法,Angular会在`ngOnInit`之前调用它。
|
||
但是在那之后,Angular还会调用`ngOnChanges`很多次。而`ngOnInit`只会被调用一次。
|
||
:marked
|
||
### OnDestroy
|
||
|
||
Put cleanup logic in `ngOnDestroy`, the logic that *must* run before Angular destroys the directive.
|
||
|
||
一些清理逻辑*必须*在Angular销毁指令之前运行,把它们放在`ngOnDestroy`中。
|
||
|
||
This is the time to notify another part of the application that this component is going away.
|
||
|
||
这是在该组件消失之前,可用来通知应用程序中其它部分的最后一个时间点。
|
||
|
||
This is the place to free resources that won't be garbage collected automatically.
|
||
Unsubscribe from observables and DOM events. Stop interval timers.
|
||
Unregister all callbacks that this directive registered with global or application services.
|
||
We risk memory leaks if we neglect to do so.
|
||
|
||
这里是用来释放那些不会被垃圾收集器自动回收的各类资源的地方。
|
||
取消那些对可观察对象和DOM事件的订阅。停止定时器。注销该指令曾注册到全局服务或应用级服务中的各种回调函数。
|
||
如果我们不这么做,就会有导致内存泄露的风险。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## OnChanges
|
||
|
||
We monitor the `OnChanges` hook in this example.
|
||
Angular calls its `ngOnChanges` method whenever it detects changes to ***input properties*** of the component (or directive).
|
||
|
||
在这个例子中,我们监听了`OnChanges`钩子。
|
||
一旦检测到该组件(或指令)的***输入属性***发生了变化,Angular就会调用它的`ngOnChanges`方法。
|
||
|
||
Here is our implementation of the hook.
|
||
|
||
这里是我们对此钩子的实现。
|
||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'ng-on-changes', 'OnChangesComponent (ngOnChanges)')(format=".")
|
||
:marked
|
||
The `ngOnChanges` method takes an object that maps each changed property name to a
|
||
[SimpleChange](../api/core/index/SimpleChange-class.html) object with the current and previous property values.
|
||
We iterate over the changed properties and log them.
|
||
|
||
`ngOnChanges`方法获取了一个对象,它把每个发生变化的属性名都映射到了一个[SimpleChange](../api/core/index/SimpleChange-class.html)对象,
|
||
该对象中有属性的当前值和前一个值。我们在这些发生了变化的属性上进行迭代,并记录它们。
|
||
|
||
The input properties for our example `OnChangesComponent` are `hero` and `power`.
|
||
|
||
我们这个例子中的`OnChangesComponent`组件的输入属性是`hero`和`power`。
|
||
+makeExample('lifecycle-hooks/ts/app/on-changes.component.ts', 'inputs')(format=".")
|
||
:marked
|
||
The parent binds to them like this:
|
||
|
||
父组件中绑定了它们,就像这样:
|
||
|
||
+makeExample('lifecycle-hooks/ts/app/on-changes-parent.component.html', 'on-changes')
|
||
:marked
|
||
Here's the sample in action as we make changes.
|
||
|
||
下面是此例子中的当我们做出更改时的操作演示:
|
||
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/lifecycle-hooks/on-changes-anim.gif' alt="OnChanges")
|
||
|
||
:marked
|
||
We see log entries as the string value of the *power* property changes. But the `ngOnChanges` did not catch changes to `hero.name`
|
||
That's surprising at first.
|
||
|
||
当*power*属性的字符串值变化时,我们看到了相应的日志。但是`ngOnChanges`并没有捕捉到`hero.name`的变化。
|
||
这是第一个意外。
|
||
|
||
Angular only calls the hook when the value of the input property changes.
|
||
The value of the `hero` property is the *reference to the hero object*.
|
||
Angular doesn't care that the hero's own `name` property changed.
|
||
The hero object *reference* didn't change so, from Angular's perspective, there is no change to report!
|
||
|
||
Angular只会在输入属性的值变化时调用这个钩子。
|
||
而`hero`属性的值是一个*到英雄对象的引用*。
|
||
Angular不会关注这个英雄对象的`name`属性的变化。
|
||
这个英雄对象的*引用*没有发生变化,于是从Angular的视角看来,也就没有什么需要报告的变化了。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## DoCheck
|
||
We can use the `DoCheck` hook to detect and act upon changes that Angular doesn't catch on its own.
|
||
|
||
我们可以使用`DoCheck`钩子来检测那些Angular自身无法捕获的变更并采取行动。
|
||
.l-sub-section
|
||
:marked
|
||
With this method we can detect a change that Angular overlooked.
|
||
What we do with that information to refresh the display is a separate matter.
|
||
|
||
用这个方法我们可以检测到那些被Angular忽略的更改。
|
||
至于如何用此信息来刷新显示就是另一个问题了。
|
||
:marked
|
||
The *DoCheck* sample extends the *OnChanges* sample with this implementation of `DoCheck`:
|
||
|
||
*DoCheck*范例通过下面的`DoCheck`实现扩展了*OnChanges*范例:
|
||
+makeExample('lifecycle-hooks/ts/app/do-check.component.ts', 'ng-do-check', 'DoCheckComponent (ngDoCheck)')(format=".")
|
||
:marked
|
||
We manually check everything that we care about, capturing and comparing against previous values.
|
||
We write a special message to the log when there are no substantive changes
|
||
to the hero or the power so we can keep an eye on the method's performance characteristics.
|
||
|
||
我们手动检测了自己关心的一切,捕获当前值并与以前的值进行比较。
|
||
当英雄或他的超能力发生了非实质性改变时,我们就往日志中写一条特殊的消息,以便看到该方法的工作特征。
|
||
|
||
The results are illuminating:
|
||
|
||
其结果就像下面展示的这样:
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/lifecycle-hooks/do-check-anim.gif' alt="DoCheck")
|
||
:marked
|
||
We now are able to detect when the hero's `name` has changed. But we must be careful.
|
||
|
||
我们现在可以监测到英雄的`name`什么时候发生了变化。但我们必须小心。
|
||
|
||
The `ngDoCheck` hook is called with enormous frequency —
|
||
after _every_ change detection cycle no matter where the change occurred.
|
||
It's called over twenty times in this example before the user can do anything.
|
||
|
||
`ngDoCheck`钩子被非常频繁的调用 —— 在_每次_变更检测周期之后,发生了变化的每个地方都会调它。
|
||
在这个例子中,用户还没有做任何操作之前,它就被调用了超过二十次。
|
||
|
||
Most of these initial checks are triggered by Angular's first rendering of *unrelated data elsewhere on the page*.
|
||
Mere mousing into another input box triggers a call.
|
||
Relatively few calls reveal actual changes to pertinent data.
|
||
Clearly our implementation must be very lightweight or the user experience may suffer.
|
||
|
||
大部分检查的第一次调用都是在Angular首次渲染该页面中*其它不相关数据*时触发的。
|
||
仅仅把鼠标移到其它输入框中就会触发一次调用。
|
||
只有相对较少的调用才是由于对相关数据的修改而触发的。
|
||
显然,我们的实现必须非常轻量级,否则将损害用户体验。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
We also see that the `ngOnChanges` method is called in contradiction of the
|
||
[incorrect API documentation](../api/core/index/DoCheck-class.html).
|
||
|
||
我们还看到,`ngOnChanges`方法的调用方式与[API文档](../api/core/index/DoCheck-interface.html)中是不一样的,这是因为API文档过时了。
|
||
(译注:这是经过与官方开发组沟通得到的消息,由于代码快速迭代,因此API文档现在的更新不够及时,将来会进行一次系统的梳理和更正)
|
||
.l-main-section
|
||
:marked
|
||
## AfterView
|
||
The *AfterView* sample explores the `AfterViewInit` and `AfterViewChecked` hooks that Angular calls
|
||
*after* it creates a component's child views.
|
||
|
||
*AfterView*例子展示了`AfterViewInit`和`AfterViewChecked`钩子,Angular会在每次创建了组件的子视图后调用它们。
|
||
|
||
Here's a child view that displays a hero's name in an input box:
|
||
|
||
下面是一个子视图,它用来把英雄的名字显示在一个输入框中:
|
||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'child-view', 'ChildComponent')(format=".")
|
||
:marked
|
||
The `AfterViewComponent` displays this child view *within its template*:
|
||
|
||
`AfterViewComponent`把这个子视图显示*在它的模板中*:
|
||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'template', 'AfterViewComponent (template)')(format=".")
|
||
:marked
|
||
The following hooks take action based on changing values *within the child view*
|
||
which we can only reach by querying for the child view via the property decorated with
|
||
[@ViewChild](../api/core/index/ViewChild-var.html).
|
||
|
||
下列钩子基于*子视图中*的每一次数据变更采取行动,我们只能通过带[@ViewChild](../api/core/index/ViewChild-var.html)装饰器的属性来访问子视图。
|
||
|
||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'hooks', 'AfterViewComponent (class excerpts)')(format=".")
|
||
.a(id="wait-a-tick")
|
||
:marked
|
||
### Abide by the unidirectional data flow rule
|
||
### 遵循单向数据流规则
|
||
The `doSomething` method updates the screen when the hero name exceeds 10 characters.
|
||
|
||
当英雄的名字超过10个字符时,`doSomething`方法就会更新屏幕。
|
||
|
||
+makeExample('lifecycle-hooks/ts/app/after-view.component.ts', 'do-something', 'AfterViewComponent (doSomething)')(format=".")
|
||
:marked
|
||
Why does the `doSomething` method wait a tick before updating `comment`?
|
||
|
||
为什么在更新`comment`属性之前,`doSomething`方法要等上一拍(tick)?
|
||
|
||
Because we must adhere to Angular's unidirectional data flow rule which says that
|
||
we may not update the view *after* it has been composed.
|
||
Both hooks fire after the component's view has been composed.
|
||
|
||
因为我们必须遵守Angular的“单向数据流”规则,这条规则是说我们不能在一个视图已经被组合好*之后*再更新视图。
|
||
而这两个钩子都是在组件的视图已经被组合好之后触发的。
|
||
|
||
Angular throws an error if we update component's data-bound `comment` property immediately (try it!).
|
||
|
||
如果我们立即更新组件中被绑定的`comment`属性,Angular就会抛出一个错误(试试!)。
|
||
block tick-methods
|
||
:marked
|
||
The `LoggerService.tick` methods, which are implemented by a call to `setTimeout`, postpone the update one turn of the of the browser's JavaScript cycle ... and that's long enough.
|
||
|
||
`LoggerService.tick`方法,是通过调用`setTimeout`的方式实现的 —— 如果想推迟到浏览器的下一轮JavaScript周期中执行,这样就够了。
|
||
|
||
:marked
|
||
Here's *AfterView* in action
|
||
|
||
这里是*AfterView*的操作演示:
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/lifecycle-hooks/after-view-anim.gif' alt="AfterView")
|
||
:marked
|
||
Notice that Angular frequently calls `AfterViewChecked`, often when there are no changes of interest.
|
||
Write lean hook methods to avoid performance problems.
|
||
|
||
注意,Angular会频繁的调用`AfterViewChecked`,甚至在并没有需要关注的更改时也会触发。
|
||
所以务必把这个钩子方法写得尽可能精简,以免出现性能问题。
|
||
|
||
.l-main-section
|
||
:marked
|
||
## AfterContent
|
||
The *AfterContent* sample explores the `AfterContentInit` and `AfterContentChecked` hooks that Angular calls
|
||
*after* Angular projects external content into the component.
|
||
|
||
*AfterContent*例子展示了`AfterContentInit`和`AfterContentChecked`钩子,Angular会在外来内容被投影到组件中*之后*调用它们。
|
||
|
||
### Content projection
|
||
### 内容投影
|
||
*Content projection* is a way to import HTML content from outside the component and insert that content
|
||
into the component's template in a designated spot.
|
||
|
||
*内容投影*是从组件外部导入HTML内容,并把它插入在组件模板中指定位置上的一种途径。
|
||
|
||
.l-sub-section
|
||
:marked
|
||
Angular 1 developers know this technique as *transclusion*.
|
||
|
||
Angular 1的开发者大概知道一项叫做*transclusion*的技术,对,这就是它的马甲。
|
||
|
||
:marked
|
||
We'll illustrate with a variation on the [previous](#afterview) example
|
||
whose behavior and output is almost the same.
|
||
|
||
在[前一个](#afterview)例子中,我们解说过此功能的一个变体,它的行为和输出与这个例子完全相同。
|
||
|
||
This time, instead of including the child view within the template, we'll import it from
|
||
the `AfterContentComponent`'s parent. Here's the parent's template.
|
||
|
||
这次,我们不再通过模板来把子视图包含进来,而是改从`AfterContentComponent`的父组件中导入它。下面是父组件的模板。
|
||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'parent-template', 'AfterContentParentComponent (template excerpt)')(format=".")
|
||
:marked
|
||
Notice that the `<my-child>` tag is tucked between the `<after-content>` tags.
|
||
We never put content between a component's element tags *unless we intend to project that content
|
||
into the component*.
|
||
|
||
注意,`<my-child>`标签被包含在`<after-content>`标签中。
|
||
永远不要在组件标签的内部放任何内容 —— *除非我们想把这些内容投影进这个组件中*。
|
||
|
||
Now look at the component's template:
|
||
|
||
现在来看下`<after-content>`组件的模板:
|
||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'template', 'AfterContentComponent (template)')(format=".")
|
||
:marked
|
||
The `<ng-content>` tag is a *placeholder* for the external content.
|
||
They tell Angular where to insert that content.
|
||
In this case, the projected content is the `<my-child>` from the parent.
|
||
|
||
`<ng-content>`标签是外来内容的*占位符*。
|
||
它告诉Angular在哪里插入这些外来内容。
|
||
在这里,被投影进去的内容就是来自父组件的`<my-child>`标签。
|
||
figure.image-display
|
||
img(src='/resources/images/devguide/lifecycle-hooks/projected-child-view.png' width="230" alt="Projected Content")
|
||
:marked
|
||
.l-sub-section
|
||
:marked
|
||
The tell-tale signs of *content projection* are (a) HTML between component element tags
|
||
and (b) the presence of `<ng-content>` tags in the component's template.
|
||
|
||
下列迹象表明存在着*内容投影*:(a) 在组件的元素标签中有HTML;(b) 组件的模板中出现了`<ng-content>`标签。
|
||
:marked
|
||
### AfterContent hooks
|
||
### AfterContent钩子
|
||
*AfterContent* hooks are similar to the *AfterView* hooks. The key difference is the kind of child component
|
||
that we're looking for.
|
||
|
||
*AfterContent*钩子和*AfterView*相似。关键的不同点是子组件的类型不同。
|
||
|
||
* The *AfterView* hooks concern `ViewChildren`, the child components whose element tags
|
||
appear *within* the component's template.
|
||
|
||
* *AfterView*钩子所关心的是`ViewChildren`,这些子组件的元素标签会出现在该组件的模板*里面*。
|
||
|
||
* The *AfterContent* hooks concern `ContentChildren`, the child components that Angular
|
||
projected into the component.
|
||
|
||
* *AfterContent*钩子所关心的是`ContentChildren`,这些子组件被Angular投影进该组件中。
|
||
|
||
The following *AfterContent* hooks take action based on changing values in a *content child*
|
||
which we can only reach by querying for it via the property decorated with
|
||
[@ContentChild](../api/core/index/ContentChild-var.html).
|
||
|
||
下列*AfterContent*钩子基于*子级内容*中值的变化而采取相应的行动,这里我们只能通过带有[@ContentChild](../api/core/index/ContentChild-var.html)装饰器的属性来查询到“子级内容”。
|
||
|
||
+makeExample('lifecycle-hooks/ts/app/after-content.component.ts', 'hooks', 'AfterContentComponent (class excerpts)')(format=".")
|
||
|
||
:marked
|
||
### No unidirectional flow worries
|
||
### 这里不用担心单向数据流规则
|
||
|
||
This component's `doSomething` method update's the component's data-bound `comment` property immediately.
|
||
There's no [need to wait](#wait-a-tick).
|
||
|
||
该组件的`doSomething`方法立即更新了组件被绑定的`comment`属性。
|
||
它[不用等](#wait-a-tick)下一拍。
|
||
|
||
Recall that Angular calls both *AfterContent* hooks before calling either of the *AfterView* hooks.
|
||
Angular completes composition of the projected content *before* finishing the composition of this component's view.
|
||
We still have a window of opportunity to modify that view.
|
||
|
||
回忆一下,Angular在每次调用*AfterView*钩子之前也会同时调用*AfterContent*。
|
||
Angular在完成当前组件的视图合成之前,就已经完成了被投影内容的合成。
|
||
所以我们仍然有机会去修改那个视图。
|