如何在Angular2中以编程方式动态创建多个独立实例组件?

8
我有一个名为fancy-textbox的按钮组件。我想让用户可以动态添加新的fancy-textbox,但它们具有不同的标签,这些标签基于仅适用于fancy-textbox本身的范围变量(或者从未在所有fancy-textbox之间共享的父级范围变量)。我该怎么做?目前,我直接在模板中使用它来显示和隐藏,但我想能够通过编程方式“动态添加更多此类实例”:
<div *ngIf="showTextBox">
    <fancy-textbox labelForBox="TextBox 1"></fancy-textbox>  
</div>

如果花式文本框只在DOM中的一个特定区域创建,那就太好了。但是,我想做的是能够在DOM的不同部分动态创建组件。

    <div class="container">
<div class="navsection"><input id="classname" type="text"><button (click)="createFancyButton()">Create Fancy Button</button></div>
    <div class="topsection">
    </div>
    <div class="midsection">
    </div>
    <div class="botsection">
    </div>
<div class="footsection"></div></div>

给定上述模板...假设用户将类名(例如botsection)输入文本框并点击“createfancybutton”按钮,我希望“<fancy-button></fancy-button>”能够放置在页面的适当部分,我希望能够动态地在页面模板的不同部分“创建”独立的“fancy-button”实例。我可以使用3个ng-if语句和ng-for,但这似乎不切实际。寻找更好的替代方案...
更新:因此步骤如下:
1)用户输入“midsection”到文本框。 2)用户单击“Create Fancy Button”按钮 3)- <fancybutton></fancybutton>组件将添加到具有类名“midsection”的div下面-
用户可以重复点击相同的“Create Fancy Button”按钮来在其下创建更多内容。如果用户将输入框更改为“topsection”,那么当用户单击“Create Fancy Button”时,fancybutton组件将添加到具有“topsection”的div下面。
如果用户输入“newsection”,则将在具有类名“container”的div下创建一个新的具有类名“newsection”的div,并将fancybutton组件添加到具有类名“newsection”的div中。

也许你可以像定义ID一样定义#target1#target2等,通过遵循这个简单的答案如何在容器中放置动态组件,在任何需要动态插入组件的地方进行操作。 - Ankit Singh
5个回答

1

从架构的角度出发,利用JS/TS面向对象的类继承,我个人认为应该在每个部分(例如<top-section-cc-fancytextboxes...></><bot-section-cc-fancytextboxes...></>)中添加一个“复合组件”('cc':遵循OO 'composite'模式)。这些作为各种<fancy-textbox>类型的“占位符”,每个cc中可以有零个或多个实例。 现在,每个复合组件都从一个基类/接口组件(例如<base-section-cc-fancytextboxes>)派生/实现,其中包含管理添加多个<fancy-textbox>类型的基本方法。然而,派生类方法将“知道”在哪里添加正确的<fancy-textbox>(可能是像上面提到的*ngFor数组)。至于实例化特定类型的<fancy-textbox>,也许利用类工厂模式也会很有用 - 可能是由复合组件驱动并返回实例的AJ2服务提供程序。

不管怎样,借助AJ2内置的TS OO,细节暂且搁置不谈,这是我个人解决这个特定问题的向量。这只是一个想法。


1
这看起来是最好的解决方案@MoMo。你能否试着创建一个plunker呢?这将对每个人都有巨大的帮助。 - rahulthakur319

1
在您的组件中拥有一个标签数组。
使用 *ngFor 迭代该数组,并为每个标签生成一个漂亮的文本框。
要添加新的漂亮文本框,请将新标签添加到数组中。
<fancy-textbox *ngFor="let label of labels" [labelForBox]="label"></fancy-textbox>  

在组件中:
labels: string[] = [];

// to add a fancy textbox:
this.labels.push('new label');

问题是,我希望在HTML模板的几个不同部分中显示fancy-textbox。例如,可能有不同的特定嵌套div,我想插入fancy-textbox。 - Rolando
很遗憾,由于你描述的内容过于模糊,我无法提供更多的帮助。 - JB Nizet
更新原帖。应该更清楚我在寻找什么。 - Rolando
嗯,正如我之前所说的,你也可以为你的部分使用一个循环。 - JB Nizet
你能否展示一个 Plunkr 的例子来说明我所描述的情况,我认为这会有所帮助。 - Rolando
显示剩余2条评论

1
我认为你不需要其他的作用域或组件。类似这样的代码应该可以工作。
组件(TypeScript):
sections: {[key] : string[]} = {};

createFancyButton: (section: string) => {
    if (!this.sections[section]) {
        this.sections[section] = [];
    }
    this.sections[section].push('');
}

getSectionKeys: () => {
    return Object.keys(this.sections);
}

