了解一个问题要从:是什么,为什么,怎么做。逐步深入去考虑
基础知识
什么是dom
参见MDN
为什么要操作dom(应用场景)
搜索相关文章,只有 “ 为什么减少操作dom ”,之类的文章,但是没有说,我们在开发中为什么需要操作dom
这里操作dom说的是,在使用框架的前提下,为什么需要操作dom(不用框架,用原生或JQ,就是直接和dom打交道的)
但是,有了框架构建的声明式编程范式的前提下,我们为什么有时候还需要操作dom,我个人理解的是:
怎么做?
正文开始
浅析angular中对dom的操作
先上总结
个人认为,看着总结,脑子里有个整体观念,再去具体看问题,会清晰一些
ViewChild获取ng的DOM的抽象类(区分虚拟dom)ElementRef- 模板:
TemplentRef创建嵌入视图 - 组件:
ComponentRef创建宿主视图
看似有很多新知识需要消化啊,但实际上 Angular 通过视图操作 DOM 的思路模型是很清晰和连贯的。你可以使用 ViewChild 查询模板引用变量来获得 Angular DOM 抽象类。DOM 元素的最简单封装是 ElementRef;而对于模板,你可以使用 TemplateRef 来创建嵌入视图;而对于组件,可以使用 ComponentRef 来创建宿主视图,同时又可以使用 ComponentFactoryResolver 创建 ComponentRef。这两个创建的视图(即嵌入视图和宿主视图)又会被 ViewContainerRef 管理。最后,Angular 又提供了两个快捷指令自动化这个过程:ngTemplateOutlet 指令使用模板创建嵌入视图;ngComponentOutlet 使用动态组件创建宿主视图。
@ViewChild
Angular 提供了一种叫做 DOM Query 的技术
@ViewChild: 返回单个引用,在视图的 DOM 中查找能匹配上该选择器的第一个元素或指令。
@ViewChildren :返回由 QueryList 对象包装好的多个引用,在视图的 DOM 中查找能匹配上该选择器的所有元素或指令。
@ViewChild([reference from template], {read: [reference type]});
使用 ViewChild装饰的 DOM 元素会返回 ElementRef
@Component({
selector: 'sample',
template: `
<span #tref>I am span</span>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tref", {read: ElementRef}) tref: ElementRef;
ngAfterViewInit(): void {
// outputs `I am span`
console.log(this.tref.nativeElement.textContent);
}
}
他就是相当于拿到了这个dom,这个东西,好像是包含着原生dom对象
DOM Query 的技术查找出来的对象分为三类:
- ElementRef:如果它挂载的是类似 span 的简单 html 元素;
- TemplateRef: 如果它挂载的是 template 元素;
- ViewContainerRef: 无法推断,一般要程序员要在read中指明,任何DOM元素都可以被用作为视图容器。
ElementRef
这是最基本的抽象类,如果你查看它的类结构,就发现它只包含所挂载的元素对象
TemplateRef
ng-template 是 Angular 提供的类似于 template 原生 html 标签
@Component({
selector: 'sample',
template: `
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("tpl") tpl: TemplateRef<any>;
ngAfterViewInit() {
let elementRef = this.tpl.elementRef;
// outputs `template bindings={}`
console.log(elementRef.nativeElement.textContent);
}
}
ViewRef
该抽象表示一个 Angular 视图(View)
- 嵌入视图(Embedded View),由
Template提供 - 宿主视图(Host View),由
Component提供
创建嵌入视图
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
创建宿主视图
宿主视图是在组件动态实例化时创建的,一个动态组件(dynamic component)可以通过 ComponentFactoryResolver 创建:
constructor(private injector: Injector,
private r: ComponentFactoryResolver) {
let factory = this.r.resolveComponentFactory(ColorComponent);
let componentRef = factory.create(injector);
let view = componentRef.hostView;
}
一旦视图被创建,它就可以使用 ViewContainer 插入 DOM 树中。
ViewContainerRef
视图容器就是挂载一个或多个视图的容器。
任何 DOM 元素都可以作为视图容器
比较好的方式是把 ViewContainer 绑定在 ng-container 元素上,因为 ng-container 元素会被渲染为注释,从而不会在 DOM 中引入多余的 html 元素。
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container #vc></ng-container>
<span>I am last span</span>
`
})
export class SampleComponent implements AfterViewInit {
@ViewChild("vc", {read: ViewContainerRef}) vc: ViewContainerRef;
ngAfterViewInit(): void {
// outputs `template bindings={}`
console.log(this.vc.element.nativeElement.textContent);
}
}
操作视图
创建视图
ngTemplateOutlet 和 ngComponentOutlet
根据一个提前备好的 TemplateRef 插入一个内嵌视图。
@Component({
selector: 'sample',
template: `
<span>I am first span</span>
<ng-container [ngTemplateOutlet]="tpl"></ng-container>
<span>I am last span</span>
<ng-template #tpl>
<span>I am span in template</span>
</ng-template>
`
})
export class SampleComponent {}
ngComponentOutlet
这个指令与 ngTemplateOutlet 很相似,区别是 ngComponentOutlet 创建的是由组件实例化生成的宿主视图,不是嵌入视图。
<ng-container *ngComponentOutlet="ColorComponent"></ng-container>
避免使用 ElementRef
允许直接访问 DOM 会导致你的应用在 XSS 攻击前面更加脆弱。
应用
动态组件 为什么?
我们先明确下动态组件的使用场景,在代码运行时要动态加载组件,换成普通人话,代码需要根据具体情况(比如用户的操作,向后台请求结果)确定在某些地方加载某些组件,这些组件不是静态的(不是固定的)。
再比如
动态弹出框,弹出的组件是不确定的、不断更新的,这里那里弹出个购买框,那那那又需要弹出样式选择框,静态组件结构模板是不能满足群众日渐增长的需求。
怎么做?
Q 动态组件放在哪
A 放在锚点上,锚点就是DOM,这个时候就需要用到上文的 操作DOM 的相关知识了
只有ViewContainerRef才是可以将一个或多个视图附着到组件中的容器,也就是只有它才可以加载组件。
它是一个可以将新的组件作为其兄弟(节点)的DOM元素(容器),是兄弟,不是父子
获取ViewContainerRef有两种方式:
- DOM Query查询@ViewChild获取
<ng-container #addComp></ng-container>
@ViewChild('addComp', {read: ViewContainerRef}) adComp:ViewContainerRef;
- 官网里的例子,用依赖注入
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
selector: '[ad-host]',
})
export class AdDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}
锚点设置ng-template上,通过指令注入获取ViewContainerRef
template: `
<div class="ad-banner-example">
<h3>Advertisements</h3>
<ng-template ad-host></ng-template>
</div>
`
太麻烦了,不懂啊,去看官网的弹窗组件例子吧,头晕