基本概念
模板引用变量是模板中某一元素的引用,这个元素可以是标准的 HTML 元素,可以是 Angular 组件,也可以是 ng-template 元素(angular 的结构型指令,常见于UI框架),甚至也可以是完全自定义的元素(Web Components,使用较少)。
定义模板引用变量
# + 变量名称
例子:
<input #phone placeholder="phone number" />
引用标准 HTML 元素
使用模板引用变量来引用 HTML 元素,在实际开发中是十分常见的。
例子:
// html
<section>
<input #person placeholder="" type="text" />
<button (click)="showName(person.value)">Search</button>
</section>
// ts
showName(name:string){
console.log(name);
}
引用 Angular 组件
通过模板引用变量,我们可以很轻松地获得组件内部的变量和方法。
例子:
// 子组件:my-component.component.html
<p>
hello {{ name }} !
</p>
// 子组件:my-component.component.ts
name:string = 'Tom';
welcome:string = 'Welcome to China, nice to meet you!';
changeName(){
this.name = 'Amy';
}
// html
// 引用子组件 my-component.component
<section>
<app-my-component #person></app-my-component>
<p>{{ person.welcome }}</p>
<button (click)="person.changeName()">Change</button>
</section>
引用 ng-template
ng-template 是 Angular 的一个结构型指令,用于定义内嵌模板。但其定义的模板不会直接显示出来,而是需要通过 ngif 等指令或者通过 ViewContainerRef 的 createEmbeddedView() 方法将内容加载到页面上。
ng-template 与 ngif 指令
例子:
// html
<section>
<p *ngIf="expression else myTemplate">Welcome to China</p>
<ng-template #myTemplate>
<p>Welcome to Korea</p>
</ng-template>
</section>
// ts
expression = false;
例子中,如果 expression 为 true,则展示“Welcome to China”,否则展示“Welcome to Korea”。
ng-template 与 ViewContainerRef 接口
例子:
// app.component.html
<section>
<button (click)="showMyTemplate()">show</button>
<ng-template #myTemplate>
<p>Welcome to Korea</p>
</ng-template>
</section>
// app.component.ts
// 引入 ViewChild、TemplateRef、ViewContainerRef
// ViewChild: 装饰器,Angular 提供的元素查询工具,可以使用它在模板中查询模板引用变量
// TemplateRef: Angular 提供的内嵌模板的接口,指向 <ng-template> 元素
// ViewContainerRef: Angular 提供的用于创建和管理内嵌模板的接口
import { Component, OnInit, ViewChild, TemplateRef, ViewContainerRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
// 取到变量 myTemplate,类型为 TemplateRef
@ViewChild('myTemplate')
myRef!: TemplateRef<any>;
constructor(
// 初始化 ViewContainerRef 接口
private viewRef : ViewContainerRef
) { }
ngOnInit() {
}
showMyTemplate(){
// 调用 ViewContainerRef 中的 createEmbeddedView() 方法
// 将内嵌模板的内容加载到页面上
this.viewRef.createEmbeddedView(this.myRef);
}
}
模板引用变量的作用域
说到模板引用变量的作用域,我们就不得不重提一下结构型指令,在 Angular 中,* 符号意味着简写:
*ngIf="exp"
*ngFor="let item of [1,2,3]"
实际上,非简写的形式是围绕 ng-template 元素进行的,只是由简写转换为普通语法的过程,Angular 已经帮助我们去完成了:
<ng-template [ngIf]="exp">
<ng-template ngFor let-item [ngForOf]="[1,2,3]">
因此,在模板变量的作用域的定义中,提到了不可以在模板的外部访问该模板内部的模板变量,而找到这个内部和外部的边界,就显得尤为重要,通常可以将结构型指令(ngIf、ngFor、ng-template)当作这个边界。
例子:
// html
<section>
<input *ngIf="true" type="text" [(ngModel)]="name" #person>
<p>{{ person.value }}</p>
</section>
// ts
name:string = '';
如果现在无法判断是否可以访问到模板变量 person,我们可以恢复一下例子的非简写模式:
例子:
// html
<section>
<ng-template [ngIf]="true">
<input type="text" [(ngModel)]="name" #person>
</ng-template>
<p>{{ person.value }}</p>
</section>
// ts
name:string = '';
此时可以看出来,例子中,我们是在 ng-template 模板的外部(p 元素处)访问其内部的变量 person,因此是访问不到的,会报错。
再看下面的例子:
例子:
// html
<section>
<input type="text" [(ngModel)]="name" #person>
<p *ngIf="true">{{ person.value }}</p>
</section>
// ts
name:string = '';
将例子恢复成非简写模式:
例子:
// html
<section>
<input type="text" [(ngModel)]="name" #person>
<ng-template [ngIf]="true">
<p>{{ person.value }}</p>
</ng-template>
</section>
// ts
name:string = '';
此时,我们是在 ng-template 模板的内部访问其外部(或者叫父辈)的变量 person,根据 ES 的查询机制,这样是可行的,因此,可以访问到变量 person。
end