有没有办法用客户模块中的组件替换 Angular 组件,就像在 AngularJS 中使用装饰器一样?

Is there a way to replace an Angular component by a component in a customer module like when using the decorator in AngularJS?

我正在为广泛的客户范围开发应用程序。不同的客户在 UI 中往往有不同的定制需求。因此,我们希望用客户特定的组件替换这些组件。不幸的是,这似乎是不可能的。有人能帮忙吗?

我们希望的情况:

在这种情况下,我们希望显示 CustomerSchedulerEvent 而不是正常的 SchedulerEvent。虽然代码以这种方式正确编译,但仍会显示 SchedulerEvent。

在旧的 AngularJS 代码中,有装饰器的概念可以替代整个 directives/components,这里描述如下:https://docs.angularjs.org/guide/decorators#directive-decorator-example.

是否有可能让这种行为在现代 Angular 中也起作用?!

虽然很麻烦,但至少似乎有一个解决方法:

  1. 确保您有组件主机指令。我们稍后会用到那个。
@Directive({
    selector: '[componentHost]',
})
export class ComponentHostDirective {
    constructor(readonly $viewContainerRef: ViewContainerRef) { }
}
  1. 创建返回要呈现的组件类型的服务:
import { Injectable, Type } from '@angular/core';
[...]
@Injectable()
export class TemplateComponentService extends TemplateComponentBaseService {
    getTemplate(): Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent> {
        // console.log('Our EventInformationTemplateService...');
        return LaneSubscriberSchedulingEventInformationTemplateComponent;
    }
}

您在基本模块中注册如下:

@NgModule({
    ...
    providers: [
        EventInformationTemplateService,
        { provide: EventInformationTemplateBaseService, useExisting: EventInformationTemplateService }
    ]
})
export class BaseModule {
}
  1. 您可以替换的组件应如下所示:
import { AfterViewInit, Component, ComponentFactoryResolver, ElementRef, Type, ViewChild } from "@angular/core";
import { ComponentHostDirective } from "src/common/directives/component-host.directive";
import { AggregateServiceFactory } from "src/common/services/aggregates";
import { LaneSubscriberSchedulingEventInformationTemplateBaseComponent } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.component";
import { EventInformationTemplateBaseService } from "src/production-planning/components/lane-subscriber-scheduling/event-information-template/event-information-template-base.service";
import { moduleName } from "src/production-planning/production-planning.states";

@Component({
    selector: 'app-template',
    templateUrl: './template.component.html',

})
export class TemplateComponent extends TemplateBaseComponent implements AfterViewInit {
    componentType: Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>;
    customComponent: boolean;

    @ViewChild(ComponentHostDirective, { static: true }) private _componentHost: ComponentHostDirective;

    constructor(
        $element: ElementRef,
        private readonly $componentFactoryResolver: ComponentFactoryResolver,
        private readonly _templateComponentService: TemplateComponentBaseService) {

        this.componentType = this._templateComponentService.getComponent();
        this.customComponent = !isNull(this.componentType) && this.componentType !== TemplateComponent;

        // console.group('TemplateComponentService.getComponent()');
        // console.log('Component type', this.componentType);
        // console.log('Component custom?', this.customComponent);
        // console.groupEnd();
    }

    // Component lifecycle events
    ngAfterViewInit(): void {
        if (this.customComponent === true) {
            const componentFactory = this.$componentFactoryResolver.resolveComponentFactory(this.componentType);
            this._componentHost.$viewContainerRef.clear();
            const componentRef = this._componentHost.$viewContainerRef.createComponent<LaneSubscriberSchedulingEventInformationTemplateBaseComponent>(componentFactory);
            componentRef.instance.event = this.event;
        }
    }
}

及其模板文件如:

<ng-container *ngIf="customComponent != true">
    <!-- TODO Display more information -->
    <strong>{{ event.title }}</strong>
</ng-container>
<ng-template componentHost></ng-template>
  1. 像上面那样创建另一个服务,并在您应用的另一个模块中以相同的方式注册它。

如您所见,我们使用 *ngIf 隐藏原始组件内容,并使用 ng-template 上的组件主机在服务 returns 与当前服务不同的类型时在其位置呈现替换组件类型。选择这条奇怪路径的原因是标签将直接映射到我们的基本模板组件并且不可替换。

此方案的一个缺点是组件宿主指令作为 ViewChild,仅在视图初始化之后可用,这已经很晚了。对于非常复杂的场景,此解决方法可能因此导致一些不需要的计时问题,例如...

希望有谁能提供更好的解决方案?