原文: https://blog.angularindepth.c...
做者: Max Koretskyi
译者: 而井
【翻译】教你如何在@ViewChild查询以前获取ViewContainerRefhtml
在我最新的一篇关于动态组件实例化的文章《在Angular中关于动态组件你所须要知道的》中,我已经展现了如何将一个子组件动态地添加到父组件中的方法。全部动态的组件经过使用ViewContainerRef
的引用被插入到指定的位置。这个引用经过指定一些模版引用变量来得到,而后在组件中使用相似ViewChild
的查询来获取它(模版引用变量)。typescript
在此快速的复习一下。假设咱们有一个父组件App
,而且咱们须要将子组件A
插入到(父组件)模版的指定位置。在此咱们会这么干。bootstrap
咱们来建立组件Asegmentfault
@Component({ selector: 'a-comp', template: ` <span>I am A component</span> `, }) export class AComponent { }
而后将(组件A)它在declarations
和entryComponents
中进行注册:app
@NgModule({ imports: [BrowserModule], declarations: [AppComponent, AComponent], entryComponents: [AComponent], bootstrap: [AppComponent] }) export class AppModule { }
而后在父组件App
中,咱们添加建立组件A
实例和插入它(到指定位置)的代码。ide
@Component({ moduleId: module.id, selector: 'my-app', template: ` <h1>I am parent App component</h1> <div class="insert-a-component-inside"> <ng-container #vc></ng-container> </div> `, }) export class AppComponent { @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; constructor(private r: ComponentFactoryResolver) {} ngAfterViewInit() { const factory = this.r.resolveComponentFactory(AComponent); this.vc.createComponent(factory); } }
在plunker中有能够运行例子(译者注:这个连接中的代码已经没法运行,因此译者把代码整理了一下,放到了stackblitz上了,能够点击查看预览)。若是有什么你不能理解的,我建议你阅读我一开始提到过的文章。this
使用上述的方法是正确的,也能够运行,可是有一个限制:咱们不得不等到ViewChild
查询执行后,那时正处于变动检测期间。咱们只能在ngAfterViewInit
生命周期以后来访问(ViewContainerRef
的)引用。若是咱们不想等到Angular运行完变动检测以后,而是想在变动检测以前拥有一个完整的组件视图呢?咱们惟一能够作到这一步的就是:用directive
指令来代替模版引用和ViewChild
查询。lua
directive
指令代替ViewChild
查询每个指令均可以在它的构造器中注入ViewContainerRef
引用。这个将是与一个视图容器相关的引用,并且是指令的宿主元素的一个锚地。让咱们声明这样一个指令:spa
import { Directive, Inject, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[app-component-container]', }) export class AppComponentContainer { constructor(vc: ViewContainerRef) { vc.constructor.name === "ViewContainerRef_"; // true } }
我已经在构造器中添加了检查(代码)来保证视图容器在指令实例化的时候是可用的。如今咱们须要在组件App
的模版中使用它(指令)来代替#vc
模版引用:翻译
<div class="insert-a-component-inside"> <ng-container app-component-container></ng-container> </div>
若是你运行它,你会看到它是能够运行的。好的,咱们如今知道在变动检查以前,指令是如何访问视图容器的了。如今咱们须要作的就是把组件传递给它(指令)。咱们要怎么作呢?一个指令能够注入一个父组件,而且直接调用(父)组件的方法。然而,这里有一个限制,就是组件不得不要知道父组件的名称。或者使用这里描述的方法。
一个更好的选择就是:用一个在组件及其子指令之间共享服务,并经过它来沟通!咱们能够直接在组件中实现这个服务并将其本地化。为了简化(这一操做),我也将使用定制的字符串token:
const AppComponentService= { createListeners: [], destroyListeners: [], onContainerCreated(fn) { this.createListeners.push(fn); }, onContainerDestroyed(fn) { this.destroyListeners.push(fn); }, registerContainer(container) { this.createListeners.forEach((fn) => { fn(container); }) }, destroyContainer(container) { this.destroyListeners.forEach((fn) => { fn(container); }) } }; @Component({ providers: [ { provide: 'app-component-service', useValue: AppComponentService } ], ... }) export class AppComponent { }
这个服务简单地实现了原始的发布/订阅模式,而且当容器注册后会通知订阅者们。
如今咱们能够将这个服务注入AppComponentContainer
指令之中,而且注册(指令相关的)视图容器了:
export class AppComponentContainer { constructor(vc: ViewContainerRef, @Inject('app-component-service') shared) { shared.registerContainer(vc); } }
剩下惟一要作的事情就是当容器注册时,在组件App
中进行监听,而且动态地建立一个组件了:
export class AppComponent { vc: ViewContainerRef; constructor(private r: ComponentFactoryResolver, @Inject('app-component-service') shared) { shared.onContainerCreated((container) => { this.vc = container; const factory = this.r.resolveComponentFactory(AComponent); this.vc.createComponent(factory); }); shared.onContainerDestroyed(() => { this.vc = undefined; }) } }
在plunker中有能够运行例子(译者注:这个连接中的代码已经没法运行,因此译者把代码整理了一下,放到了stackblitz上了,能够点击查看预览)。你能够看到,已经没有ViewChild
查询(的代码)了。若是你新增一个ngOnInit
生命周期,你将看到组件A
在它(ngOnInit
生命周期)触发前就已经渲染好了。
也许你以为这个办法十分骇人听闻,其实不是的,咱们只需看看Angular中router-outlet
指令的源代码就行了。这个指令在构造器中注入了viewContainerRef
,而且使用了一个叫parentContexts
的共享服务在路由器配置中注册自身(即:指令)和视图容器:
export class RouterOutlet implements OnDestroy, OnInit { ... private name: string; constructor(parentContexts, private location: ViewContainerRef) { this.name = name || PRIMARY_OUTLET; parentContexts.onChildOutletCreated(this.name, this); ... }