7

精简版

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

编辑:由于 user3636086 的响应,现在可以正常工作。

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


长版

角 1

在我们的项目中,我们希望我们的大部分代码不直接依赖于 UI 框架。我们有一个将模型和视图联系在一起的视图模型类。以下是简化示例:

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>`; 
    }
}

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

<div view='viewInstance'></div>

如果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'>"; }
}

一个关键点是这是可组合的。<view>可以包含 a<view>可以包含 a ,<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());
                });
            }
        };
    }
]);

角 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;
                });
        }
    }
}

使用了这样的东西:

@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();
    }
}

编辑:这现在已经解决的问题。

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

<view>VIEW CONTENTS RENDERED HERE</view>

相反,我们有:

<view>
      <span></spawn>
      <viewrenderer>VIEW CONTENTS RENDERED HERE</viewrenderer>
</view>

我们嵌套的视图越多,情况就越糟,这里没有一半的行是无关紧要的废话:

<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>
4

2 回答 2

3

精简版

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


长版

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

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

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

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

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

如果你在 gitter 线程中 grep “DCL”或“DynamicComponentLoader”,那里有几个关于这个主题的有趣对话。一个核心团队成员说了一些话,大意是“DCL 是一种强大的工具,我们只希望人们会使用它来做真正的框架性事情”——我觉得这很有趣。

(如果 gitter 的搜索不糟糕,我会直接引用/链接到那个)

于 2015-12-14T09:48:57.273 回答
2

正确的行为可以通过对代码的微小更改来实现:您必须在添加新组件之前“处理”先前创建的组件。

savedComp: Component = null;
...
if (this.savedComp) {
  this.savedComp.dispose();
}
this.loader.loadIntoLocation(DynamicComponent, this.element, 'attach')
    then((res) => {res.instance.model = model; this.savedComp = res;});

完整解决方案:http: //plnkr.co/edit/KQM31HTubn0LfL7jSP5l

希望能帮助到你!

于 2015-12-14T20:02:30.437 回答