docs: move template ref vars doc to concepts section clarify, add scope section, and update headers (#31195)
Fixes #31186. This commit adds more context about the behavior of template reference variables in nested templates and moves doc into concepts section. PR Close #31195
This commit is contained in:
parent
60e377ed77
commit
e3d2f46bbd
|
@ -0,0 +1,14 @@
|
|||
h3 {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
pre, .wrapper {
|
||||
background-color: rgb(240, 250, 250);
|
||||
padding: 1rem;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
input {
|
||||
margin: .5rem;
|
||||
padding: .5rem;
|
||||
}
|
|
@ -39,8 +39,7 @@
|
|||
<h2>Reference variables, forms, and NgForm</h2>
|
||||
<!-- #docregion ngForm -->
|
||||
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
|
||||
<label for="name"
|
||||
>Name <input class="form-control" name="name" ngModel required />
|
||||
<label for="name">Name <input class="form-control" name="name" ngModel required />
|
||||
</label>
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
|
@ -53,3 +52,60 @@
|
|||
|
||||
|
||||
<p>JSON: {{ itemForm.form.value | json }}</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>Template Reference Variable Scope</h2>
|
||||
|
||||
<p>This section demonstrates in which situations you can access local template reference variables (<code>#ref</code>).</p>
|
||||
|
||||
<h3>Accessing in a child template</h3>
|
||||
<!-- Accessing a template reference variable from an inner template
|
||||
works as the context is inherited. Try changing the text in the
|
||||
input to see how it is immediately reflected through the template
|
||||
reference variable. -->
|
||||
|
||||
<div class="example">
|
||||
<!-- #docregion template-ref-vars-scope1 -->
|
||||
<input #ref1 type="text" [(ngModel)]="firstExample" />
|
||||
<span *ngIf="true">Value: {{ ref1.value }}</span>
|
||||
<!-- #enddocregion template-ref-vars-scope1 -->
|
||||
</div>
|
||||
|
||||
<!-- In this case there's a "hidden" ng-template around the
|
||||
span and the definition of the variable is outside of it. Thus, you access a template variable from a parent template (which is logical as the context is inherited). -->
|
||||
|
||||
<p>Here's the desugared syntax:</p>
|
||||
<pre><code [innerText]="desugared1"></code></pre>
|
||||
|
||||
<h3>Accessing from outside parent template. (Doesn't work.)</h3>
|
||||
<!-- Accessing the template reference variable from outside the parent template does
|
||||
not work. The value to the right is empty and changing the
|
||||
value of the input will have no effect. -->
|
||||
|
||||
<div class="example">
|
||||
<!-- #docregion template-ref-vars-scope2 -->
|
||||
<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
|
||||
<!-- <span>Value: {{ ref2?.value }}</span> -->
|
||||
<!-- uncomment the above and the app breaks -->
|
||||
<!-- #enddocregion template-ref-vars-scope2 -->
|
||||
</div>
|
||||
|
||||
<p>Here's the desugared syntax:</p>
|
||||
<pre><code [innerText]="desugared2"></code></pre>
|
||||
|
||||
<h3>*ngFor and template reference variable scope</h3>
|
||||
|
||||
<!-- The template is instantiated twice because *ngFor iterates
|
||||
over the two items in the array, so it's impossible to define what ref2 is referencing. -->
|
||||
|
||||
<pre><code [innerText]="ngForExample"></code></pre>
|
||||
|
||||
<h3>Accessing a on an <code>ng-template</code></h3>
|
||||
|
||||
See the console output to see that when you declare the variable on an <code>ng-template</code>, the variable refers to a <code>TemplateRef</code> instance, which represents the template.
|
||||
|
||||
<!-- #docregion template-ref -->
|
||||
<ng-template #ref3></ng-template>
|
||||
<button (click)="log(ref3)">Log type of #ref</button>
|
||||
<!-- #enddocregion template-ref -->
|
||||
|
|
|
@ -8,7 +8,38 @@ import { NgForm } from '@angular/forms';
|
|||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
@ViewChild('itemForm') form: NgForm;
|
||||
|
||||
public firstExample = 'Hello, World!';
|
||||
public secondExample = 'Hello, World!';
|
||||
|
||||
public ref2 = '';
|
||||
|
||||
public desugared1 = `
|
||||
<input #ref1 type="text" [(ngModel)]="firstExample" /><!-- A new template! -->
|
||||
<ng-template [ngIf]="true">
|
||||
<!-- … and it works -->
|
||||
<span>Value: {{ ref1.value }}</span>
|
||||
</ng-template>`
|
||||
;
|
||||
|
||||
public desugared2 = `<ng-template [ngIf]="true">
|
||||
<!-- The reference, ref2, is defined within a template -->
|
||||
<input #ref2 type="text" [(ngModel)]="secondExample" />
|
||||
</ng-template>
|
||||
|
||||
<!-- Attempting to access ref2 from outside the above template doesn't work. -->
|
||||
<span>Value: {{ ref2?.value }}</span>`;
|
||||
|
||||
public ngForExample = `<ng-container *ngFor="let i of [1,2]">
|
||||
<input #ref type="text" [value]="i" />
|
||||
</ng-container>
|
||||
|
||||
<!-- The template is instantiated twice because *ngFor iterates
|
||||
over the two items in the array, so it's impossible to define what ref2 is referencing.-->
|
||||
|
||||
{{ ref.value }}`;
|
||||
|
||||
@ViewChild('itemForm', { static: false }) form: NgForm;
|
||||
|
||||
get submitMessage() { return this._submitMessage; }
|
||||
private _submitMessage = ''; // tslint:disable-line: variable-name
|
||||
|
@ -25,4 +56,7 @@ export class AppComponent {
|
|||
console.warn(`Faxing ${value} ...`);
|
||||
}
|
||||
|
||||
log(ref3: any) {
|
||||
console.warn(ref3.constructor);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
# Template reference variables (`#var`)
|
||||
# Template variables
|
||||
|
||||
A **template reference variable** is often a reference to a DOM element within a template.
|
||||
It can also refer to a directive (which contains a component), an element, [TemplateRef](api/core/TemplateRef), or a <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" title="MDN: Web Components">web component</a>.
|
||||
Template variables help you use data from one part of a template in another part of the template.
|
||||
With template variables, you can 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 <a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components" title="MDN: Web Components">web component</a>
|
||||
|
||||
<div class="alert is-helpful">
|
||||
|
||||
|
@ -9,61 +17,138 @@ See the <live-example></live-example> for a working example containing the code
|
|||
|
||||
</div>
|
||||
|
||||
Use the hash symbol (#) to declare a reference variable.
|
||||
The following reference variable, `#phone`, declares a `phone` variable on an `<input>` element.
|
||||
## Syntax
|
||||
|
||||
In the template, you use the hash symbol, `#`, to declare a template variable.
|
||||
The following template variable, `#phone`, declares a `phone` variable on an `<input>` element.
|
||||
|
||||
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-var" header="src/app/app.component.html"></code-example>
|
||||
|
||||
You can refer to a template reference variable anywhere in the component's template.
|
||||
You can 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 path="template-reference-variables/src/app/app.component.html" region="ref-phone" header="src/app/app.component.html"></code-example>
|
||||
|
||||
Angular assigns each template reference variable a value based on where you declare the variable:
|
||||
## 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 the [ng-template](guide/structural-directives#the-ng-template) section of [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.
|
||||
<!-- What does the second half of this mean?^^ Can we explain this more fully? Could I see a working example? -kw -->
|
||||
|
||||
<h3 class="no-toc">How a reference variable gets its value</h3>
|
||||
### Using `NgForm` with template variables
|
||||
|
||||
In most cases, Angular sets the reference variable's value to the element on which it is declared.
|
||||
In most cases, Angular sets the template variable's value to the element on which it occurs.
|
||||
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 can change that behavior and set the value to something else. In the following example, the template reference variable, `itemForm`, appears three times separated
|
||||
by HTML.
|
||||
The `NgForm` directive demonstrates getting a reference to a different value by reference a directive's `exportAs` name.
|
||||
In the following example, the template variable, `itemForm`, appears three times separated by HTML.
|
||||
|
||||
<code-example path="template-reference-variables/src/app/app.component.html" region="ngForm" header="src/app/hero-form.component.html"></code-example>
|
||||
|
||||
The reference value of `itemForm`, without the `ngForm` attribute value, would be
|
||||
the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement).
|
||||
There is, however, a difference between a `Component` and a `Directive` in that a `Component`
|
||||
will be referenced without specifying the attribute value, and a `Directive` will not
|
||||
change the implicit reference (that is, the element).
|
||||
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>`.
|
||||
There is, however, a difference between a `Component` and a `Directive` in that Angular references a `Component` without specifying the attribute value, and a `Directive` does not change the implicit reference, or the element.
|
||||
<!-- 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 -->
|
||||
|
||||
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 allows you to disable the submit button if the `itemForm.form.valid` is invalid.
|
||||
|
||||
|
||||
## Template variable scope
|
||||
|
||||
However, 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.
|
||||
You can 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.
|
||||
|
||||
The native `<form>` element doesn't have a `form` property, but the `NgForm` directive does, which allows disabling the submit button
|
||||
if the `itemForm.form.valid` is invalid and passing the entire form control tree
|
||||
to the parent component's `onSubmit()` method.
|
||||
<div class="alert is-helpful">
|
||||
|
||||
<h3 class="no-toc">Template reference variable considerations</h3>
|
||||
Define a variable only once in the template so the runtime value remains predictable.
|
||||
|
||||
A template _reference_ variable (`#phone`) is not the same as a template _input_ variable (`let phone`) such as in an [`*ngFor`](guide/built-in-directives#template-input-variable).
|
||||
See [_Structural directives_](guide/structural-directives#template-input-variable) for more information.
|
||||
</div>
|
||||
|
||||
The scope of a reference variable is the entire template. So, don't define the same variable name more than once in the same template as the runtime value will be unpredictable.
|
||||
### Accessing in a nested template
|
||||
|
||||
### Alternative syntax
|
||||
An inner template can access template variables that the outer template defines.
|
||||
|
||||
You can use the `ref-` prefix alternative to `#`.
|
||||
This example declares the `fax` variable as `ref-fax` instead of `#fax`.
|
||||
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 path="template-reference-variables/src/app/app.component.html" region="template-ref-vars-scope1" header="src/app/app.component.html"></code-example>
|
||||
|
||||
<code-example path="template-reference-variables/src/app/app.component.html" region="ref-fax" 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.
|
||||
|
||||
Rewriting the above code in a more verbose form explicitly shows the `<ng-template>`.
|
||||
|
||||
```html
|
||||
|
||||
<input #ref1 type="text" [(ngModel)]="firstExample" />
|
||||
|
||||
<!-- New template -->
|
||||
<ng-template [ngIf]="true">
|
||||
<!-- Since the context is inherited, the value is available to the new template -->
|
||||
<span>Value: {{ ref1.value }}</span>
|
||||
</ng-template>
|
||||
|
||||
```
|
||||
|
||||
However, accessing a template variable from outside the parent template doesn't work.
|
||||
|
||||
```html
|
||||
<input *ngIf="true" #ref2 type="text" [(ngModel)]="secondExample" />
|
||||
<span>Value: {{ ref2?.value }}</span> <!-- doesn't work -->
|
||||
```
|
||||
|
||||
The verbose form shows that `ref2` is outside the parent template.
|
||||
|
||||
```
|
||||
<ng-template [ngIf]="true">
|
||||
<!-- The reference is defined within a template -->
|
||||
<input #ref2 type="text" [(ngModel)]="secondExample" />
|
||||
</ng-template>
|
||||
<!-- ref2 accessed from outside that template doesn't work -->
|
||||
<span>Value: {{ ref2?.value }}</span>
|
||||
```
|
||||
|
||||
Consider the following example that uses `*ngFor`.
|
||||
|
||||
```
|
||||
<ng-container *ngFor="let i of [1,2]">
|
||||
<input #ref type="text" [value]="i" />
|
||||
</ng-container>
|
||||
{{ ref.value }}
|
||||
```
|
||||
|
||||
Here, `ref.value` doesn't work.
|
||||
The structural directive, `*ngFor` instantiates the template twice because `*ngFor` iterates over the two items in the array.
|
||||
It is impossible to define what the `ref.value` reference signifies.
|
||||
|
||||
With structural directives, such as `*ngFor` or `*ngIf`, there is no way for Angular to know if a template is ever instantiated.
|
||||
|
||||
As a result, Angular isn't able to access the value and returns an error.
|
||||
|
||||
### 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 path="template-reference-variables/src/app/app.component.html" region="template-ref" header="src/app/app.component.html"></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 language="sh">
|
||||
|
||||
▼ ƒ TemplateRef()
|
||||
name: "TemplateRef"
|
||||
__proto__: Function
|
||||
|
||||
</code-example>
|
||||
|
|
|
@ -192,11 +192,6 @@
|
|||
"title": "Two-way binding",
|
||||
"tooltip": "Introductory guide to sharing data between a class and a template."
|
||||
},
|
||||
{
|
||||
"url": "guide/template-reference-variables",
|
||||
"title": "Template reference variables",
|
||||
"tooltip": "Introductory guide to referring to DOM elements within a template."
|
||||
},
|
||||
{
|
||||
"url": "guide/template-expression-operators",
|
||||
"title": "Template expression operators",
|
||||
|
@ -777,6 +772,11 @@
|
|||
"url": "guide/event-binding-concepts",
|
||||
"title": "How event binding works",
|
||||
"tooltip": "About event binding."
|
||||
},
|
||||
{
|
||||
"url": "guide/template-reference-variables",
|
||||
"title": "Template variables",
|
||||
"tooltip": "Introductory guide to referring to DOM elements within a template."
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue