如何使用服务的输入/输出动态创建组件实例并将其分别注入到 DOM? [英] How to dynamically create component instance with input/output from a service and inject it to DOM separately?

查看:22
本文介绍了如何使用服务的输入/输出动态创建组件实例并将其分别注入到 DOM?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Angular 2 中创建动态组件时,我 发现此过程需要 ViewContainerRef 以将新创建​​的组件添加到 DOM.

In creating dynamic components in Angular 2, I found out that this process requires ViewContainerRef in order to add newly created component to DOM.

并且通过将 @Input@Output 传递给那些动态创建的组件,我在上面的第二个链接和 这里.

And in passing @Input and @Outputto those dynamically created components, I found the answer in the second link above and here.

但是,如果我要创建一个名为 shape.service 的服务,该服务包含返回不同形状组件的函数,其中包含一些 @Input,例如 bgColor,我不知道这个服务将如何在不指定 DOM 位置的情况下创建组件,以及容器组件如何从服务接收这个返回的组件(可能它的类型是 ComponentRef)并将其注入到DOM 容器组件指定.

However, if I were to create a service named shape.service that contains functions returning different shape components with some @Input like bgColor, I don't know how this service will create a component without specifying DOM location, and how the container-component receives this returned component (probably its type will be ComponentRef) from the service and injects it to the DOM container-component specifies.

例如,一个服务包含一个方法:

For example, a service contains a method:

getCircle(bgColor:string): ComponentRef<Circle> {
    let circleFactory = componentFactoryResolver.resolveComponentFactory(CircleComponent);
    let circleCompRef = this.viewContainerRef.createComponent(circleFactory);
    circleCompRef.instance.bgColor = bgColor;

    return circleCompRef;
}

第一个问题出现在这里,我如何让 this.viewContainerRef 在此期间指向无处?我导入 ViewContainerRef 的原因是为了动态创建组件.

First question rises here, how do I make this.viewContainerRef point to no where for the meantime? The reason why I'm importing ViewContainerRef is to create component dynamically.

第二个问题是容器组件从服务接收到特定输入的componentRef后,它会如何注入到它的DOM中?

Second question is after container-component receives input-specificcomponentRef from the service, how will it inject to its DOM?

更新:我认为我上面的问题不够具体.我处于以下情况:

UPDATE: I think my question above wasn't specific enough. I'm in a situation where:

  1. 父组件调用服务并获取 componentRef/s,
  2. 创建一个包含 componentRef/s 和一些其他数据的对象,并将这些创建的对象存储到数组中
  3. 将它作为 @Input 传递给它的孩子,
  4. 让每个孩子将 componentRef 注入其 DOM 并以其他方式使用对象中的其余数据.

这意味着服务调用组件不知道那些 componentRef 将被注入到哪里.简而言之,我需要可以随时随地注入的独立组件对象.

That means the service calling component has no idea where those componentRef will get injected. In short, I need independent component objects that can be injected to anywhere, anytime I want.

我已经多次阅读 rumTimeCompiler 解决方案,但我真的不明白它是如何工作的.与使用 viewContainerRef 创建组件相比,似乎有太多的工作.如果我找不到其他解决方案,我会深入研究...

I'm reading the rumTimeCompiler solution several times already but I don't really get how that really works. It seems like there's too much work compared to component creation using viewContainerRef. I'll dig into that more if I find no other solution...

推荐答案

如果像我这样的人现在仍在寻找简单明了的解决方案 - 就在这里.我从@angular/cdk https://github.com/angular/components 得到它/tree/master/src/cdk并做了一个简单的服务.

In case someone like me still looking for a simple and clear solution nowdays - here it is. I got it from @angular/cdk https://github.com/angular/components/tree/master/src/cdk and made a simple service.

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

export type ComponentType<T> = new (...args: any[]) => T;

@Injectable({
    providedIn: 'root'
})
export class MyService {

    constructor(
        private _appRef: ApplicationRef,
        private _resolver: ComponentFactoryResolver,
        private _injector: Injector
    ) { }

    private _components: ComponentRef<any>[] = [];

    add<T>(
        component: ComponentType<T> | ComponentRef<T>,
        element?: Element | string
    ): ComponentRef<T> {
        const componentRef = component instanceof ComponentRef
            ? component
            : this._resolver.resolveComponentFactory(component).create(this._injector);
        this._appRef.attachView(componentRef.hostView);
        if (typeof element === 'string') {
            element = document.querySelector(element);
        }
        if (!element) {
            element = document.body;
        }
        element.appendChild(
            (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement
        );
        this._components.push(componentRef);
        return componentRef;
    }

    remove(dialog: number | ComponentRef<any>): boolean {
        let componentRef;
        if (typeof dialog === 'number' && this._components.length > dialog)  {
            componentRef = this._components.splice(dialog, 1)[0];
        }
        else {
            for (const cr of this._components) {
                if (cr === dialog) {
                    componentRef = cr;
                }
            }
        }
        if (componentRef) {
            this._remove(componentRef);
            return true;
        }
        return false;
    }

    private _remove(componentRef: ComponentRef<any>) {
        this._appRef.detachView(componentRef.hostView);
        componentRef.destroy();
    }

    clear() {
        while (this._components.length > 0) {
            this._remove(this._components.pop());
        }
    }

    getIndex(componentRef: ComponentRef<any>): number {
        return this._components.indexOf(componentRef);
    }

}

您可以将 ComponentClass 或 ComponentRef 传递给 add 和 Element 或任何指向任何 DOM 元素的 querySelector 字符串,您要在其中附加组件作为第二个参数(或者什么都不做,然后它假定您要附加到身体).

You can pass ComponentClass or ComponentRef to add and Element or any querySelector string pointing to any DOM element where you want to attach your component as second argument (or nothing, then it assumes you want to attach to body).

const cr = this._service.add(MyComponent); // will create MyComponent and attach it to document.body or
const cr = this._service.add(MyComponent, this.someElement); // to attach to Element stored in this.someElement or
const cr = this._service.add(MyComponent, 'body div.my-class > div.my-other-div'); // to search for that element and attach to it
const crIndex = this._service.getIndex(cr);
cr.instance.myInputProperty = 42;
cr.instance.myOutputEmitter.subscribe(
    () => {
        // do something then for example remove this component
        this._service.remove(cr);
    }
);
this._service.remove(crIndex); // remove by index or
this._service.remove(cr); // remove by reference or
this._service.clear(); // remove all dynamically created components

P.S.不要忘记把你的动态组件添加到@NgModule

这篇关于如何使用服务的输入/输出动态创建组件实例并将其分别注入到 DOM?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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