341 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			341 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # 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.
 | ||
| 
 | ||
| <div class="alert is-helpful">
 | ||
| 
 | ||
| For the example app that this page describes, see the <live-example></live-example>.
 | ||
| 
 | ||
| </div>
 | ||
| 
 | ||
| For more information on Angular's built-in structural directives, such as `NgIf`, `NgFor`, and `NgSwitch`, see [Built-in directives](guide/built-in-directives).
 | ||
| 
 | ||
| {@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`.
 | ||
| 
 | ||
| Following is the `UnlessDirective` selector, `appUnless`, applied to the paragraph element.
 | ||
| When `condition` is `true`, the browser displays the sentence.
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (appUnless-1)" region="appUnless-1"></code-example>
 | ||
| 
 | ||
| 1. Using the Angular CLI, run the following command, where `unless` is the name of the directive:
 | ||
| 
 | ||
|   ```bash
 | ||
| 
 | ||
|   ng generate directive unless
 | ||
| 
 | ||
|   ```
 | ||
| 
 | ||
|   Angular creates the directive class and specifies the CSS selector, `appUnless`, that identifies the directive in a template.
 | ||
| 
 | ||
| 1. Import `Input`, `TemplateRef`, and `ViewContainerRef`.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/unless.directive.ts" header="src/app/unless.directive.ts (skeleton)" region="skeleton"></code-example>
 | ||
| 
 | ||
| 1. Inject `TemplateRef` and `ViewContainerRef` in the directive constructor as private variables.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/unless.directive.ts" header="src/app/unless.directive.ts (ctor)" region="ctor"></code-example>
 | ||
| 
 | ||
|   The `UnlessDirective` creates an [embedded view](api/core/EmbeddedViewRef "API: EmbeddedViewRef") from the Angular-generated `<ng-template>` and inserts that view in a [view container](api/core/ViewContainerRef "API: ViewContainerRef") adjacent to the directive's original `<p>` host element.
 | ||
| 
 | ||
|   [`TemplateRef`](api/core/TemplateRef "API: TemplateRef") helps you get to the `<ng-template>` contents and [`ViewContainerRef`](api/core/ViewContainerRef "API: ViewContainerRef") accesses the view container.
 | ||
| 
 | ||
| 1. Add an `appUnless` `@Input()` property with a setter.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/unless.directive.ts" header="src/app/unless.directive.ts (set)" region="set"></code-example>
 | ||
| 
 | ||
|   Angular sets the `appUnless` property whenever the value of the condition changes.
 | ||
| 
 | ||
|     * 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.
 | ||
| 
 | ||
|     * If the condition is truthy and the view is currently displayed, the setter clears the container, which disposes of the view.
 | ||
| 
 | ||
| The complete directive is as follows:
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/unless.directive.ts" header="src/app/unless.directive.ts (excerpt)" region="no-docs"></code-example>
 | ||
| 
 | ||
| ### Testing the directive
 | ||
| 
 | ||
| In this section, you'll update your application to test the `UnlessDirective`.
 | ||
| 
 | ||
| 1. Add a `condition` set to `false` in the `AppComponent`.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/app.component.ts" header="src/app/app.component.ts (excerpt)" region="condition"></code-example>
 | ||
| 
 | ||
| 1. Update the template to use the directive.
 | ||
|   Here, `*appUnless` is on two `<p>` tags with opposite `condition` values, one `true` and one `false`.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (appUnless)" region="appUnless"></code-example>
 | ||
| 
 | ||
|   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.
 | ||
| 
 | ||
| 1. To change and display the value of `condition` in the browser, add markup that displays the status and a button.
 | ||
| 
 | ||
|   <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html" region="toggle-info"></code-example>
 | ||
| 
 | ||
| To verify that the directive works, click the button to change the value of `condition`.
 | ||
| 
 | ||
|   <div class="lightbox">
 | ||
|     <img src='generated/images/guide/structural-directives/unless-anim.gif' alt="UnlessDirective in action">
 | ||
|   </div>
 | ||
| 
 | ||
| 
 | ||
| {@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.
 | ||
| 
 | ||
| The following is an example of `*ngIf` that displays the hero's name if `hero` exists:
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (asterisk)" region="asterisk"></code-example>
 | ||
| 
 | ||
| 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>`.
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (ngif-template)" region="ngif-template"></code-example>
 | ||
| 
 | ||
| Angular does not create a real `<ng-template>` element, instead rendering only the `<div>` and a comment node placeholder to the DOM.
 | ||
| 
 | ||
| ```html
 | ||
| <!--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:
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (inside-ngfor)" region="inside-ngfor"></code-example>
 | ||
| 
 | ||
| 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>`.
 | ||
| 
 | ||
| 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.
 | ||
| 
 | ||
| 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`.
 | ||
| 
 | ||
| Angular sets `let-hero` to the value of the context's `$implicit` property, which `NgFor` has initialized with the hero for the current iteration.
 | ||
| 
 | ||
| For more information, see the [NgFor API](api/common/NgForOf "API: NgFor") and [NgForOf API](api/common/NgForOf) documentation.
 | ||
| 
 | ||
| ### Creating template fragments with `<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.
 | ||
| 
 | ||
| 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>`.
 | ||
| 
 | ||
| <code-example path="structural-directives/src/app/app.component.html" header="src/app/app.component.html (template-tag)" region="template-tag"></code-example>
 | ||
| 
 | ||
| <div class="lightbox">
 | ||
|   <img src='generated/images/guide/structural-directives/template-rendering.png' alt="template tag rendering">
 | ||
| </div>
 | ||
| 
 | ||
| ## 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:
 | ||
| 
 | ||
| <table>
 | ||
| 
 | ||
|   <tr>
 | ||
|     <td><code>prefix</code></td>
 | ||
|     <td>HTML attribute key</td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>key</code></td>
 | ||
|     <td>HTML attribute key</td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>local</code></td>
 | ||
|     <td>local variable name used in the template</td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>export</code></td>
 | ||
|     <td>value exported by the directive under a given name</td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>expression</code></td>
 | ||
|     <td>standard Angular expression</td>
 | ||
|   </tr>
 | ||
| </table>
 | ||
| 
 | ||
| <table>
 | ||
|   <tr>
 | ||
|     <th></th>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td colspan="3"><code>keyExp = :key ":"? :expression ("as" :local)? ";"? </code></td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td colspan="3"><code>let = "let" :local "=" :export ";"?</code></td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td colspan="3"><code>as = :export "as" :local ";"?</code></td>
 | ||
|   </tr>
 | ||
| </table>
 | ||
| 
 | ||
| ### How Angular translates shorthand
 | ||
| 
 | ||
| Angular translates structural directive shorthand into the normal binding syntax as follows:
 | ||
| 
 | ||
| <table>
 | ||
|   <tr>
 | ||
|     <th>Shorthand</th>
 | ||
|     <th>Translation</th>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>prefix</code> and naked <code>expression</code></td>
 | ||
|     <td><code>[prefix]="expression"</code></td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>keyExp</code></td>
 | ||
|     <td><code>[prefixKey] "expression"
 | ||
|     (let-prefixKey="export")</code>
 | ||
|     <br />
 | ||
|     Notice that the <code>prefix</code>
 | ||
|     is added to the <code>key</code>
 | ||
|     </td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>let</code></td>
 | ||
|     <td><code>let-local="export"</code></td>
 | ||
|   </tr>
 | ||
| </table>
 | ||
| 
 | ||
| ### Shorthand examples
 | ||
| 
 | ||
| The following table provides shorthand examples:
 | ||
| 
 | ||
| <table>
 | ||
|   <tr>
 | ||
|     <th>Shorthand</th>
 | ||
|     <th>How Angular interprets the syntax</th>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>*ngFor="let item of [1,2,3]"</code></td>
 | ||
|     <td><code><ng-template ngFor let-item [ngForOf]="[1,2,3]"></code></td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>*ngFor="let item of [1,2,3] as items; trackBy: myTrack; index as i"</code></td>
 | ||
|     <td><code><ng-template ngFor let-item [ngForOf]="[1,2,3]" let-items="ngForOf" [ngForTrackBy]="myTrack" let-i="index"></code>
 | ||
|     </td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>*ngIf="exp"</code></td>
 | ||
|     <td><code><ng-template [ngIf]="exp"></code></td>
 | ||
|   </tr>
 | ||
|   <tr>
 | ||
|     <td><code>*ngIf="exp as value"</code></td>
 | ||
|     <td><code><ng-template [ngIf]="exp" let-value="ngIf"></code></td>
 | ||
|   </tr>
 | ||
| </table>
 | ||
| 
 | ||
| {@a directive-type-checks}
 | ||
| 
 | ||
| <!-- To do follow up PR: move this section to a more general location because it also applies to attribute directives. -->
 | ||
| ## 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:
 | ||
| 
 | ||
| * A property `ngTemplateGuard_(someInputProperty)` lets you specify a more accurate type for an input expression within the template.
 | ||
| * The `ngTemplateContextGuard` static property declares the type of the template context.
 | ||
| 
 | ||
| This section provides examples of both kinds of type-guard property.
 | ||
| For more information, see [Template type checking](guide/template-typecheck "Template type-checking guide").
 | ||
| 
 | ||
| {@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'`](api/common/NgIf#static-properties).
 | ||
| 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.
 | ||
| 
 | ||
| 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`.
 | ||
| 
 | ||
| For example, consider the following structural directive that takes the result of a template expression as an input:
 | ||
| 
 | ||
| <code-example language="ts" header="IfLoadedDirective">
 | ||
| export type Loaded<T> = { type: 'loaded', data: T };
 | ||
| export type Loading = { type: 'loading' };
 | ||
| export type LoadingState<T> = Loaded<T> | Loading;
 | ||
| export class IfLoadedDirective<T> {
 | ||
|     @Input('ifLoaded') set state(state: LoadingState<T>) {}
 | ||
|     static ngTemplateGuard_state<T>(dir: IfLoadedDirective<T>, expr: LoadingState<T>): expr is Loaded<T> { return true; };
 | ||
| }
 | ||
| 
 | ||
| export interface Person {
 | ||
|   name: string;
 | ||
| }
 | ||
| 
 | ||
| @Component({
 | ||
|   template: `<div *ifLoaded="state">{{ state.data }}</div>`,
 | ||
| })
 | ||
| export class AppComponent {
 | ||
|   state: LoadingState<Person>;
 | ||
| }
 | ||
| </code-example>
 | ||
| 
 | ||
| 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.
 | ||
| 
 | ||
| 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`.
 | ||
| 
 | ||
| {@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.
 | ||
| 
 | ||
| <code-example language="ts" header="myDirective.ts">
 | ||
| @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; };
 | ||
| 
 | ||
|     // …
 | ||
| }
 | ||
| </code-example>
 |