简介
先给出一个核心结论:ViewChild 是 Angular 中非常实用的装饰器,专门用来解决父组件直接访问子组件、指令或 DOM 元素的常见需求。它的作用是返回与指定指令、组件或模板引用选择器匹配的第一个元素,掌握 Angular ViewChild 用法能大幅提升组件间数据交互的效率。

先决条件
想动手实践?需要做好以下准备工作:
- 确保已安装
@angular/cli工具。 - 使用
@angular/cli创建一个新项目,专门用来测试ViewChild的各种场景。
本教程已在 @angular/core v13.0.2 和 @angular/cli v13.0.3 环境下验证通过,所有示例均可直接运行。
用 ViewChild 访问指令
先从指令入手。ViewChild 让父组件能够轻松获取指令内部的数据或行为,这是 Angular 组件通信的重要技巧。
假设有一个 SharkDirective,它会查找带有 appShark 属性的元素,并在元素文本前加上 "Shark" 字样。
使用 @angular/cli 生成指令最快捷——
ng generate directive shark --skip-tests
执行命令后,自动生成 shark.directive.ts,并将该指令注册到 app.module.ts:
import { SharkDirective } from './shark.directive';
...
@NgModule({
declarations: [
AppComponent,
SharkDirective
],
...
})
接下来,借助 ElementRef 和 Renderer2 操作文本内容。将 shark.directive.ts 替换为以下代码:
import {
Directive,
ElementRef,
Renderer2
} from '@angular/core';
@Directive(
{ selector: '[appShark]' }
)
export class SharkDirective {
creature = 'Dolphin';
constructor(elem: ElementRef, renderer: Renderer2) {
let shark = renderer.createText('Shark ');
renderer.appendChild(elem.nativeElement, shark);
}
}
然后,在组件模板中找一个带文本的 span,加上 appShark 属性。将 app.component.html 替换为:
Fin!
在浏览器中可以看到,元素内容前面多出了 "Shark" 字样:
Shark Fin!
现在,你可以更进一步——访问 SharkDirective 中的 creature 实例变量,并用它的值设置一个 extraCreature 变量。将 app.component.ts 替换为:
import {
Component,
ViewChild,
AfterViewInit
} from '@angular/core';
import { SharkDirective } from './shark.directive';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
extraCreature!: string;
@ViewChild(SharkDirective)
set appShark(directive: SharkDirective) {
this.extraCreature = directive.creature;
};
ngAfterViewInit() {
console.log(this.extraCreature); // Dolphin
}
}
这段代码通过 setter 为 extraCreature 赋值。需要注意,必须在 AfterViewInit 生命周期钩子触发后才访问该变量——因为此时子组件和指令才真正初始化完成。
再次查看浏览器,页面依旧显示 "Shark Fin!",但控制台日志输出:
Dolphin
这意味着父组件已成功读取指令中的属性值,这是 Angular ViewChild 访问指令的典型应用。
用 ViewChild 访问 DOM 元素
ViewChild 的另一个常见用途是通过模板引用变量直接操作原生 DOM 元素,这是实现 Angular 中 DOM 操作的高效方式。
假设模板中有一个 ,带有 #someInput 引用变量。将 app.component.html 替换为:
然后,用 ViewChild 定位这个输入框,并设置其值。将 app.component.ts 替换为:
import {
Component,
ViewChild,
AfterViewInit,
ElementRef
} from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
@ViewChild('someInput') someInput!: ElementRef;
ngAfterViewInit() {
this.someInput.nativeElement.value = 'Whale!';
}
}
当 ngAfterViewInit 被触发时,输入框的 value 将变为:
Whale!
如此简单——父组件现在可以自由设置子 DOM 元素的值,这也是 Angular ViewChild 操作 DOM 的核心用法。
用 ViewChild 访问子组件
最后来看访问子组件的场景。ViewChild 使父组件能够调用子组件暴露的方法或读取其实例变量,这对于 Angular 父子组件通信至关重要。
假设有一个 PupComponent。同样,用 @angular/cli 生成:
ng generate component pup --flat --skip-tests
这条命令会创建 pup.component.ts、pup.component.css 和 pup.component.html,并将组件添加到 app.module.ts:
import { PupComponent } from './pup.component';
...
@NgModule({
declarations: [
AppComponent,
PupComponent
],
...
})
然后在 PupComponent 中添加一个简单的 whoAmI 方法,返回字符串信息:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-pup',
templateUrl: './pup.component.html',
styleUrs: ['./pup/component.css']
})
export class PupComponent implements OnInit {
constructor() { }
whoAmI() {
return 'I am a pup component!';
}
ngOnInit(): void {
}
}
接下来在应用模板中引用这个子组件。将 app.component.html 替换为:
pup works!
现在,使用 ViewChild 在父组件类中调用 whoAmI 方法。将 app.component.ts 替换为:
import {
Component,
ViewChild,
AfterViewInit
} from '@angular/core';
import { PupComponent } from './pup.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent implements AfterViewInit {
@ViewChild(PupComponent) pup!: PupComponent;
ngAfterViewInit() {
console.log(this.pup.whoAmI()); // I am a pup component!
}
}
在浏览器中打开应用,控制台日志会显示:
I am a pup component!
父组件就这样成功调用了子组件的方法,这也是 Angular ViewChild 访问子组件的标准实践。
小结
通过本教程,我们完整演示了 ViewChild 的三种典型用法:访问指令、操作 DOM 元素以及调用子组件。这些技巧是 Angular 开发中实现组件间交互的重要基础。
还有一个值得注意的细节:如果引用的元素在运行时被动态替换,ViewChild 会自动更新引用,无需手动干预。
当然,如果父组件需要同时访问多个子元素,则应该使用 ViewChildren——那是另一个同样强大的工具。
