Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.

Latest commit

 

History

History
498 lines (342 loc) · 24.9 KB

File metadata and controls

498 lines (342 loc) · 24.9 KB

Writing structural directives

编写结构型指令

This topic demonstrates how to create a structural directive and provides conceptual information on how directives work, how Angular interprets shorthand, and how to add template guard properties to catch template type errors.

本主题演示如何创建结构型指令,并提供有关指令如何工作、Angular 如何解释简写形式以及如何添加模板守卫属性以捕获模板类型错误的概念性信息。

For the example app that this page describes, see the .

有关此页面描述的示例应用程序,请参见 。

For more information on Angular's built-in structural directives, such as NgIf, NgFor, and NgSwitch, see Built-in directives.

有关 Angular 的内置结构型指令(如 NgIfNgForNgSwitch)的更多信息,请参见内置指令

{@a unless}

Creating a structural directive

创建结构型指令

This section guides you through creating an UnlessDirective and how to set condition values. The UnlessDirective does the opposite of NgIf, and condition values can be set to true or false. NgIf displays the template content when the condition is true. UnlessDirective displays the content when the condition is false.

本节将指导你创建 UnlessDirective 以及如何设置 condition 值。 UnlessDirectiveNgIf 相反,并且 condition 值可以设置为 truefalseNgIftrue 时显示模板内容;而 UnlessDirective 在这个条件为 false 时显示内容。

Following is the UnlessDirective selector, appUnless, applied to the paragraph element. When condition is false, the browser displays the sentence.

以下是应用于 p 元素的 UnlessDirective 选择器 appUnlessconditionfalse ,浏览器将显示该句子。

  1. Using the Angular CLI, run the following command, where unless is the name of the directive:

    使用 Angular CLI,运行以下命令,其中 unless 是伪指令的名称:

    ng generate directive unless

    Angular creates the directive class and specifies the CSS selector, appUnless, that identifies the directive in a template.

    Angular 会创建指令类,并指定 CSS 选择器 appUnless,它会在模板中标识指令。

  2. Import Input, TemplateRef, and ViewContainerRef.

    导入 InputTemplateRefViewContainerRef

  3. Inject TemplateRef and ViewContainerRef in the directive constructor as private variables.

    在指令的构造函数中将 TemplateRefViewContainerRef 注入成私有变量。

    The UnlessDirective creates an embedded view from the Angular-generated <ng-template> and inserts that view in a view container adjacent to the directive's original <p> host element.

    UnlessDirective 会通过 Angular 生成的 <ng-template> 创建一个嵌入的视图,然后将该视图插入到该指令的原始 <p> 宿主元素紧后面的视图容器中。

    TemplateRef helps you get to the <ng-template> contents and ViewContainerRef accesses the view container.

    TemplateRef可帮助你获取 <ng-template> 的内容,而 ViewContainerRef 可以访问视图容器。

  4. Add an appUnless @Input() property with a setter.

    添加一个带 setter 的 @Input() 属性 appUnless

    Angular sets the appUnless property whenever the value of the condition changes.

    每当条件的值更改时,Angular 都会设置 appUnless 属性。

    • If the condition is falsy and Angular hasn't created the view previously, the setter causes the view container to create the embedded view from the template.

      如果条件是假值,并且 Angular 以前尚未创建视图,则此 setter 会导致视图容器从模板创建出嵌入式视图。

    • If the condition is truthy and the view is currently displayed, the setter clears the container, which disposes of the view.

      如果条件为真值,并且当前正显示着视图,则此 setter 会清除容器,这会导致销毁该视图。

The complete directive is as follows:

完整的指令如下:

Testing the directive

测试指令

In this section, you'll update your application to test the UnlessDirective.

在本节中,你将更新你的应用程序,以测试 UnlessDirective

  1. Add a condition set to false in the AppComponent.

    添加一个 condition 设置为 falseAppComponent

  2. Update the template to use the directive. Here, *appUnless is on two <p> tags with opposite condition values, one true and one false.

    更新模板以使用指令。这里,*appUnless 位于两个具有相反 condition<p> 标记上,一个为 true ,一个为 false

    The asterisk is shorthand that marks appUnless as a structural directive. When the condition is falsy, the top (A) paragraph appears and the bottom (B) paragraph disappears. When the condition is truthy, the top (A) paragraph disappears and the bottom (B) paragraph appears.

    星号是将 appUnless 标记为结构型指令的简写形式。如果 condition 是假值,则会让顶部段落 A 出现,而底部段落 B 消失。当 condition 为真时,顶部段落 A 消失,而底部段落 B 出现。

  3. To change and display the value of condition in the browser, add markup that displays the status and a button.

    要在浏览器中更改并显示 condition 的值,请添加一段标记代码以显示状态和按钮。

