https://blog.angularindepth.com/exploring-angular-dom-abstractions-80b3ebcfc02javascript
原文连接,墙裂推荐阅读原文,事实上这篇文章网上已有的翻译都有或多或少的错误,我这篇确定也不例外。html
Angular文档中关于使用Angular DOM的操做,老是会提到一个或几个类: ElementRef, TemplateRef, ViewContainerRef等。本文旨在描述这种模型。java
在Angular中DOM被抽象出ElementRef
, TemplateRef
, ViewRef
, ComponentRef
和ViewContainerRef
。web
@ViewChild
| @ViewChildren
在深刻这些DOM抽象以前,咱们先了解一下如何在组件/指令类中访问这些DOM抽象。Angular提供了一个DOM查询机制。这个机制由@ViewChild
和@ViewChildren
两个装饰器完成。他俩的使用方法是一毛同样的,只是返回结果有所不一样,显而易见,后折返回一个列表,前者只返回一个引用。typescript
一般状况下,这些装饰器都和模板引用标识(template reference variable)
同时出现,模板引用标识(template reference variable)
是一个在模板文件里给dom元素命名的东西,你也能够把它理解成相似于dom元素的id的存在。给一个元素增长一个引用标识,你就能够经过这两个装饰器来获取它们。浏览器
@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);
}
}
复制代码
@ViewChild的基本语法为@ViewChild([reference from template], {read: [reference type]});
第二个参数并非必须的,假如他是一个简单的dom元素,相似span这样的,angular会推断为ElementRef。若是是一个template元素,则会推断为TemplateRef。固然,也有一些类型好比ViewContainerRef是不能被推断出来的,须要手动声明在read的值中,Others, like ViewRef cannot be returned from the DOM and have to be constructed manually.安全
ok,如今咱们知道怎么执行dom查询了,咱们来开始深刻这些dom抽象吧~数据结构
ElementRef能够说是坠基本的dom抽象了。app
class ElementRef<T> {
constructor(nativeElement: T)
nativeElement: T
}
复制代码
这个类中只包含了与之关联的原生元素,经过它你能够很轻松的查找到dom元素。dom
console.log(this.tref.nativeElement.textContent);
可是Angular并不推荐这种直接dom元素进行操做的方法,不光是安全缘由,更是由于这样作会使得一套代码多平台运行的原则受到了打破,它使得应用和渲染层紧密耦合。我嚼的这并非由于使用nativeElement
致使的,而是由于使用了textContent
这样的DOM API致使的。事实上Angular所实现的DOM操做模型几乎没有用到这么底层的dom访问。
对任何dom元素使用@ViewChild
装饰器都能返回ElementRef。由于全部的组件其实都是被host在普通的DOM元素上,全部的指令都是做用于DOM元素,因此借助于Angular的依赖注入机制,全部的组件/指令类能够获取它们的**宿主元素(host element)**的ElementRef。方法以下:
@Component({
selector: 'sample',
...
})
export class SampleComponent{
constructor(private hostElement: ElementRef) {
//outputs <sample>...</sample>
console.log(this.hostElement.nativeElement.outerHTML);
}
}
复制代码
因此既然一个组件能够经过DI机制轻松的获取它的宿主元素,@ViewChild装饰器通常就用来获取模板中的一个子元素了。对指令而言则是彻底相反的,它们没有视图、没有子元素,因此它们一般与他们所依附的元素一块儿工做。
模板的概念对于广大web开发者而言能够说是见的多了。它是在应用中能被屡次复用的一个DOM元素的集合。在HTML5将template便签列入标准以前,多数模板都是经过script标签包裹的方式实现的。这种实现方式不是本文的重点,因此咱们按住不表。
咱们来说讲template标签,做为HTML5新增长的标签,浏览器会解析这个标签并生成对应的DOM元素,可是并不会直接渲染到页面上。经过template元素的content属性咱们就能够获取到这个DOM元素。
<script> let tpl = document.querySelector('#tpl'); let container = document.querySelector('.insert-after-me'); insertAfter(container, tpl.content); </script>
<div class="insert-after-me"></div>
<ng-template id="tpl">
<span>I am span in template</span>
</ng-template>
复制代码
Angular拥抱了这种实现方式,并声明了TemplateRef类来与**模板(template)**一块儿工做。使用方法以下:
@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);
}
}
复制代码
Angular在渲染过程当中移除了template标签。并在其位置插入了一条注释,
<sample>
<!--template bindings={}-->
</sample>
复制代码
下面是Angular文档中对TemplateRef类的描述 TemplateRef类的数据结构以下:
class TemplateRef<C> {
get elementRef: ElementRef
createEmbeddedView(context: C): EmbeddedViewRef<C>
}
复制代码
The location in the View where the Embedded View logically belongs to.
他有一个属性elementRef: ElementRef,表明了这个内嵌视图在view中所属的位置,也就是ng-template
标签上的模板引用标识获取的ElementRef。
The data-binding and injection contexts of Embedded Views created from this TemplateRef inherit from the contexts of this location.
Typically new Embedded Views are attached to the View Container of this location, but in advanced use-cases, the View can be attached to a different container while keeping the data-binding and injection context from the original location.
他有一个方法createEmbeddedView()
能够建立一个内嵌视图并将其驻留在一个视图容器上,同时还能返回一个对视图的引用:ViewRef。
ViewRef正是对Angular中最基本的UI构成-View的抽象。他是一系列元素的最小集合,被同时建立出来又被同时销毁。Angular高度鼓励开发者们将UI视为一系列View的组成,而不是一个个html标签组成的树。
Angular有且仅有两种视图类型:内嵌视图(Embedded Views)和宿主视图(Host Views)。一般状况下,内嵌视图每每跟Template相关联,而宿主视图与组件相关联。
TemplateRef中的方法createEmbeddedView()
方法能够直接建立出一个内嵌视图,该方法返回的类型正是ViewRef;
ngAfterViewInit() {
let view = this.tpl.createEmbeddedView(null);
}
复制代码
当一个组件被动态生成时,宿主视图也就随之被建立出来了。经过ComponentFactoryResolver
类你能够轻松的动态建立出一个组件。
constructor( private _injector: Injector, private _r: ComponentFactoryResolver, ) {
let factory = this._r.resolveComponentFactory(aComponent);
let componentRef = factory.create(this._injector);
let view = componentRef.hostView;
}
复制代码
这里薛薇的解释一下以上代码,在Angular中每一个组件都和一个**注入器(Injector)**的实例绑定的,所以当咱们动态建立一个组件的时候,咱们会把当前的注入器实例传递进create()方法中。固然,除了上述代码,若是你想要获取一个组件的组件工厂,你须要在当前模块的entryComponents中声明这个组件。
下面是Angular官网中对VIewRef类的一点描述:
class ViewRef extends ChangeDetectorRef {
get destroyed: boolean
destroy(): void
onDestroy(callback: Function): any
// 自ChangeDetectorRef继承来的属性方法
markForCheck(): void
detach(): void
detectChanges(): void
checkNoChanges(): void
reattach(): void
}
复制代码
好的,如今咱们知道了如何建立内嵌视图和宿主视图。当这些视图建立完毕以后咱们就能够经过ViewContainer将他们插入到DOM中。
首先要声明的是,任何DOM元素均可以被用做为视图容器。英催思挺的是Angular不会在元素内部插入视图,而是把这些视图添加到绑定到ViewContainer的元素后面。这跟router-outlet
插入组件的方法灰常类似。
一般状况下,一个坠佳的建立ViewContainer的地方是<ng-container></ng-container>
标签。最终它会被渲染为一行注释而不会在dom中引入冗余的元素。
@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 one line of comment
console.log(this.vc.element.nativeElement.textContent);
}
}
复制代码
Angular文档中对于ViewContainerRef类的描述以下:
class ViewContainerRef {
get element: ElementRef
get injector: Injector
get parentInjector: Injector
get length: number
clear(): void
get(index: number): ViewRef | null
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>
insert(viewRef: ViewRef, index?: number): ViewRef
move(viewRef: ViewRef, currentIndex: number): ViewRef
indexOf(viewRef: ViewRef): number
remove(index?: number): void
detach(index?: number): ViewRef | null
}
复制代码
前面咱们已经知道了两种视图类型是如何从template和component建立出来的,一旦咱们有了view,就能够经过viewContainer的insert()方法将其插入到dom中。
import {
AfterViewInit,
Component,
TemplateRef,
ViewChild,
ViewContainerRef
} from '@angular/core';
@Component({
selector: 'app-root',
template: ` <span>firsr para</span> <ng-container #vc></ng-container> <span>second para</span> <ng-template #tpl> <span>the para in template</span> </ng-template> `
})
export class AppComponent implements AfterViewInit {
@ViewChild('vc', {read: ViewContainerRef}) viewContainer: ViewContainerRef;
@ViewChild('tpl') tpl: TemplateRef<any>;
ngAfterViewInit() {
const tp_view = this.tpl.createEmbeddedView(null);
this.viewContainer.insert(tp_view);
set
}
}
// 输出
// <span>firsr para</span>
// <!---->
// <span>the para in template</span>
// <span>second para</span>
// <!---->
复制代码
要移除这个被插入的元素,只要调用viewContainer的detach()方法。全部其余方法都是自解释性的,可用于获取索引视图的引用,将视图移到另外一个位置,或者从容器中删除全部视图。
createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>
createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
复制代码
这两个方法至关于对咱们上面的代码进行了一层封装,他会从template或component中建立一个view出来并插入到dom中相应的位置。
如今看起来彷佛有不少概念须要去理解吸取。可是实际上这些概念的调条理都十分清晰,而且这些概念组成了一个十分清晰的视图操做DOM的模型。
经过@ViewChild和模板引用标识符你能够获取到Angular DOM抽象的引用。围绕DOM元素最简单的包裹是ElementRef。
对于模板(template)而言,你能够经过TemplateRef来建立一个内嵌视图(Embedded View);宿主视图则能够经过ComponentFactoryResolver建立的componentRef来获取。
经过ViewContainerRef咱们则能够操做这些视图
结束!哈!