将模型和模板动态绑定到Angular 2中的DOM节点

Mud*_*Mud 7 angular2-directives angular2-template angular

精简版

Plunker定义了一个<view>可以渲染任意模型+模板的组件.需要更改此项以替换先前呈现的内容,而不是添加新的对等项.

编辑:由于user3636086的响应,现在正在运行.

仍然存在一个问题:与Angular 1不同,Angular 2强迫我创建一个嵌套组件来更新模板(因为模板实际上是组件的静态属性),所以我添加了一堆不必要的DOM节点.


长版

角度1

在我们的项目中,我们更喜欢我们的大部分代码都没有直接依赖于UI框架.我们有一个viewmodel类,它将模型和视图连接在一起.以下是简化示例:

interface IView {
    template: string;
}

class SalesView implements IView  {
    sales: number = 100;
    get template() { return "<p>Current sales: {{model.sales}} widgets.<p>"; }
}

class CalendarView implements IView {
    eventName: string = "Christmas Party";
    get template() { return "<p>Next event: {{model.eventName}}.<p>"; }
}

class CompositeView implements IView  {
    calendarView = new CalendarView();
    salesView = new SalesView();
    get template() { return 
        `<div view='model.salesView'></div>
        <div view='model.calendarView'></div>`; 
    }
}
Run Code Online (Sandbox Code Playgroud)

我们有一个view可以显示以下视图之一的指令:

<div view='viewInstance'></div>
Run Code Online (Sandbox Code Playgroud)

如果viewInstance更改,则在DOM中的该位置呈现新的View对象(模型+模板).例如,此Dashboard视图可以具有可以呈现的任意视图列表:

class Dashboard implements IView {
    views: Array<IView> = [ new SalesView(), new CalendarView(), new CompositiveView() ];
    activeView: View;
    get template() { return "<h1>Dashboard</h1>  <div view='model.activeView'>"; }
}
Run Code Online (Sandbox Code Playgroud)

关键是这是可组合的.该<view>可包含一个<view>可以包含一个<view>,等等等等.

在Angular 1中,我们的view指令看起来像这样:

.directive("View", [ "$compile",
    ($compile: ng.ICompileService) => {
        return <ng.IDirective> {
            restrict: "A",
            scope: { model: "=View" },
            link(scope: ng.IScope, e: ng.IAugmentedJQuery, atts: ng.IAttributes): void {
                scope.$watch((scope: any) => scope.model, (newValue: any) => {
                    e.html(newValue.template);
                    $compile(e.contents())(scope.$new());
                });
            }
        };
    }
]);
Run Code Online (Sandbox Code Playgroud)

Angular 2

我正在尝试将其移植到Angular 2,但是在DOM位置动态加载新模板非常笨重,迫使我每次都要创建一个新的组件类型.

这是我提出的最好的(更新来自user3636086的反馈):

@Component({
  selector: 'view',
  template: '<span #attach></span>',
})
export class MyView {
    @Input() model: IView;

    previousComponent: ComponentRef;

    constructor(private loader: DynamicComponentLoader, private element: ElementRef) {
    }

    onChanges(changes: {[key: string]: SimpleChange}) {
        var modelChanges = changes['model']
        if (modelChanges) {
            var model = modelChanges.currentValue;
            @Component({
                selector: 'viewRenderer',
                template: model.template,
            })
            class ViewRenderer {
                model: any;
            }
            if (this.previousComponent) {
                this.previousComponent.dispose();
            }
            this.loader.loadIntoLocation(ViewRenderer, this.element, 'attach')
                .then(component => {
                    component.instance.model = model;
                    this.previousComponent = component;
                });
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这样的东西:

@Component({
    selector: 'app',
    template: `
        <view [model]='currentView'></view>
        <button (click)='changeView()'>Change View</button>
    `,
    directives: [MyView]
})
export class App {
    currentView: IView = new SalesView();
    changeView() {
        this.currentView = new CalendarView();
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:这问题,现在已经修复.

剩下的问题是它创建了一堆不必要的嵌套DOM元素.我真正想要的是:

<view>VIEW CONTENTS RENDERED HERE</view>
Run Code Online (Sandbox Code Playgroud)

相反,我们有:

<view>
      <span></spawn>
      <viewrenderer>VIEW CONTENTS RENDERED HERE</viewrenderer>
</view>
Run Code Online (Sandbox Code Playgroud)

我们嵌套的视图越多越好,这里没有一半的线是无关的垃圾:

<view>
    <span></spawn>
    <viewrenderer>
        <h1>CONTENT</h1>
        <view>
            <span></spawn>
            <viewrenderer>
                <h1>NESTED CONTENT</h1>
                <view>
                    <span></spawn>
                    <viewrenderer>
                        <h1>NESTED NESTED CONTENT</h1>
                    </viewrenderer>
                </view>
            </viewrenderer>
        </view>
    </viewrenderer>
    <viewrenderer>
        <h1>MORE CONTENT</h1>
        <view>
            <span></spawn>
            <viewrenderer>
                <h1>CONTENT</h1>
            </viewrenderer>
        </view>
    </viewrenderer>
</view>
Run Code Online (Sandbox Code Playgroud)

dre*_*ore 3

简洁版本

请参阅https://github.com/angular/angular/issues/2753(最近的评论,不是原始问题)


长版

我有一个类似的用例,并且一直在关注有关推荐方法的讨论。

到目前为止,DynamicComponentLoader确实是动态组件编译的事实上的工具(阅读:替代$compile),并且您在示例中采用的方法基本上与相同,@RobWormald 已发布该方法以回应几个问题关于 gitter 的类似问题。

这是@EricMartinez 给我的另一个有趣的例子,使用了非常相似的方法。

但是,是的,这种方法对我来说也很笨重,而且我还没有找到(或想出)一种更优雅的方法来使用 DCL 来实现这一点。我上面链接的关于 github 问题的评论包含第三个例子,以及迄今为止尚未得到答复的类似批评。

我很难相信像这样常见的用例的规范解决方案在最终版本中会如此笨重(特别是考虑到当时相对优雅的$compile),但除此之外的任何事情都将是猜测。

如果您在 gitter 线程中 grep "DCL" 或 "DynamicComponentLoader",就会发现关于这个主题的几个有趣的对话。一位核心团队人员说了一些大意是“DCL 是一种强大的工具,我们只希望人们能够真正使用它来做框架相关的事情”——我发现这很有趣。

(如果 gitter 的搜索不糟糕的话,我会直接引用/链接到该内容)