docs: Understanding template variables updated (#45897)

This converts the "Template variables" doc to the new "Understanding"
format and remove some irrelevant content.

PR Close #45897
This commit is contained in:
Virginia Dooley
2021-11-29 18:18:08 +00:00
committed by Jessica Janiuk
parent 3c690bc1e9
commit 5879e4616f
+64 -147
View File
@@ -1,15 +1,14 @@
# Template variables
# Understanding template variables
Template variables help you use data from one part of a template in another part of the template.
Use template variables to perform tasks such as respond to user input or finely tune your application's forms.
A template variable can refer to the following:
* A DOM element within a template
* A directive
* An element
* [TemplateRef](api/core/TemplateRef)
* A [web component](https://developer.mozilla.org/docs/Web/Web_Components "MDN: Web Components")
* a DOM element within a template
* a directive or component
* a [TemplateRef](api/core/TemplateRef) from an [ng-template](api/core/ng-template)
* a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" title="MDN: Web Components">web component</a>
<div class="alert is-helpful">
@@ -17,29 +16,35 @@ See the <live-example></live-example> for a working example containing the code
</div>
## Prerequisites
* [Understanding templates](guide/template-overview)
## Syntax
In the template, you use the hash \(`#`\) character to declare a template variable.
The following template variable, `#phone`, declares a `phone` variable on an `<input>` element.
In the template, you use the hash symbol, `#`, to declare a template variable.
The following template variable, `#phone`, declares a `phone` variable with the `<input>` element as its value.
<code-example header="src/app/app.component.html" path="template-reference-variables/src/app/app.component.html" region="ref-var"></code-example>
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-var" header="src/app/app.component.html"></code-example>
Refer to a template variable anywhere in the component's template.
Here, a `<button>` further down the template refers to the `phone` variable.
<code-example header="src/app/app.component.html" path="template-reference-variables/src/app/app.component.html" region="ref-phone"></code-example>
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-phone" header="src/app/app.component.html"></code-example>
## How Angular assigns values to template variables
Angular assigns a template variable a value based on where you declare the variable:
* If you declare the variable on a component, the variable refers to the component instance
* If you declare the variable on a standard HTML tag, the variable refers to the element
* If you declare the variable on an `<ng-template>` element, the variable refers to a `TemplateRef` instance, which represents the template.
For more information on `<ng-template>`, see [How Angular uses the asterisk, `*`, syntax](guide/structural-directives#asterisk) in [Structural directives](guide/structural-directives).
* If you declare the variable on a component, the variable refers to the component instance.
* If you declare the variable on a standard HTML tag, the variable refers to the element.
* If you declare the variable on an `<ng-template>` element, the variable refers to a `TemplateRef` instance which represents the template.
For more information on `<ng-template>`, see [How Angular uses the asterisk, `*`, syntax](guide/structural-directives#asterisk) in [Structural directives](guide/structural-directives).
* If the variable specifies a name on the right-hand side, such as `#var="ngModel"`, the variable refers to the directive or component on the element with a matching `exportAs` name.
<!--todo: What does the second half of this mean?^^ Can we explain this more fully? Could I see a working example? -kw -->
## Variable specifying a name
* If the variable specifies a name on the right-hand side, such as `#var="ngModel"`, the variable refers to the directive or component on the element with a matching `exportAs` name.
<!-- What does the second half of this mean?^^ Can we explain this more fully? Could I see a working example? -kw -->
### Using `NgForm` with template variables
@@ -47,34 +52,21 @@ In most cases, Angular sets the template variable's value to the element on whic
In the previous example, `phone` refers to the phone number `<input>`.
The button's click handler passes the `<input>` value to the component's `callPhone()` method.
The `NgForm` directive is applied by Angular on `<form>` elements. This example demonstrates getting a reference to a different value by referencing a directive's `exportAs` name.
The `NgForm` directive demonstrates getting a reference to a different value by referencing a directive's `exportAs` name.
In the following example, the template variable, `itemForm`, appears three times separated by HTML.
<code-example header="src/app/hero-form.component.html" path="template-reference-variables/src/app/app.component.html" region="ngForm"></code-example>
<code-example path="template-reference-variables/src/app/app.component.html" region="ngForm" header="src/app/hero-form.component.html"></code-example>
Without the `ngForm` attribute value, the reference value of `itemForm` would be
the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement), `<form>`.
With `NgForm`, `itemForm` is a reference to the [NgForm](api/forms/NgForm "API: NgForm") directive with the ability to track the value and validity of every control in the form.
Unlike the native `<form>` element, the `NgForm` directive has a `form` property.
The `NgForm` `form` property lets you disable the submit button if the `itemForm.form.valid` is invalid.
## Default reference type without assigned value
When declaring a template reference variable on an element without defining a value for it, its returned type will reflect the type of element it's applied to:
- **Native element**: specific subclass of [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement)
- **Component**: instance of the specific Component class
- **NgTemplate**: TemplateRef
Referencing an element by its directive needs the directive `exportAs` property set as reference value.
In case of an unspecified variable value, the reference will return an `HTMLElement`, even if the element has one or more directive applied to itself.
If an element is an Angular Component, a reference with no attribute value will automatically reference the component instance. Otherwise, a reference with no value will reference the DOM element, even if the element has one or more directives applied to it.
<!-- What is the train of thought from talking about a form element to the difference between a component and a directive? Why is the component directive conversation relevant here? -kw I agree -alex -->
## Template variable scope
Refer to a template variable anywhere within its surrounding template.
[Structural directives](guide/built-in-directives), such as `*ngIf` and `*ngFor`, or `<ng-template>` act as a template boundary.
You cannot access template variables outside of these boundaries.
Just like variables in JavaScript or TypeScript code, template variables are scoped to the template that declares them.
Similarly, [Structural directives](guide/built-in-directives) such as `*ngIf` and `*ngFor`, or `<ng-template>` declarations create a new nested template scope, much like JavaScript's control flow statements like `if` and `for` create new lexical scopes. You cannot access template variables within one of these structural directives from outside of its boundaries.
<div class="alert is-helpful">
@@ -88,126 +80,51 @@ An inner template can access template variables that the outer template defines.
In the following example, changing the text in the `<input>` changes the value in the `<span>` because Angular immediately updates changes through the template variable, `ref1`.
<code-example header="src/app/app.component.html" path="template-reference-variables/src/app/app.component.html" region="template-ref-vars-scope1"></code-example>
<code-example path="template-reference-variables/src/app/app.component.html" region="template-ref-vars-scope1" header="src/app/app.component.html"></code-example>
In this case, there is an implied `<ng-template>` around the `<span>` and the definition of the variable is outside of it.
Accessing a template variable from the parent template works because the child template inherits the context from the parent template.
In this case, the `*ngIf` on `<span>` creates a new template scope, which includes the `ref1` variable from its parent scope.
Rewriting the preceding code in a more verbose form explicitly shows the `<ng-template>`.
However, accessing a template variable from a child scope in the parent template doesn't work:
<code-example format="html" language="html">
```html
<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
<span>Value: {{ ref2?.value }}</span> <!-- doesn't work -->
```
&lt;input #ref1 type="text" [(ngModel)]="firstExample" /&gt;
&lt;!-- New template --&gt;
&lt;ng-template [ngIf]="true"&gt;
&lt;!-- Because the context is inherited, the value is available to the new template --&gt;
&lt;span&gt;Value: {{ ref1.value }}&lt;/span&gt;
&lt;/ng-template&gt;
</code-example>
However, accessing a template variable from outside the parent template doesn't work.
<code-example format="html" language="html">
&lt;input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" /&gt;
&lt;span&gt;Value: {{ ref2?.value }}&lt;/span&gt; &lt;!-- doesn't work --&gt;
</code-example>
The verbose form shows that `ref2` is outside the parent template.
<code-example format="html" language="html">
&lt;ng-template [ngIf]="true"&gt;
&lt;!-- The reference is defined within a template --&gt;
&lt;input #ref2 type="text" [(ngModel)]="secondExample" /&gt;
&lt;/ng-template&gt;
&lt;!-- ref2 accessed from outside that template doesn't work --&gt;
&lt;span&gt;Value: {{ ref2?.value }}&lt;/span&gt;
</code-example>
Consider the following example that uses `*ngFor`.
<code-example format="html" language="html">
&lt;ng-container *ngFor="let i of [1,2]"&gt;
&lt;input #ref type="text" [value]="i" /&gt;
&lt;/ng-container&gt;
{{ ref.value }}
</code-example>
Here, `ref.value` doesn't work.
Verbose syntax of the same loop shows why:
<code-example format="html" language="html">
&lt;ng-template ngFor let-i [ngForOf]="[1,2]"&gt;
&lt;input #ref type="text" [value]="i" /&gt;
&lt;/ng-template&gt;
{{ ref.value }}
</code-example>
The interpolation trying to access property `ref.value` occurs outside of the referenced element's parent template, making it unreachable.
Moving the interpolation inside the template makes the variable available. Now it references the correct distinct value for each element the loop renders.
<code-example format="html" language="html">
&lt;ng-template ngFor let-i [ngForOf]="[1,2]"&gt;
&lt;input #ref type="text" [value]="i" /&gt;
{{ ref.value }}
&lt;/ng-template&gt;
</code-example>
This snippet shows 2 `<input>` elements, with their respective value printed.
### Accessing a template variable within `<ng-template>`
When you declare the variable on an `<ng-template>`, the variable refers to a `TemplateRef` instance, which represents the template.
<code-example header="src/app/app.component.html" path="template-reference-variables/src/app/app.component.html" region="template-ref"></code-example>
In this example, clicking the button calls the `log()` function, which outputs the value of `#ref3` to the console.
Because the `#ref` variable is on an `<ng-template>`, the value is `TemplateRef`.
The following is the expanded browser console output of the `TemplateRef()` function with the name of `TemplateRef`.
<code-example format="shell" language="shell">
&blacktriangledown; ƒ TemplateRef()
name: "TemplateRef"
&lowbar;&lowbar;proto&lowbar;&lowbar;: Function
</code-example>
<a id="template-input-variable"></a>
<a id="template-input-variables"></a>
Here, `ref2` is declared in the child scope created by `*ngIf`, and is not accessible from the parent template.
{@a template-input-variable}
{@a template-input-variables}
## Template input variable
A *template input variable* is a variable to reference within a single instance of the template.
You declare a template input variable using the `let` keyword as in `let hero`.
A _template input variable_ is a variable with a value that is set when an instance of that template is created. See: [Writing structural directives](https://angular.io/guide/structural-directives)
If its value is omitted, it gets the `$implicit` template context's property value.
Template input variables can be seen in action in the long-form usage of `NgFor`:
There are several such variables in this example: `hero`, `i`, and `odd`.
The first one takes the value of the iterated item, because `NgForOf` assigns that to `$implicit`
```html
<ul>
<ng-template ngFor let-hero [ngForOf]="heroes">
<li>{{hero.name}}
</ng-template>
</ul>
```
<code-example format="html" language="html">
&lt;ng-template ngFor #hero let-hero [ngForOf]="heroes" let-i="index" let-odd="odd"&gt;
&lt;div [class]="{'odd-row': odd}"&gt;{{i}}:{{hero.name}}&lt;/div&gt;
&lt;/ng-template&gt;
The `NgFor` directive will instantiate this <ng-template> once for each hero in the `heroes` array, and will set the `hero` variable for each instance accordingly.
</code-example>
When an `<ng-template>` is instantiated, multiple named values can be passed which can be bound to different template input variables. The right-hand side of the `let-` declaration of an input variable can specify which value should be used for that variable.
The variable's scope is limited to a single instance of the repeated template.
Use the same variable name again in the definition of other structural directives.
`NgFor` for example also provides access to the `index` of each hero in the array:
When in the same template a _template reference variable_ and a _template input variable_ with the same name get declared, the latter takes precedence.
```html
<ul>
<ng-template ngFor let-hero let-i="index" [ngForOf]="heroes">
<li>Hero number {{i}}: {{hero.name}}
</ng-template>
</ul>
```
## What’s next
[Writing structural directives](https://angular.io/guide/structural-directives)
@reviewed 2022-05-12