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>
|
<h2>Reference variables, forms, and NgForm</h2>
|
||||||
<!-- #docregion ngForm -->
|
<!-- #docregion ngForm -->
|
||||||
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
|
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
|
||||||
<label for="name"
|
<label for="name">Name <input class="form-control" name="name" ngModel required />
|
||||||
>Name <input class="form-control" name="name" ngModel required />
|
|
||||||
</label>
|
</label>
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -53,3 +52,60 @@
|
||||||
|
|
||||||
|
|
||||||
<p>JSON: {{ itemForm.form.value | json }}</p>
|
<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']
|
styleUrls: ['./app.component.css']
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
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; }
|
get submitMessage() { return this._submitMessage; }
|
||||||
private _submitMessage = ''; // tslint:disable-line: variable-name
|
private _submitMessage = ''; // tslint:disable-line: variable-name
|
||||||
|
@ -25,4 +56,7 @@ export class AppComponent {
|
||||||
console.warn(`Faxing ${value} ...`);
|
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.
|
Template variables help you use data from one part of a template in another part of the 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>.
|
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">
|
<div class="alert is-helpful">
|
||||||
|
|
||||||
|
@ -9,61 +17,138 @@ See the <live-example></live-example> for a working example containing the code
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
Use the hash symbol (#) to declare a reference variable.
|
## Syntax
|
||||||
The following reference 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 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>
|
<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.
|
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>
|
<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 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 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.
|
* 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.
|
* 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>`.
|
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 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
|
The `NgForm` directive demonstrates getting a reference to a different value by reference a directive's `exportAs` name.
|
||||||
by HTML.
|
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>
|
<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
|
Without the `ngForm` attribute value, the reference value of `itemForm` would be
|
||||||
the [HTMLFormElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement).
|
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 a `Component`
|
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.
|
||||||
will be referenced without specifying the attribute value, and a `Directive` will not
|
<!-- 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 -->
|
||||||
change the implicit reference (that is, the element).
|
|
||||||
|
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")
|
You can refer to a template variable anywhere within its surrounding template.
|
||||||
directive with the ability to track the value and validity of every control in the form.
|
[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
|
<div class="alert is-helpful">
|
||||||
if the `itemForm.form.valid` is invalid and passing the entire form control tree
|
|
||||||
to the parent component's `onSubmit()` method.
|
|
||||||
|
|
||||||
<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).
|
</div>
|
||||||
See [_Structural directives_](guide/structural-directives#template-input-variable) for more information.
|
|
||||||
|
|
||||||
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 `#`.
|
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`.
|
||||||
This example declares the `fax` variable as `ref-fax` instead of `#fax`.
|
|
||||||
|
|
||||||
|
<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",
|
"title": "Two-way binding",
|
||||||
"tooltip": "Introductory guide to sharing data between a class and a template."
|
"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",
|
"url": "guide/template-expression-operators",
|
||||||
"title": "Template expression operators",
|
"title": "Template expression operators",
|
||||||
|
@ -777,6 +772,11 @@
|
||||||
"url": "guide/event-binding-concepts",
|
"url": "guide/event-binding-concepts",
|
||||||
"title": "How event binding works",
|
"title": "How event binding works",
|
||||||
"tooltip": "About event binding."
|
"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