To verify that the directive works, click the button to change the value of condition.

要验证指令是否有效,请单击按钮以更改 condition 的值。

UnlessDirective in action

{@a shorthand} {@a asterisk}

Structural directive shorthand

结构型指令简写形式

The asterisk, *, syntax on a structural directive, such as *ngIf, is shorthand that Angular interprets into a longer form. Angular transforms the asterisk in front of a structural directive into an <ng-template> that surrounds the host element and its descendants.

结构型指令(例如 *ngIf)上的星号 * 语法是 Angular 解释为较长形式的简写形式。 Angular 将结构型指令前面的星号转换为围绕宿主元素及其后代的 <ng-template>

The following is an example of *ngIf that displays the hero's name if hero exists:

下面是一个 *ngIf 的示例,如果 hero 存在,则显示英雄的名称:

The *ngIf directive moves to the <ng-template> where it becomes a property binding in square brackets, [ngIf]. The rest of the <div>, including its class attribute, moves inside the <ng-template>.

*ngIf 指令移到了 <ng-template> 上,在这里它成为绑定在方括号 [ngIf] 中的属性。 <div> 的其余部分(包括其 class 属性)移到了 <ng-template> 内部。

Angular does not create a real <ng-template> element, instead rendering only the <div> and a comment node placeholder to the DOM.

Angular 不会创建真正的 <ng-template> 元素,只会将 <div> 和注释节点占位符渲染到 DOM 中。

<!--bindings={
  "ng-reflect-ng-if": "[object Object]"
}-->
<div _ngcontent-c0>Mr. Nice</div>

The following example compares the shorthand use of the asterisk in *ngFor with the longhand <ng-template> form:

*ngFor 中的星号的简写形式与非简写的 <ng-template> 形式进行比较:

Here, everything related to the ngFor structural directive applies to the <ng-template>. All other bindings and attributes on the element apply to the <div> element within the <ng-template>. Other modifiers on the host element, in addition to the ngFor string, remain in place as the element moves inside the <ng-template>. In this example, the [class.odd]="odd" stays on the <div>.

这里,ngFor 结构型指令相关的所有内容都应用到了 <ng-template> 中。而元素上的所有其他绑定和属性应用到了 <ng-template> 中的 <div> 元素上。除了 ngFor 字符串外,宿主元素上的其他修饰都会保留在 <ng-template> 中。在这个例子中,[class.odd]="odd" 就留在了 <div> 中。

The let keyword declares a template input variable that you can reference within the template. The input variables in this example are hero, i, and odd. The parser translates let hero, let i, and let odd into variables named let-hero, let-i, and let-odd. The let-i and let-odd variables become let i=index and let odd=odd. Angular sets i and odd to the current value of the context's index and odd properties.

let 关键字会声明一个模板输入变量,你可以在模板中引用该变量。在这个例子中,是 heroiodd。解析器将 let herolet ilet odd 转换为名为 let-herolet-ilet-odd 的变量。 let-ilet-odd 变量变为 let i=indexlet odd=odd 。 Angular 会将 iodd 设置为上下文中 indexodd 属性的当前值。

The parser applies PascalCase to all directives and prefixes them with the directive's attribute name, such as ngFor. For example, the ngFor input properties, of and trackBy, map to ngForOf and ngForTrackBy. As the NgFor directive loops through the list, it sets and resets properties of its own context object. These properties can include, but aren't limited to, index, odd, and a special property named $implicit.

解析器会将 PascalCase 应用于所有指令,并为它们加上指令的属性名称(例如 ngFor)。比如,ngFor 的输入特性 oftrackBy ,会映射为 ngForOfngForTrackBy 。当 NgFor 指令遍历列表时,它会设置和重置它自己的上下文对象的属性。这些属性可以包括但不限于 indexodd 和一个名为 $implicit 的特殊属性。

Angular sets let-hero to the value of the context's $implicit property, which NgFor has initialized with the hero for the current iteration.

