[译] 探索 Angular 使用 ViewContainerRef 操作 DOM
原文链接: Exploring Angular DOM manipulation techniques using ViewContainerRef 每次我读到 Angular 如何操作 DOM 相关文章时,总会发现这些文章提到 如果你来自于 angular.js 世界,很容易明白如何使用 angular.js 操作 DOM。angular.js 会在 新版本 Angular 需要在不同平台上运行,如 Browser 平台,Mobile 平台或者 Web Worker 平台,所以,就需要在特定平台的 API 和框架接口之间进行一层抽象(abstraction)。Angular 中的这层抽象就包括这些引用类型: @ViewChild在探索 DOM 抽象类前,先了解下如何在组件/指令中获取这些抽象类。Angular 提供了一种叫做 DOM Query 的技术,主要来源于 通常这两个装饰器与模板引用变量(template reference variable)一起使用,模板引用变量仅仅是对模板(template)内 DOM 元素命名式引用(a named reference),类似于 @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([reference from template],{read: [reference type]}); 上例中你可以看到,我把 现在,让我们看看应该如何获取这些引用,一起去探索吧。 ElementRef这是最基本的抽象类,如果你查看它的类结构,就发现它只包含所挂载的元素对象,这对访问原生 DOM 元素很有用,比如: // outputs `I am span` console.log(this.tref.nativeElement.textContent); 然而,Angular 团队不鼓励这种写法,不但因为这种方式会暴露安全风险,而且还会让你的程序与渲染层(rendering layers)紧耦合,这样就很难在多平台运行你的程序。我认为这个问题并不是使用 使用 @Component({ selector: 'sample',... export class SampleComponent{ constructor(private hostElement: ElementRef) { //outputs <sample>...</sample> console.log(this.hostElement.nativeElement.outerHTML); } ... 所以组件通过 DI(Dependency Injection)可以访问到它的宿主元素,但 TemplateRef对于大部分开发者来说,模板概念很熟悉,就是跨程序视图内一堆 DOM 元素的组合。在 HTML5 引入 template 标签前,浏览器通过在 <script id="tpl" type="text/template"> <span>I am span in template</span> </script> 这种方式不仅有语义缺陷,还需要手动创建 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 采用 @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 框架从 DOM 中移除 <sample> <!--template bindings={}--> </sample>
ViewRef该抽象表示一个 Angular 视图(View),在 Angular 世界里,视图(View)是一堆元素的组合,一起被创建和销毁,是构建程序 UI 的基石。Angular 鼓励开发者把 UI 作为一堆视图(View)的组合,而不仅仅是 html 标签组成的树。 Angular 支持两种类型视图:
创建嵌入视图模板仅仅是视图的蓝图,可以通过之前提到的 ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); } 创建宿主视图宿主视图是在组件动态实例化时创建的,一个动态组件(dynamic component)可以通过 constructor(private injector: Injector,private r: ComponentFactoryResolver) { let factory = this.r.resolveComponentFactory(ColorComponent); let componentRef = factory.create(injector); let view = componentRef.hostView; } 在 Angular 中,每一个组件绑定着一个注入器(Injector)实例,所以创建 现在,我们已经看到嵌入视图和宿主视图是如何被创建的,一旦视图被创建,它就可以使用 ViewContainerRef视图容器就是挂载一个或多个视图的容器。 首先需要说的是,任何 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 `template bindings={}` console.log(this.vc.element.nativeElement.textContent); } } 如同其他抽象类一样, 操作视图
class ViewContainerRef { ... clear() : void insert(viewRef: ViewRef,index?: number) : ViewRef get(index: number) : ViewRef indexOf(viewRef: ViewRef) : number detach(index?: number) : ViewRef move(viewRef: ViewRef,currentIndex: number) : ViewRef } 从上文我们已经知道如何通过模板和组件创建两种类型视图,即嵌入视图和组件视图。一旦有了视图,就可以通过 @Component({ selector: 'sample',template: ` <span>I am first span</span> <ng-container #vc></ng-container> <span>I am last span</span> <ng-template #tpl> <span>I am span in template</span> </ng-template> ` }) export class SampleComponent implements AfterViewInit { @ViewChild("vc",{read: ViewContainerRef}) vc: ViewContainerRef; @ViewChild("tpl") tpl: TemplateRef<any>; ngAfterViewInit() { let view = this.tpl.createEmbeddedView(null); this.vc.insert(view); } } 通过上面的实现,最后的 <sample> <span>I am first span</span> <!--template bindings={}--> <span>I am span in template</span> <span>I am last span</span> <!--template bindings={}--> </sample> 可以通过 创建视图
class ViewContainerRef { element: ElementRef length: number createComponent(componentFactory...): ComponentRef<C> createEmbeddedView(templateRef...): EmbeddedViewRef<C> ... } 上面两个方法是个很好的封装,可以传入模板引用对象或组件工厂对象来创建视图,并将该视图插入视图容器中特定位置。 ngTemplateOutlet 和 ngComponentOutlet尽管知道 Angular 操作 DOM 的内部机制是好事,但是要是有某种快捷方式就更好了啊。没错,Angular 提供了两种快捷指令: ngTemplateOutlet该指令会把 DOM 元素标记为 @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这个指令与 <ng-container *ngComponentOutlet="ColorComponent"></ng-container> 总结看似有很多新知识需要消化啊,但实际上 Angular 通过视图操作 DOM 的思路模型是很清晰和连贯的。你可以使用 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |