根中的Angular2动态组件注入 [英] Angular2 Dynamic Component Injection in Root

查看:57
本文介绍了根中的Angular2动态组件注入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在寻找将已知/定义的组件注入到应用程序的根中并将@Input()选项投射到该组件上的最佳方法.

I'm looking for the best approach for injecting a known/defined component into the root of an application and projecting @Input() options onto that component.

这对于在应用程序主体中创建模态/工具提示之类的东西是必需的,这样overflow:hidden/etc不会扭曲位置或将其完全切断.

This is necessary for creating things like modals/tooltips in the body of the application so that overflow:hidden/etc will not distort the position or cut it off completely.

我发现我可以拿到ApplicationRef,然后黑乎乎地向上走,找到ViewContainerRef.

I've found that I can get the ApplicationRef's and then hackily traverse upwards and find the ViewContainerRef.

constructor(private applicationRef: ApplicationRef) {
}

getRootViewContainerRef(): ViewContainerRef {
  return this.applicationRef['_rootComponents'][0]['_hostElement'].vcRef;
}

有一次我可以在裁判上调用createComponent,例如:

once I have that I can then call createComponent on the ref like:

appendNextToLocation<T>(componentClass: Type<T>, location: ViewContainerRef): ComponentRef<T> {
  const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
  const parentInjector = location.parentInjector;
  return location.createComponent(componentFactory, location.length, parentInjector);
}

,但是现在我已经创建了该组件,但是我的Input属性都没有实现.为此,我必须手动遍历我的选项,并将这些选项设置为appendNextToLocation的实例结果,例如:

but now I've created the component but none of my Input properties are fulfilled. To achieve that I have to manually traverse over my options and set those on the result of appendNextToLocation's instance like:

const props = Object.getOwnPropertyNames(options);
for(const prop of props) {
  component.instance[prop] = options[prop];
}

现在我意识到您可以执行一些DI注入选项,但是这使得当尝试将其用作常规组件时,该选项不可重用.以下是供参考的外观:

now I do realize you could do some DI to inject the options but that makes it not re-usable when trying to use as a normal component then. Heres what that looks like for reference:

let componentFactory = this.componentFactoryResolver.resolveComponentFactory(ComponentClass);
let parentInjector = location.parentInjector;

let providers = ReflectiveInjector.resolve([
  { provide: ComponentOptionsClass, useValue: options }
]);

childInjector = ReflectiveInjector.fromResolvedProviders(providers, parentInjector);

return location.createComponent(componentFactory, location.length, childInjector);

话虽如此,以上所有方法实际上都是有效的,但有时感觉有些古怪.我还担心像上面这样设置输入属性的生命周期时机,因为它是在创建后发生的.

all that said, all of the above actually works but it feels tad hacky at times. I'm also concerned about lifecycle timing of setting the input properties like the above since it happens after its created.

  • https://github.com/angular/angular/issues/9293
  • https://github.com/angular/angular/issues/6446

推荐答案

在2.3.0中,引入了attachView,它使您能够将变更检测附加到ApplicationRef,但是,您仍然需要手动进行将元素附加到根容器.这是因为使用Angular2,其运行环境的可能性可能是Web worker,通用,本机脚本等,因此我们需要明确告诉它要将其添加到视图的位置/方式.

In 2.3.0, attachView was introduced which allows you to be able to attach change detection to the ApplicationRef, however, you still need to manually append the element to the root container. This is because with Angular2 the possibilities of environments its running could be web workers, universal, nativescript, etc so we need to explicitly tell it where/how we want to add this to the view.

下面是一个示例服务,可让您动态插入组件并自动投影组件的Input.

Below is a sample service that will allow you to insert a component dynamically and project the Input's of the component automatically.

import {
  ApplicationRef, ComponentFactoryResolver, ComponentRef, Injectable,
  Injector, ViewContainerRef, EmbeddedViewRef, Type
} from '@angular/core';

/**
 * Injection service is a helper to append components
 * dynamically to a known location in the DOM, most
 * noteably for dialogs/tooltips appending to body.
 * 
 * @export
 * @class InjectionService
 */
@Injectable()
export class InjectionService {
  private _container: ComponentRef<any>;

  constructor(
    private applicationRef: ApplicationRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private injector: Injector) {
  }

  /**
   * Gets the root view container to inject the component to.
   * 
   * @returns {ComponentRef<any>}
   * 
   * @memberOf InjectionService
   */
  getRootViewContainer(): ComponentRef<any> {
    if(this._container) return this._container;

    const rootComponents = this.applicationRef['_rootComponents'];
    if (rootComponents.length) return rootComponents[0];

    throw new Error('View Container not found! ngUpgrade needs to manually set this via setRootViewContainer.');
  }

  /**
   * Overrides the default root view container. This is useful for 
   * things like ngUpgrade that doesn't have a ApplicationRef root.
   * 
   * @param {any} container
   * 
   * @memberOf InjectionService
   */
  setRootViewContainer(container): void {
    this._container = container;
  }

  /**
   * Gets the html element for a component ref.
   * 
   * @param {ComponentRef<any>} componentRef
   * @returns {HTMLElement}
   * 
   * @memberOf InjectionService
   */
  getComponentRootNode(componentRef: ComponentRef<any>): HTMLElement {
    return (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
  }

  /**
   * Gets the root component container html element.
   * 
   * @returns {HTMLElement}
   * 
   * @memberOf InjectionService
   */
  getRootViewContainerNode(): HTMLElement {
    return this.getComponentRootNode(this.getRootViewContainer());
  }

  /**
   * Projects the inputs onto the component
   * 
   * @param {ComponentRef<any>} component
   * @param {*} options
   * @returns {ComponentRef<any>}
   * 
   * @memberOf InjectionService
   */
  projectComponentInputs(component: ComponentRef<any>, options: any): ComponentRef<any> {
    if(options) {
      const props = Object.getOwnPropertyNames(options);
      for(const prop of props) {
        component.instance[prop] = options[prop];
      }
    }

    return component;
  }

  /**
   * Appends a component to a adjacent location
   * 
   * @template T
   * @param {Type<T>} componentClass
   * @param {*} [options={}]
   * @param {Element} [location=this.getRootViewContainerNode()]
   * @returns {ComponentRef<any>}
   * 
   * @memberOf InjectionService
   */
  appendComponent<T>(
    componentClass: Type<T>, 
    options: any = {}, 
    location: Element = this.getRootViewContainerNode()): ComponentRef<any> {

    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
    let componentRef = componentFactory.create(this.injector);
    let appRef: any = this.applicationRef;
    let componentRootNode = this.getComponentRootNode(componentRef);

    // project the options passed to the component instance
    this.projectComponentInputs(componentRef, options);

    appRef.attachView(componentRef.hostView);

    componentRef.onDestroy(() => {
      appRef.detachView(componentRef.hostView);
    });

    location.appendChild(componentRootNode);

    return componentRef;
  }
}

这篇关于根中的Angular2动态组件注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