Angular 会将 let-hero 设置为上下文的 $implicit 属性的值, NgFor 已经将其初始化为当前正在迭代的英雄。

For more information, see the NgFor API and NgForOf API documentation.

有关更多信息,请参见 NgFor APINgForOf API 文档。

Creating template fragments with <ng-template>

<ng-template> 创建模板片段

Angular's <ng-template> element defines a template that doesn't render anything by default. With <ng-template>, you can render the content manually for full control over how the content displays.

Angular 的 <ng-template> 元素定义了一个默认情况下不渲染任何内容的模板。使用 <ng-template> ,你可以手动渲染内容,以完全控制内容的显示方式。

If there is no structural directive and you wrap some elements in an <ng-template>, those elements disappear. In the following example, Angular does not render the middle "Hip!" in the phrase "Hip! Hip! Hooray!" because of the surrounding <ng-template>.

如果没有结构型指令,并且将某些元素包装在 <ng-template> 中,则这些元素会消失。在下面的示例中,Angular 不会渲染中间的 “Hip!”,因为它被 <ng-template> 包裹着。

template tag rendering

Structural directive syntax reference

结构型指令语法参考

When you write your own structural directives, use the following syntax:

当你编写自己的结构型指令时,请使用以下语法:

*:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*"

The following tables describe each portion of the structural directive grammar:

下表描述了结构型指令语法的每个部分:

prefix HTML attribute key
prefix HTML 属性的键名
key HTML attribute key
key HTML 属性的键名
local local variable name used in the template
local 在模板中使用的局部变量名
export value exported by the directive under a given name
export 该指令以特定名称导出的值
expression standard Angular expression
expression 标准 Angular 表达式
keyExp = :key ":"? :expression ("as" :local)? ";"?
let = "let" :local "=" :export ";"?
as = :export "as" :local ";"?

How Angular translates shorthand

Angular 如何翻译简写形式

Angular translates structural directive shorthand into the normal binding syntax as follows:

Angular 会将结构型指令的简写形式转换为普通的绑定语法,如下所示:

Shorthand Translation
简写形式 翻译结果
prefix and naked expression [prefix]="expression"
prefix 和裸 expression [prefix]="expression"
keyExp [prefixKey] "expression" (let-prefixKey="export")
Notice that the prefix is added to the key
keyExp [prefixKey] "expression" (let-prefixKey="export")
注意,这个 prefix 已经加到了 key 上。
let let-local="export"

Shorthand examples

简写形式示例

The following table provides shorthand examples:

下表提供了一些简写形式示例:

Shorthand How Angular interprets the syntax
简写形式 Angular 如何解释此语法
*ngFor="let item of [1,2,3]" <ng-template ngFor let-item [ngForOf]="[1,2,3]">
*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i" <ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index">
*ngIf="exp" <ng-template [ngIf]="exp">
*ngIf="exp as value" <ng-template [ngIf]="exp" let-value="ngIf">

{@a directive-type-checks}

Improving template type checking for custom directives

改进自定义指令的模板类型检查

You can improve template type checking for custom directives by adding template guard properties to your directive definition. These properties help the Angular template type checker find mistakes in the template at compile time, which can avoid runtime errors. These properties are as follows:

你可以通过将模板守卫属性添加到指令定义中来改进自定义指令的模板类型检查。这些属性可帮助 Angular 的模板类型检查器在编译时发现模板中的错误,从而避免运行时错误。这些属性如下:

  • A property ngTemplateGuard_(someInputProperty) lets you specify a more accurate type for an input expression within the template.

    ngTemplateGuard_(someInputProperty) 属性使你可以为模板中的输入表达式指定更准确的类型。

  • The ngTemplateContextGuard static property declares the type of the template context.

    静态属性 ngTemplateContextGuard 声明了模板上下文的类型。

This section provides examples of both kinds of type-guard property. For more information, see Template type checking.

本节提供了两种类型守卫的示例。欲知详情,请参见模板类型检查

{@a narrowing-input-types}

Making in-template type requirements more specific with template guards

使用模板守卫使模板中的类型要求更具体

A structural directive in a template controls whether that template is rendered at run time, based on its input expression. To help the compiler catch template type errors, you should specify as closely as possible the required type of a directive's input expression when it occurs inside the template.

