Angular 相当于 WPF 的 ContentPresenter 是什么?

What's the Angular equivalent to WPF's ContentPresenter?

关于 Angular 使用 TypeScript。

我来自 WPF 世界,现在我正在尝试一些 Angular 开发。

我的应用程序中有这个地方,我想建立一个选项卡控件来包含各种 'open' 文档(Angular 组件)。也许我认为这是完全错误的,但让我们考虑一下,有一个包含 TabItem 数组的可注射物 TabItem 的属性之一是 class 的字符串、工厂或我的应用程序组件的类型名称(待定)。

export class TabItem {
    public title : string;
    public disabled : boolean;
    public active : boolean;
    public factory: any; // or class name or object
}
@Injectable()
export class OpenDocumentService {
    openTabs: Array<TabItem> = [];
    addTab(t:TabItem){ openTabs.push(t); }
}

在 WPF 世界中,我会创建一个内容展示器并将其绑定到要显示的名称或对象。

在 Angular 的世界里我会做什么。备注:显示的组件可能在不同的模块中。

我怎样才能 *ngFor 覆盖它并在添加到服务时显示任意组件? (替换 ng-contentpresenter)

     <tabset>
        <tab *ngFor="let tabz of tabservice.openTabs"
[heading]="tabz.titel">
           <ng-contentpresenter use={{tabz?.factory}}/>
        </tab>
    </tabset>

致所有来到这里的人:

简短回答 - angular 不赞成这样的骗子,所以你最好坚持使用推荐的方法来构建 ui - 比如 templates injection, correct usage of routing, ngSwitch,对于带有树的项目浏览器的复杂情况添加@ngrx/store,等等

长答案 - look here。 你必须首先建立基础设施:

  • 您将需要创建服务来保存模型的组件。使用数据类型的构造函数作为键和组件构造函数类型作为值 (DataTemplateService)
import { Injectable, Type } from '@angular/core';

/**
 * This service allows dynamically bind viewModel and component in configuration stage and then resolve it in render stage.
 * Service for dynamic component registration and detection. Component to be used are determined based on view model they have to render.
 */
@Injectable()
export class DataTemplateService {

    private dictionary: Map<Type<any>, Type<any>>;

    constructor() {
        this.dictionary = new Map<Type<any>, Type<any>>();

    }
    /**
     * Determines component class, searching in registered components.
     * @param data ViewModel that will be used for component, returns undefined if not found.
     */
    public determine(data: any): Type<any> | undefined {
        return data ? this.dictionary.get(data.constructor) : undefined;
    }

    /**
     * Registers binding of certain view model towards certain component.
     * @param viewModelType Type of ViewModel to be registered.
     * @param componentType Type of Component to be registered.
     */
    public register(viewModelType: Type<any>, componentType: Type<any>) {
        this.dictionary.set(viewModelType, componentType);
    }
}
  • 您将需要使用 ComponentFactoryResolver 从构造函数创建组件并在其中设置 viewModel 的服务 (ComponentRendererService)

import { ComponentFactoryResolver, Injectable } from '@angular/core';

/**
 * Service fro rendering dynamic components.
 */
@Injectable()
export class ComponentRendererService {

    constructor(private componentFactoryResolver: ComponentFactoryResolver,
        private dataTemplateService: DataTemplateService) {
    }
    public render(data: any, containerComponent: any) {
        setTimeout(() => {
            this.doRender(data, containerComponent);
        }, 0);
    }

    /**
     * Renders dynamic components based on ViewModel they have to use.
     * @param data Collection of ViewModels that have to be used to render all child components.
     * @param containerComponent Parent component, that have to host dynamic child components.
     */
    public renderAll(data: Array<any>, containerComponent: any) {
        setTimeout(() => {
            if (data) {
                data.forEach(item => {
                    this.doRender(item, containerComponent);
                });
            }
        }, 0);
    }

    private doRender(data: any, containerComponent: any) {
        if (!data) {
            console.debug('No data (viewModel) for ComponentRendererService to render.');
            return;
        }
        const viewContainerRef = containerComponent.viewContainerRef;
        const dataItem = data;
        const component = this.dataTemplateService.determine(dataItem);
        if (component) {
            const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
            const componentRef = viewContainerRef.createComponent(componentFactory);
            (<any>componentRef.instance).data = data;
        } else {
            console.warn('Failed to find component for viewmodel of type' + dataItem.constructor);
        }
    }
}
  • 您将需要作为动态组件和将要调用服务的组件的标记的指令。
import { Directive, ViewContainerRef, Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core';

/**
 *  Directive that enables construction of dynamic child components.
 */
@Directive({
    selector: '[dynamic-component-host]',
})
export class DynamicComponentHostDirective {
    constructor(public viewContainerRef: ViewContainerRef) { }
}

/**
 * Component that represents extention point for rendering dynamic child components.
 */
@Component({
    selector: 'ext-point-single-host',
    template: `
              <div class="ext-point-host">
                <ng-template dynamic-component-host></ng-template>
              </div>
            `
})
export class ExtPointSingleHostComponent implements OnChanges {
    @ViewChild(DynamicComponentHostDirective) public hostDirective: DynamicComponentHostDirective;
    @Input() public viewModel: any;

    constructor(private componentRenderer: ComponentRendererService) { }

    /**
     * Loads nested components.
     */
    public loadComponent() {
        const viewModel = this.viewModel;
        this.componentRenderer.render(viewModel, this.hostDirective);
    }

    public ngOnChanges(changes: SimpleChanges) {
        this.hostDirective.viewContainerRef.clear();
        this.loadComponent();
    }
}

之后您可以将模型绑定到模块中的组件:


@Component({
    template: '<button type="button" class="">Custom style 2</button>'
})
export class CustomButton1Component  {
    public data: CustomButton1ViewModel;
}

export class CustomButton1ViewModel {

}
@Component({
    template: '<button type="button" class="">Custom style 2</button>'
})
export class CustomButton2Component  {
    public data: CustomButton2ViewModel;
}

export class CustomButton2ViewModel {

}

@NgModule({
    ...
    providers: [..., DataTemplateService]
})
export class DemoModule {
    constructor(dataTemplateService: DataTemplateService) {
        dataTemplateService.register(CustomButton2ViewModel, CustomButton2Component);
        dataTemplateService.register(CustomButton1ViewModel, CustomButton1Component);
    }
}

就是这样!

现在我们可以使用它的 viewModel 属性 并将其绑定到 CustomButton1ViewModel 或 CustomButton2ViewModel 以实际呈现 CustomButton2Component 或 CustomButton1Component。为什么这么多代码?好吧,寻找简短的答案:(