Angular 8 使用 @ViewChild、@ViewChildren 访问 DOM、组件、指令
概述
- 用于配置一个视图查询。 变更检测器会在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。 如果视图的 DOM 发生了变化,出现了匹配该选择器的新的子节点,该属性就会被更新。
- ViewChild 装饰器用于获取模板视图中的元素或直接调用其组件中的方法。它支持 Type 类型或 string 类型的选择器,同时支持设置 read 查询条件,以获取不同类型的实例。
- ViewChildren 装饰器是用来从模板视图中获取匹配的多个元素,返回的结果是一个 QueryList 集合。
- static:true 用于页面初始化时就显示的数据,可以结合ngAfterViewInit使用
- static:false 用于页面动态加载模板
一、选择器
1.1、任何带有 @Component 或 @Directive 装饰器的类
1.2、字符串形式的模板引用变量
比如可以使用 @ViewChild(‘cmp’) 来查询 <my-component #cmp>
cmp就是模板应用变量。
1.3、组件树中任何当前组件的子组件所定义的提供商(provider)
比如 @ViewChild(SomeService) someService: SomeService , 在子组件中,引用someService.
文件结构
子组件:子组件里面provider里面提供了一个ChildService类
child.component.ts
import { Component, OnInit } from '@angular/core';
import { SomeService } from '../some.service';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
providers: [SomeService]
})
export class ChildComponent implements OnInit {
name: string;
constructor(private someService: SomeService) { }
ngOnInit() {
this.name = this.someService.getname();
}
}
child.component.html
<p>我是子组件</p>
<p>someService getname:{{name}}</p>
some.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SomeService {
constructor() { }
getname() {
return 'success';
}
}
结果:获取到了success
接下来将 providers: [SomeService]改成@ViewChild(SomeService) someService: SomeService
import { Component, OnInit, ViewChild, AfterViewInit, Inject } from '@angular/core';
import { SomeService } from '../some.service';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
})
export class ChildComponent implements OnInit, AfterViewInit {
name: string;
@ViewChild('SomeService', { static: false }) someService: SomeService;
constructor() {
console.log(this.someService);
}
ngOnInit() {
console.log(this.someService);
}
ngAfterViewInit() {
this.name = this.someService.getname();
}
}
会报错,没有getname,暂时未找到原因。。。。。
1.4、任何通过字符串令牌定义的提供商
比如 @ViewChild(‘someToken’) someTokenVal: any
1.5、TemplateRef
比如可以用 @ViewChild(TemplateRef) template; 来查询
选择器是TemplateRef的时候,则会获取到html里面所有的ng-template的节点
综上所述,参考angular 官网,使用还有待实践。
二、适用场景
2.1、DOM
如果想通过@ViewChild、@ViewChildren访问到DOM节点的话都得先给相应的DOM节点设置模板应用变量。例如下面的test就是一个模板应用变量。
<div #testDiv>
<p>我是子组件</p>
<p>someService getname:{{name}}</p>
</div>
2.1.1、获取DOM里面宿主视图
可以简单把div,input,label等等,当然了还包括我们自定义的一些组件都认为是宿主视图。用ElementRef变量接收到获取到的宿主视图元素。
import { Component, OnInit, ViewChild, AfterViewInit, Inject, ElementRef } from '@angular/core';
import { SomeService } from '../some.service';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
})
export class ChildComponent implements OnInit, AfterViewInit {
name: string;
@ViewChild('testDiv', { static: false }) testDivChildComponent: ElementRef;
constructor(private someService: SomeService) {
}
ngOnInit() {
}
ngAfterViewInit() {
// dom 节点
console.log(this.testDivChildComponent.nativeElement);
this.name = this.someService.getname();
}
}
结果获取到child组件的dom节点了。
2.1.2、获取DOM里面嵌入视图
可以简单的吧 ng-template的内容认为是嵌入视图。
<ng-template #domTemplate>
<div>test默认我是不会显示的</div>
</ng-template>
<ng-template>
<div>123</div>
</ng-template>
child组件获取自身的dom
import { Component, OnInit, ViewChild, AfterViewInit, Inject, ElementRef, TemplateRef, ViewChildren, QueryList } from '@angular/core';
import { SomeService } from '../some.service';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
})
export class ChildComponent implements OnInit, AfterViewInit {
name: string;
// 宿主视图
@ViewChild('testDiv', { static: false }) testDivChildComponent: ElementRef;
// 嵌入视图
@ViewChild('domTemplate', {static: false}) domTemplate: TemplateRef<any>; // 查找嵌入元素
// @ViewChild(TemplateRef) @ViewChildren(TemplateRef)获取页面上的ng-template节点信息
@ViewChild(TemplateRef, {static: false}) template: TemplateRef<any>;
@ViewChildren(TemplateRef) templateList: QueryList<TemplateRef<any>>;
// 注入服务
constructor(private someService: SomeService) {
}
ngOnInit() {
}
ngAfterViewInit() {
console.log(this.domTemplate);
console.log(this.template);
if (this.templateList != null && this.templateList.length !== 0) {
this.templateList.forEach(elementRef => console.log(elementRef));
}
// dom 节点
console.log(this.testDivChildComponent.nativeElement);
// 使用服务方法
this.name = this.someService.getname();
}
}
注:默认情况下ng-template的dom不显示,具体为什么,还在展开ng-template详说。
2.2、组件
大部分情况下第三方库里面的组件或者我们自定义的一些组件在父组件里面都是需要拿到这个子组件对象的及子组件的方法。有两种获取获取到组件:一个是通过模板变量名、另一个是通过组件类。
一定要在ngAfterViewInit之后才可以调用方法和dom
子组件:
import { Component, OnInit, ViewChild, AfterViewInit, Inject, ElementRef, TemplateRef, ViewChildren, QueryList } from '@angular/core';
import { SomeService } from '../some.service';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
})
export class ChildComponent implements OnInit, AfterViewInit {
name: string;
// 宿主视图
@ViewChild('testDiv', { static: false }) testDivChildComponent: ElementRef;
// 嵌入视图
@ViewChild('domTemplate', {static: false}) domTemplate: TemplateRef<any>; // 查找嵌入元素
// @ViewChild(TemplateRef) @ViewChildren(TemplateRef)获取页面上的ng-template节点信息
@ViewChild(TemplateRef, {static: false}) template: TemplateRef<any>;
@ViewChildren(TemplateRef) templateList: QueryList<TemplateRef<any>>;
// 注入服务
constructor(private someService: SomeService) {
}
ngOnInit() {
}
ngAfterViewInit() {
console.log(this.domTemplate);
console.log(this.template);
if (this.templateList != null && this.templateList.length !== 0) {
this.templateList.forEach(elementRef => console.log(elementRef));
}
// dom 节点
console.log(this.testDivChildComponent.nativeElement);
// 使用服务方法
this.name = this.someService.getname();
}
getValue() {
console.log('app-child中的方法!');
}
}
父组件:
import { Component, ViewChild, ElementRef, AfterViewInit, OnInit, QueryList, ViewChildren } from '@angular/core';
import { ChildComponent } from './child/child.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit, OnInit {
title = 'demos';
@ViewChild('appChild', {static : false}) componentChild: ChildComponent; // 通过模板变量名获取
@ViewChild(ChildComponent, { static: false }) componentChildByType: ChildComponent; // 直接通过组件类型获取
@ViewChild('appChild', { static: false }) componentChildElement: ElementRef; // 直接找到子组件对应的DOM
@ViewChildren(ChildComponent) componentChildList: QueryList<ChildComponent>; // 获取所有的
ngOnInit() {}
ngAfterViewInit() {
console.log(this.componentChild);
console.log(this.componentChildByType);
console.log(this.componentChildElement);
if (this.componentChildList != null && this.componentChildList.length !== 0) {
this.componentChildList.forEach(elementRef => console.log(elementRef));
}
this.runChild();
}
runChild() {
// 获取子组件方法
this.componentChildByType.getValue();
this.componentChild.getValue();
}
}
2.3、指令
@ViewChild、@ViewChildren也是可以获取到指令对象的。指令对象的获取和组件对象的获取差不多,唯一不同的地方就是用模板变量名获取指令的时候要做一些特殊的处理(要使用exportAs)。我们还是用具体的实例来说明。
定义的指令:
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[appTestDirective]',
exportAs: 'appTest'
})
export class TestDirective {
constructor(private elementRef: ElementRef) {
this.elementRef.nativeElement = '我添加了指令';
}
}
<input appTestDirective />
<br />
<input appTestDirective #divTestDirective='appTest' />
import { Component, ViewChild, ElementRef, AfterViewInit, OnInit, QueryList, ViewChildren } from '@angular/core';
import { ChildComponent } from './child/child.component';
import { TestDirective } from './my-directive.directive';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit, OnInit {
title = 'demos';
@ViewChild('appChild', {static : false}) componentChild: ChildComponent; // 通过模板变量名获取
@ViewChild(ChildComponent, { static: false }) componentChildByType: ChildComponent; // 直接通过组件类型获取
@ViewChild('appChild', { static: false }) componentChildElement: ElementRef; // 直接找到子组件对应的DOM
@ViewChildren(ChildComponent) componentChildList: QueryList<ChildComponent>; // 获取所有的
// 获取html里面所有的TestDirective
@ViewChildren(TestDirective) testDirectiveList: QueryList<TestDirective>;
// 获取模板变量名为divTestDirective的TestDirective的指令,这个得配合指令的exportAs使用
@ViewChild('divTestDirective', {static: false}) testDirective: TestDirective;
ngOnInit() {}
ngAfterViewInit() {
console.log(this.componentChild);
console.log(this.componentChildByType);
console.log(this.componentChildElement);
if (this.componentChildList != null && this.componentChildList.length !== 0) {
this.componentChildList.forEach(elementRef => console.log(elementRef));
}
this.runChild();
// 指令
console.log(this.testDirectiveList);
console.log(this.testDirective);
if (this.testDirectiveList != null && this.testDirectiveList.length !== 0) {
this.testDirectiveList.forEach(elementRef => console.log(elementRef));
}
}
runChild() {
// 获取子组件方法
this.componentChildByType.getValue();
this.componentChild.getValue();
}
}