"sections"属性是一个可索引的对象,意味着它的行为类似于哈希表。每个"sections"对象的属性("key")都是一个字符串数组,表示花哨按钮值(ngModel)。
模板(HTML)
<div class="container">
    <div class="navsection">
        <input #section type="text" placeholder="section" />
        <button (click)="createFancyButton(#section.value)">Create Fancy Button</button>
    </div>
    <div *ngFor="let key of getSectionKeys()" [class]="key">
        <fancy-textbox *ngFor="let textBox of sections[key]" [(ngModel)]="textBox"></fancy-textbox>
    </div>
</div>

有一个模板引用变量 (#section),它可以方便地访问模板中的 DOM 元素。然后,我们使用 *ngFor 遍历 sections 哈希表中的键来创建每个 section div。最后,我们对每个 section 的字符串数组进行 *ngFor。

有关模板引用变量的更多信息,请参见此处。 https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ref-vars

注意:由于我没有测试过,可能会有拼写错误。


1

您需要动态加载组件

这是我的解决方案

Parent.component.ts

import { Component, OnInit, ViewChild, ViewContainerRef, Input, ComponentFactoryResolver, ReflectiveInjector } from
    "@angular/core";

import { FancyButtonCompoent } from "../FancyButton.component";

@Component({
    moduleId: module.id,
    selector: "app-parent",
    templateUrl: "parent.component.html",
    styleUrls: ["parent.component.css"],
    entryComponents: [FancyButtonCompoent]

})
export class ParentCompoent {

    @ViewChild("midsection", { read: ViewContainerRef })
    midsectionContainer: ViewContainerRef;

    constructor(private resolver: ComponentFactoryResolver) {
    }

    createFancyButton() {
        //Name Is Fancybutton data binding property
        var yourdatavalues= {Name:'myname'}
        this.createDynamicbutton({
            input: yourdatavalues,
        });
    }
    //you can add your own model to get what you want like remove,move
     // var dynamiccompoent={Data:yourmodel,compoentcontainer:any}
     //fancybuttonarray:dynamiccompoent[];

 fancybuttonarray:any[];

    createDynamicbutton(elementData) {
        if (!elementData) {
            return;
        }

        // Inputs need to be in the following format to be resolved properly
        let inputProviders = Object.keys(elementData.inputs)
            .map((inputName) => { return { provide: inputName, useValue: elementData.inputs[inputName] }; });
        let resolvedInputs = ReflectiveInjector.resolve(inputProviders);

        // We create an injector out of the data we want to pass down and this components injector
        let injector = ReflectiveInjector
            .fromResolvedProviders(resolvedInputs, this.midsectionContainer.parentInjector);

        // We create a factory out of the component we want to create
        let factory = this.resolver.resolveComponentFactory(DefaultButtonComponent);

        // We create the component using the factory and the injector
        let component = factory.create(injector);

        this.midsectionContainer.insert(component.hostView)

         //your getting every instance of fancy button instance into array
         this.fancybuttonarray.push.(component )

         //if you want to clear elment if you wish
         //this.fancybuttonarray[0].destroy()
          //this.fancybuttonarray[1].destroy()


    }
}

parent.component.html

<div   class="row col-lg-12" >
    <div #midsection >

    </div>
</div>

Fancybutton.compoent.ts

    import { Component, OnInit, Injector } from '@angular/core';


@Component({
    moduleId: module.id,
    selector: 'Fancy-button',
    templateUrl: 'Fancybutton.component.html'
})
export class FancybuttonComponent {

    inputelement:yourDatamodel
    constructor(private injector: Injector) {
        this.inputElement = this.injector.get('inputElement');
    }

}

Fancybutton.compoent.html

    <div>
    <button title="inputelement.Name"(click)r ="alert(inputelement.Name)"></button>
    </div>    

更新

这是一个关于使用angular2动态加载子组件的好文章。

同时也提供了一个漂亮的Plunker示例


如果您想清除生成的组件,请将插入的组件添加到数组中并将其清除。 - Jagadeesh Govindaraj
ReflectiveInjector已被弃用。您可以使用Injector,例如:let injector = Injector.create({ providers: [resolvedInputs], parent: this.midsectionContainer.parentInjector, }); - snorberhuis

0

这是如何通过编程构建和添加组件的方法:

import {ComponentRef, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';

@Component({
    selector: 'fancy-box',
    template: `<div>{{fancyContent}}</div> `,
})
export class FancyBox {

    fancyContent;

    doStuff() {
        console.log('done');
    }
}

@Component({
    selector: 'fancy-parent',
    template: `<div (click)="addNewFancyBox()">Add Box</div> `,
})
export class FancyParent {
    private counter = 0;

    constructor(
        private viewContainerRef: ViewContainerRef,
        private resolver: ComponentFactoryResolver) {
    }

    addNewFancyBox() {

        const factory = this.resolver.resolveComponentFactory(FancyBox);
        fancybox = this.viewContainerRef.createComponent(factory);

        const fancyboxElement = fancybox.instance as FancyBox;
        fancyboxElement.content = 'box number: ' + counter;

        fancyboxElement.doStuff();
        counter++;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接