模板中的结构型指令会根据输入表达式来控制是否要在运行时渲染该模板。为了帮助编译器捕获模板类型中的错误,你应该尽可能详细地指定模板内指令的输入表达式所期待的类型。

A type guard function narrows the expected type of an input expression to a subset of types that might be passed to the directive within the template at run time. You can provide such a function to help the type-checker infer the proper type for the expression at compile time.

类型保护函数会将输入表达式的预期类型缩小为可能在运行时传递给模板内指令的类型的子集。你可以提供这样的功能来帮助类型检查器在编译时为表达式推断正确的类型。

For example, the NgIf implementation uses type-narrowing to ensure that the template is only instantiated if the input expression to *ngIf is truthy. To provide the specific type requirement, the NgIf directive defines a static property ngTemplateGuard_ngIf: 'binding'. The binding value is a special case for a common kind of type-narrowing where the input expression is evaluated in order to satisfy the type requirement.

例如,NgIf 的实现使用类型窄化来确保只有当 *ngIf 的输入表达式为真时,模板才会被实例化。为了提供具体的类型要求,NgIf 指令定义了一个静态属性 ngTemplateGuard_ngIf: 'binding'。这里的 binding 值是一种常见的类型窄化的例子,它会对输入表达式进行求值,以满足类型要求。

To provide a more specific type for an input expression to a directive within the template, add an ngTemplateGuard_xx property to the directive, where the suffix to the static property name, xx, is the @Input() field name. The value of the property can be either a general type-narrowing function based on its return type, or the string "binding", as in the case of NgIf.

要为模板中指令的输入表达式提供更具体的类型,请在指令中添加 ngTemplateGuard_xx 属性,其中静态属性名称 xx 就是 @Input() 字段的名字。该属性的值可以是基于其返回类型的常规类型窄化函数,也可以是字符串,例如 NgIf 中的 "binding"

For example, consider the following structural directive that takes the result of a template expression as an input:

例如,考虑以下结构型指令,该指令以模板表达式的结果作为输入:

export type Loaded = { type: 'loaded', data: T }; export type Loading = { type: 'loading' }; export type LoadingState = Loaded | Loading; export class IfLoadedDirective { @Input('ifLoaded') set state(state: LoadingState) {} static ngTemplateGuard_state(dir: IfLoadedDirective, expr: LoadingState): expr is Loaded { return true; }; }

export interface Person { name: string; }

@Component({ template: &lt;div *ifLoaded="state">{{ state.data }}&lt;/div>, }) export class AppComponent { state: LoadingState; }

In this example, the LoadingState<T> type permits either of two states, Loaded<T> or Loading. The expression used as the directive’s state input is of the umbrella type LoadingState, as it’s unknown what the loading state is at that point.

在这个例子中, LoadingState<T> 类型允许两个状态之一, Loaded<T>Loading 。用作指令的 state 输入的表达式是宽泛的伞形类型 LoadingState,因为还不知道此时的加载状态是什么。

The IfLoadedDirective definition declares the static field ngTemplateGuard_state, which expresses the narrowing behavior. Within the AppComponent template, the *ifLoaded structural directive should render this template only when state is actually Loaded<Person>. The type guard allows the type checker to infer that the acceptable type of state within the template is a Loaded<T>, and further infer that T must be an instance of Person.

IfLoadedDirective 定义声明了静态字段 ngTemplateGuard_state,以表示其窄化行为。在 AppComponent 模板中,*ifLoaded 结构型指令只有当实际的 stateLoaded<Person> 类型时,才会渲染该模板。类型守护允许类型检查器推断出模板中可接受的 state 类型是 Loaded<T>,并进一步推断出 T 必须是一个 Person 的实例。

{@a narrowing-context-type}

Typing the directive's context

为指令的上下文指定类型

If your structural directive provides a context to the instantiated template, you can properly type it inside the template by providing a static ngTemplateContextGuard function. The following snippet shows an example of such a function.

如果你的结构型指令要为实例化的模板提供一个上下文,可以通过提供静态的 ngTemplateContextGuard 函数在模板中给它提供合适的类型。下面的代码片段展示了该函数的一个例子。

@Directive({…}) export class ExampleDirective { // Make sure the template checker knows the type of the context with which the // template of this directive will be rendered static ngTemplateContextGuard(dir: ExampleDirective, ctx: unknown): ctx is ExampleContext { return true; };
// …

}