I am developing an application for a wide customer range. Different customers tend to have different needs for customization in their UI. Therefore, we would like to replace those components by customer-specific components. Unfortunately, this seems to be impossible. Could anyone be of help?
The situation we would like:
In this situation, we would like to have the CustomerSchedulerEvent displayed instead of the normal SchedulerEvent. Although the code compiles properly this way, still the SchedulerEvent is displayed.
In old AngularJS code, there was the decorator concept which could replace entire directives/components, which is being described here: https://docs.angularjs.org/guide/decorators#directive-decorator-example.
Is there a possibility to get kind-of this behavior working in modern Angular as well?!
Although quite cumbersome, at least there appears to be a workaround:
@Directive({
selector: '[componentHost]',
})
export class ComponentHostDirective {
constructor(readonly $viewContainerRef: ViewContainerRef) { }
}
import { Injectable, Type } from '@angular/core';
[...]
@Injectable()
export class TemplateComponentService extends TemplateComponentBaseService {
getTemplate(): Type<LaneSubscriberSchedulingEventInformationTemplateBaseComponent> {
// console.log('Our EventInformationTemplateService...');
return LaneSubscriberSchedulingEventInformationTemplateComponent;
}
}
which you register as follows in your base module:
@NgModule({
...
providers: [
EventInformationTemplateService,
{ provide: EventInformationTemplateBaseService, useExisting: EventInformationTemplateService }
]
})
export class BaseModule {
}
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;
}
}
}
and its template file like:
<ng-container *ngIf="customComponent != true">
<!-- TODO Display more information -->
<strong>{{ event.title }}</strong>
</ng-container>
<ng-template componentHost></ng-template>
As you can see, we hide the original component content using *ngIf and use the component host on the ng-template to render the replacing component in its place when the service returns another type than the current type. The reason to opt for this strange path, is that the tag will directly map to our base template component and is not replaceable.
A drawback for this scenario is that the component host directive, being a ViewChild, is only available after view init, which is quite late. For very complex scenarios, this workaround could therefore lead to some unwanted timing issues and such...
I hope anyone could provide a better solution?