当前位置: 移动技术网 > IT编程>网页制作>CSS > 教你如何在@ViewChild查询之前获取ViewContainerRef

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

2019年01月02日  | 移动技术网IT编程  | 我要评论

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

<img alt="图片描述" data-="" data-cke-saved-src="/img/bvblhjiw=800&h=530" src="/img/bvblhjiw=800&h=530" 图片描述"="">

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

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

组件a

我们来创建组件a

@component({
  selector: 'a-comp',
  template: `
      i am a component
  `,
})
export class acomponent {
}

app根模块

然后将(组件a)它在declarations和entrycomponents中进行注册:

@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);
    ...
  }

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网