将模型和模板动态绑定到 Angular 2 中的 DOM 节点 [英] Dynamically bind model and template to at DOM node in Angular 2

查看:21
本文介绍了将模型和模板动态绑定到 Angular 2 中的 DOM 节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

短版

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

由于用户 3636086 的响应,现在可以使用了.

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


长版

角 1

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

interface IView {模板:字符串;}类 SalesView 实现 IView {销售额:数量 = 100;get template() { return "<p>Current sales: {{model.sales}} widgets.<p>>;}}类 CalendarView 实现了 IView {事件名称:字符串=圣诞派对";get template() { return "<p>Next event: {{model.eventName}}.<p>";}}类 CompositeView 实现 IView {日历视图 = 新日历视图();salesView = new SalesView();获取模板(){ 返回`<div view='model.salesView'></div><div view='model.calendarView'></div>`;}}

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

如果 viewInstance 发生变化,则会在 DOM 中的该位置呈现一个新的 View 对象(模型 + 模板).例如,此仪表板视图可以具有可以呈现的任意视图列表:

class Dashboard 实现 IView {视图:数组= [新销售视图(),新日历视图(),新组合视图()];活动视图:查看;get template() { return "<h1>Dashboard</h1><div view='model.activeView'>>";}}

一个关键点是它是可组合的. 可以包含 ,后者可以包含 ,依此类推.强>

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

.directive("查看", [ "$compile",($compile: ng.ICompileService) =>{返回<ng.IDirective>{限制:A",范围:{模型:=视图";},链接(范围:ng.IScope,e:ng.IAugmentedJQuery,atts:ng.IAttributes):无效{scope.$watch((scope: any) => scope.model, (newValue: any) => {e.html(newValue.template);$compile(e.contents())(scope.$new());});}};}]);

角 2

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

这是我想出的最好的(根据用户 3636086 的反馈更新):

@Component({选择器:'视图',模板:'<span #attach></span>',})导出类 MyView {@Input() 模型:IView;以前的组件:组件引用;构造函数(私有加载器:DynamicComponentLoader,私有元素:ElementRef){}onChanges(changes: {[key: string]: SimpleChange}) {var modelChanges = changes['model']如果(模型更改){var 模型 = modelChanges.currentValue;@成分({选择器:'viewRenderer',模板:model.template,})类 ViewRenderer {型号:任意;}如果(this.previousComponent){this.previousComponent.dispose();}this.loader.loadIntoLocation(ViewRenderer, this.element, 'attach').then(组件 => {component.instance.model = 模型;this.previousComponent = 组件;});}}}

使用了这样的东西:

@Component({选择器:'应用',模板:`<view [model]='currentView'></view><button(点击)='changeView()'>改变视图</button>`,指令:[MyView]})出口类应用{currentView: IView = new SalesView();更改视图(){this.currentView = new CalendarView();}}

问题现已修复.

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

查看此处呈现的内容

相反,我们有:

<span></spawn><viewrenderer>查看此处呈现的内容</viewrenderer></查看>

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

<span></spawn><查看渲染器><h1>内容</h1><查看><span></spawn><查看渲染器><h1>嵌套内容</h1><查看><span></spawn><查看渲染器><h1>嵌套嵌套内容</h1></viewrenderer></查看></viewrenderer></查看></viewrenderer><查看渲染器><h1>更多内容</h1><查看><span></spawn><查看渲染器><h1>内容</h1></viewrenderer></查看></viewrenderer></查看>

解决方案

简短版本

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

<小时>

长版

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

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

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

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

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

如果你在 gitter 线程中 grep "DCL" 或 "DynamicComponentLoader",那里有几个关于这个主题的有趣对话.一名核心团队成员说了一些大意:DCL 是一种强大的工具,我们只希望它会被真正做框架相关事情的人使用"——我发现……很有趣.

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

Short version

This Plunker defines a <view> component which can render an arbitrary model+template. This needs to be changed to replace the previously rendered contents rather than appending new peers.

EDIT: This is working now, thanks to the response by user3636086.

One problem still remains: unlike Angular 1, Angular 2 forces me to create a nested component to update a template (since templates are effectively a static property of a component's class), so I have a bunch of unnecessary DOM nodes being added.


Long Version

Angular 1

In our project we'd prefer most of our code to have no direct dependency on a UI framework. We have a viewmodel class which ties together a model and view. Here are simplified examples:

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

We have a view directive that can display one of these views:

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

If viewInstance changes, a new View object is rendered (model + template) at that location in the DOM. For instance, this Dashboard view can have an arbitrary list of views that it can render:

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

A crucial point is that this is composable. The <view> can contain a <view> which can contain a <view>, so on and so forth.

In Angular 1, our view directive looks something like this:

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

Angular 2

I'm trying to port this to Angular 2, but dynamically loading a new template at a DOM location is very clunky, forcing me to create a new component type every time.

This is the best I've come up with (updated with feedback from 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;
                });
        }
    }
}

Used something like this:

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

EDIT: This had problems that have now been fixed.

The remaining problem is that it creates a bunch of unnecessary nested DOM elements. What I really want is:

<view>VIEW CONTENTS RENDERED HERE</view>

Instead we have:

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

This gets worse the more views we have nested, without half the lines here being extraneous crap:

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

解决方案

Short version

see https://github.com/angular/angular/issues/2753 (the recent comments, not the original issue)


Long version

I have a similar use-case and have been keeping an eye on chatter about recommended approaches to it.

As of now, DynamicComponentLoader is indeed the de-facto tool for dynamic component compilation (read: stand-in for $compile) and the approach you've taken in your example is essentially identical to this one, which @RobWormald has posted in response to several similar questions on gitter.

Here's another interesting example @EricMartinez gave me, using a very similar approach.

But yes, this approach feels clunky to me too, and I've yet to find (or come up with) a more elegant way of doing this with DCL. The comments on the github issue I linked above contain a third example of it, along with similar criticisms that have so far gone unsanswered.

I have a hard time believing that the canonical solution for a use-case as common as this will be so clunky in the final release (particularly given then relative elegance of $compile), but anything beyond that would be speculation.

If you grep "DCL" or "DynamicComponentLoader" in the gitter thread, there are several interesting conversations on this topic there. One of the core team guys said something to the effect of "DCL is a power-tool that we only expect will be used by people doing really framework-ey things" - which I found... interesting.

(I'd have quoted/linked to that directly if gitter's search didn't suck)

这篇关于将模型和模板动态绑定到 Angular 2 中的 DOM 节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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