文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

在运行时动态创建一个组件是非常少见的, 到目前为止你应该没有在angular2官方的文档中看到过这样的例子.在这篇文章中,我们通过一个例子来填补这个空白..

如果你想在angular2运行时动态创建一个组件,对话框组件会是一个很好的例子.如果你的应用像他们一样非常多 (比如 Lucidchart )(译者注:一个在线绘制流程图的工具), 然后你就会觉得像在写一个巨型的 ngSwitch组件, 所以动态构建会是一个很好的选择.

我们将构建一个简单的动态对话框. (从这里checkout下来  完整例子.) 这里是我们的初始化时的应用:

@Component({
    selector: 'my-app',
    template: `
        <div class="open-button" (click)='openDialogBox()'>Open dialog box</div>
    `,
    styles: [`
        :host {
            display: flex;
            justify-content: center;
        }
        .open-button {
            padding: 5px;
            border: 1px solid black;
            cursor: pointer;
        }
    `],
    directives: []
})
export class AppComponent {
    openDialogBox() {
        // TODO: Open up a dialog box
    }
}

这个应用脚手架可以在这里 运行和fork。

第 1 段(可获 1.51 积分)

我们的目标是,当用户点击【打开对话框】按钮的时候弹出一个对话框。 我们实现这一目标的主要工具是 Angular 2的 ViewContainerRef 类, 它提供了一个方便的createComponent 方法. 很难用通俗的话来描述一个 ViewContainerRef类, 但一下几点是我们需要知道的:

  • ViewContainerRef 可以简单的认为是一个新组件插入DOM中的一个位置.
  • 在内部,该插入的位置是由一个DOM元素指定的,称为“锚元素”。
  • 当使用 createComponent 方法创建一个新组件的时候, 这个创建的组件将作为锚元素元素的兄弟节点插入到DOM结构中.

给出一个大纲, 在我们的例子中如何去使用 ViewContainerRef类:

  1. 在AppComponent组件的模板中我们会选择一个点(比如说一个元素)来作为我们的对话框的插入点 ,然后我们会插入一个新的ViewContainerRef 在那里。
  2. 我们将使用 ViewContainerRef的 createComponent 方法去创建和插入我们的对话到到选定的位置(1中的点).

该计划很容易总结成两个步骤,但实际上需要不少的工作来实现。那我们就开始吧。

 

第 2 段(可获 2.43 积分)

设置插入位置

查看AppComponent的模板, 没有什么元素来供我们选择插入位子,仅仅只有一个div元素.让我们添加另外一个div元素并使用它作为锚点: 

...
    template: `
        <div></div>
        <div class="open-button" (click)='openDialogBox()'>Open dialog box</div>
    `,
...

我们可以绑定一个ViewContainerRef到这个div上通过增加一个注入了ViewContainerRef类的指令来实现 . 这里是我们的指令:

@Directive({ selector: '[dialogAnchor]' })
export class DialogAnchorDirective {
    constructor(
        private viewContainer: ViewContainerRef,
    ) {}
}

这里,我们将指令添加到我们创建的那个div元素上:

...
    template: `
        <div [dialogAnchor]></div>
        <div class="open-button" (click)='openDialogBox()'>Open dialog box</div>
    `,
...
第 3 段(可获 0.8 积分)

当DialogAnchorDirective指令创建完成,它将会从angular的注入器中获取ViewContainerRef.实例,然后设置到我们的锚点元素中,就是我们上面添加的那个div元素。 我们的对话框BOM将会作为这个锚点元素的兄弟节点插入.

使用createComponent

实际上我们需要使用DialogAnchorDirective’的viewContainer属性来提供一些方法让我构建对话框. 我们将添加一下方法到DialogAnchorDirective中:

...
    createDialog(dialogComponent: { new(): DialogComponent }): ComponentRef {
        this.viewContainer.clear();

        let dialogComponentFactory = 
          this.componentFactoryResolver.resolveComponentFactory(dialogComponent);
        let dialogComponentRef = this.viewContainer.createComponent(dialogComponentFactory);

        dialogComponentRef.instance.close.subscribe(() => {
            dialogComponentRef.destroy();
        });

        return dialogComponentRef;
    }
...
第 4 段(可获 0.73 积分)

这部分的代码看起来似乎有点多, 所以我们将会拆分它们, 在做这些之前, 我们首先要在这个方法中添加一个小依赖. 这个依赖就是componentFactoryResolver,一个帮助属性, 我们把这个依赖添加到我们的DialogAnchorDirective类中:

...
    constructor(
        private viewContainer: ViewContainerRef,
        private componentFactoryResolver: ComponentFactoryResolver
    ) {}
...

这样一来,让我们从方法签名开始:

createDialog(dialogComponent: { new(): DialogComponent }):ComponentRef<DialogComponent> { ... }
第 5 段(可获 0.69 积分)

我们通过这种方式创建了一个动态对话框组件- DialogComponent,  对于这个新的组件我们来回顾一下ComponentRef. (注意, DialogComponent就是我们一直提到的自定义的对话框组件. 我们不在这里提供代码清单, 但是你可以在这里获取到 完整例子. 只需要知道它不需要特殊设置而是动态构建的.)

这行代码将能够移除任何已经打开的对话框:

this.viewContainer.clear();

下面几行是实际创建组件的地方:

let dialogComponentFactory = 
    this.componentFactoryResolver.resolveComponentFactory(dialogComponent);
let dialogComponentRef = this.viewContainer.createComponent(dialogComponentFactory);
第 6 段(可获 1.03 积分)

我们使用新增加的componentFactoryResolver来解析DialogComponent类型到 ComponentFactory . 然后这个工厂通过ViewContainerRef的 createComponent 方法来构建,插入和返回新创建的对话框的引用.

接下来,我们设置了一个监听器,当用户试图关闭对话框时,该事件将被触发.:

dialogComponentRef.instance.close.subscribe(() => {
    dialogComponentRef.destroy();
});

最后, 我们返回ComponentRef给新插入的对话框组件:

return componentCreated;
第 7 段(可获 0.81 积分)

完成这个应用组件的最后一部分. 首先, 我们必须声明 AppComponent能够动态构建DialogComponent. 我们使用@Component元注解中的 entryComponents 选项声明:

@Component({
    selector: 'my-app',
    template: `...`,
    styles: [...],
    directives: [DialogComponent, DialogAnchorDirective],
    entryComponents: [DialogComponent]
})

最后, 我们查找AppComponent’的子组件DialogAnchorDirective以及使用createDialog方法去完成AppComponent的openDialogBox 方法的逻辑:

export class AppComponent {
    @ViewChild(DialogAnchorDirective) dialogAnchor: DialogAnchorDirective;

    openDialogBox() {
        this.dialogAnchor.createDialog(DialogComponent);
    }
}

一定要运行完整例子

第 8 段(可获 0.6 积分)

回顾

使用一个像DialogAnchorDirective一样的专用组件的最重要的优点是它可以被注入到任何子组件中. 在一些情况下, 这可能不是必要的, 所以一个这里言简意赅的例子, 完全去掉了这个额外的指令,可以在这里获取到. 在这个例子中要注意我们如何使用  @ViewChild‘的“读” API来获取锚点div中的ViewContainerRef。

第 9 段(可获 0.73 积分)

文章评论