资讯专栏INFORMATION COLUMN

教你如何在@ViewChild查询之前获取ViewContainerRef

suxier / 1963人阅读

摘要:使用指令代替查询每一个指令都可以在它的构造器中注入引用。让我们声明这样一个指令我已经在构造器中添加了检查代码来保证视图容器在指令实例化的时候是可用的。

原文:https://blog.angularindepth.c...
作者:Max Koretskyi
译者:而井

【翻译】教你如何在@ViewChild查询之前获取ViewContainerRef

在我最新的一篇关于动态组件实例化的文章《在Angular中关于动态组件你所需要知道的》中,我已经展示了如何将一个子组件动态地添加到父组件中的方法。所有动态的组件通过使用ViewContainerRef的引用被插入到指定的位置。这个引用通过指定一些模版引用变量来获得,然后在组件中使用类似ViewChild的查询来获取它(模版引用变量)。

在此快速的复习一下。假设我们有一个父组件App,并且我们需要将子组件A插入到(父组件)模版的指定位置。在此我们会这么干。

组件A

我们来创建组件A

@Component({
  selector: "a-comp",
  template: `
      I am A component
  `,
})
export class AComponent {
}
App根模块

然后将(组件A)它在declarationsentryComponents中进行注册:

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent, AComponent],
  entryComponents: [AComponent],
  bootstrap: [AppComponent]
})
export class AppModule {
}
组件App

然后在父组件App中,我们添加创建组件A实例和插入它(到指定位置)的代码。

@Component({
  moduleId: module.id,
  selector: "my-app",
  template: `
      

I am parent App component

`, }) 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上了,可以点击查看预览)。如果有什么你不能理解的,我建议你阅读我一开始提到过的文章。

使用上述的方法是正确的,也可以运行,但是有一个限制:我们不得不等到ViewChild查询执行后,那时正处于变更检测期间。我们只能在ngAfterViewInit生命周期之后来访问(ViewContainerRef的)引用。如果我们不想等到Angular运行完变更检测之后,而是想在变更检测之前拥有一个完整的组件视图呢?我们唯一可以做到这一步的就是:用directive指令来代替模版引用和ViewChild查询。

使用directive指令代替ViewChild查询

每一个指令都可以在它的构造器中注入ViewContainerRef引用。这个将是与一个视图容器相关的引用,而且是指令的宿主元素的一个锚地。让我们声明这样一个指令:

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模版引用:

如果你运行它,你会看到它是可以运行的。好的,我们现在知道在变更检查之前,指令是如何访问视图容器的了。现在我们需要做的就是把组件传递给它(指令)。我们要怎么做呢?一个指令可以注入一个父组件,并且直接调用(父)组件的方法。然而,这里有一个限制,就是组件不得不要知道父组件的名称。或者使用这里描述的方法。

一个更好的选择就是:用一个在组件及其子指令之间共享服务,并通过它来沟通!我们可以直接在组件中实现这个服务并将其本地化。为了简化(这一操作),我也将使用定制的字符串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生命周期)触发前就已经渲染好了。

RouterOutlet

也许你觉得这个办法十分骇人听闻,其实不是的,我们只需看看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);
    ...
  }

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100421.html

相关文章

  • [译] 探索 Angular 使用 ViewContainerRef 操作 DOM

    摘要:在探索抽象类前,先了解下如何在组件指令中获取这些抽象类。下面示例描述在组建模板中如何创建如同其他抽象类一样,通过属性绑定元素,比如上例中,绑定的是会被渲染为注释的元素,所以输出也将是。你可以使用查询模板引用变量来获得抽象类。 原文链接:Exploring Angular DOM manipulation techniques using ViewContainerRef如果想深入学习 ...

    wind5o 评论0 收藏0
  • 源码分析 @angular/cdk 之 Portal

    摘要:这些依赖对象也进一步暴露了其设计思想。关键功能包括在上下文内挂载在上下文外挂载在上下文外共享数据。在构造必须依赖,所以可以直接创建嵌入视图,然后手动强制执行变更检测。提供了两个指令和。 @angular/material 是 Angular 官方根据 Material Design 设计语言提供的 UI 库,开发人员在开发 UI 库时发现很多 UI 组件有着共同的逻辑,所以他们把这些共...

    BearyChat 评论0 收藏0
  • 理解Angular2中的ViewContainerRef

    摘要:注意本文不是关于如何用编程的方式来创建组件的文章。在这个例子中,容器元素就是元素,模版将作为这个元素的兄弟节点被插入。用来演示以组件自身作为视图容器,将组件中的模版插入视图容器的效果。 原文链接:https://netbasal.com/angular-...作者:Netanel Basal译者:而井 showImg(https://segmentfault.com/img/bVbl...

    Codeing_ls 评论0 收藏0
  • [译] 关于 Angular 动态组件你需要知道的

    摘要:第一种方式是使用模块加载器,如果你使用加载器的话,路由在加载子路由模块时也是用的作为模块加载器。还需注意的是,想要使用还需像这样去注册它你当然可以在里使用任何标识,不过路由模块使用标识,所以最好也使用相同。 原文链接:Here is what you need to know about dynamic components in Angular showImg(https://se...

    lcodecorex 评论0 收藏0
  • Angular中操作DOM:意料之外的结果及优化技术

    摘要:翻译在中操作意料之外的结果及优化技术原文链接作者译者而井我最近在的一个研讨会上讨论了中的高级操作的话题。首先,我会介绍在中操作的工具和方法,然后再介绍一些我在研讨会上没有说过的更高级的优化技术。 【翻译】在Angular中操作DOM:意料之外的结果及优化技术 原文链接:https://blog.angularindepth.c... 作者:Max Koretskyi 译者:而井 ...

    未东兴 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<