精简版
这个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>