I want to create an angular 7 web application that dynamically loads different components, as demonstrated in this official documentation: https://angular.io/guide/dynamic-component-loader
But I am not sure if it's a good idea to use ComponentFactoryResolver
.
I never used it and I don't know if it is stable and I don't know about the performance either.
I would like some opinions about it and if anyone knows any alternatives.
I don't want to use native innerHTML
I am trying to create a custom and generic wizard with dynamic steps. This wizard has
The steps are dynamic. Based on some business logic like user's inputs from previous steps.
My current implementation:
I will show only the part where I am using the ComponentFactoryResolver
to make it understandable and readable :)
export class WizComponent implements OnInit {
public wizContentItems: WizContentItem[] = undefined;
public currentContentItem: WizContentItem = undefined;
public currentContentItemNumber: number = -1;
public currentWizContentComponent: WizContentComponent = undefined;
private componentRef: any;
@Output() public onStepChanged = new EventEmitter<StepPosition>();
private _position: StepPosition = StepPosition.First;
constructor(private componentFactoryResolver: ComponentFactoryResolver, private viewContainerRef: ViewContainerRef) { }
public ngOnInit() {
}
public onSelectStep(contentItem: WizContentItem) {
console.log("step was clicked");
console.log(contentItem);
if (this.currentContentItem !== undefined &&
!this.validateStep(this.currentContentItem)) {
return;
}
if (this.currentWizContentComponent !== undefined ) {
this.currentContentItem.stepProgressStatus = this.currentWizContentComponent.stepProgressStatus;
}
contentItem.stepState = StepState.Active;
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(contentItem.component);
this.viewContainerRef.clear();
this.componentRef = this.viewContainerRef.createComponent(componentFactory);
(<WizContentComponent>this.componentRef.instance).data = contentItem.data;
(<WizContentComponent>this.componentRef.instance).stepState = contentItem.stepState;
this.currentWizContentComponent = (<WizContentComponent>this.componentRef.instance);
if (this.currentContentItem != null) {
this.currentContentItem.stepState = StepState.Empty;
}
this.currentContentItem = contentItem;
this.currentContentItem.stepState = StepState.Active;
// Get currentContentItemNumber based currentContentItem
this.currentContentItemNumber = this.wizContentItems.findIndex(wizContentItem => wizContentItem === this.currentContentItem);
this.stepChanged();
}
public onNextClick(event: Event) {
if ((this.currentContentItemNumber + 1) < this.wizContentItems.length) {
let nextContentItem = this.wizContentItems[this.currentContentItemNumber + 1];
if (nextContentItem.stepState === StepState.Disabled) {
nextContentItem = this.getNextActiveItem(this.currentContentItemNumber + 1);
}
if (nextContentItem != null) {
this.onSelectStep(nextContentItem);
}
}
}
public onPreviousClick(event: Event) {
if ((this.currentContentItemNumber - 1) >= 0) {
let previousContentItem = this.wizContentItems[this.currentContentItemNumber - 1];
if (previousContentItem.stepState === StepState.Disabled) {
previousContentItem = this.getPreviousActiveItem(this.currentContentItemNumber - 1);
}
if (previousContentItem !== null) {
this.onSelectStep(previousContentItem);
}
}
}
public getCurrentStepPosition(): StepPosition {
return this._position;
}
private validateStep(contentItem: WizContentItem): boolean {
return (<WizContentImplComponent>this.componentRef.instance).isValid();
}
private stepChanged(): void {
this._position = undefined;
if (this.currentContentItemNumber <= 0) {
this._position = StepPosition.First;
} else if (this.currentContentItemNumber >= this.wizContentItems.length) {
this._position = StepPosition.Last;
} else {
this._position = StepPosition.Middle;
}
if ((<WizContentComponent>this.componentRef.instance).isSummary) {
this._position = StepPosition.Summary;
}
this.onStepChanged.emit(this._position);
}
private getNextActiveItem(itemNumber: number): WizContentItem {
if (this.wizContentItems.length <= (itemNumber + 1)) {
return null;
}
let nextContentItem = null;
for (let i = (itemNumber); i < this.wizContentItems.length; i++) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
nextContentItem = this.wizContentItems[i];
break;
}
}
return nextContentItem;
}
private getPreviousActiveItem(itemNumber: number): WizContentItem {
if ((itemNumber - 1) < 0 ) {
return null;
}
let previousContentItem = null;
for (let i = (itemNumber - 1); i >= 0; i--) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
previousContentItem = this.wizContentItems[i];
break;
}
}
return previousContentItem;
}
}
Thank you!!
ComponentFactoryResolverlinkA simple registry that maps Components to generated ComponentFactory classes that can be used to create instances of components. Use to obtain the factory for a given component type, then use the factory's create() method to create a component of that type.
To add the component to the template, you call createComponent() on ViewContainerRef .
A factory component is a composite activity. Structurally, it contains a set of viewpoints and a production plan. Behaviorally, it delegates its activities to other factory components or tasks.
ComponentFactorylinkBase class for a factory that can create a component dynamically. Instantiate a factory for a given type of component with resolveComponentFactory() . Use the resulting ComponentFactory. create() method to create a component of that type. Deprecated: Angular no longer requires Component factories.
As a complement to the previous answer, to better compare the two methods, it might be worth adding a few details on what is going on in each case.
Steps to 'create' a component with the FactoryResolver service:
resolveComponentFactory()
method: this method takes, as parameter, the component type, and
looks for the corresponding 'component factory'.createComponent()
method of the ViewContainerRef
classFor information: https://angular.io/guide/dynamic-component-loader#resolving-components
Steps applied when a structural directive (ngIf
, ngSwitch
...) 'creates' a component:
ViewContainerRef
class (the
createEmbeddedView()
method).=> the two methods go roughly through the same steps (actually the 'structural directive' method adds an additional step, the creation of an embedded view, which, I think, is negligible).
Therefore, in my opinion, the most valuable reason to choose one out of the two options is the use case, which I would summerize as the following:
Structural directive (ngIf
, ngSwitch
...):
FactoryResolver service:
Yes it is good to use the ComponentFactoryResolver
that is why it is in the official documentation. It is stable it is inside since Angular 2. It has no significant performance hit.
Many Angular libraries use it internally also the Angular Material library. Check the Portal inside the Component Development Kit (CDK) and its source in GitHub where you can see it being used for displaying dynamic content inside it.
Regarding your question if it is better to do NgSwitch
or create components using the ComponetFactoryResolver
is difficult to answer since it depends on what you are trying to do and you did not explain what exactly is your scenario. I would say that in most cases you should use the ComponentFactoryResolver
since it allows you to add any component dynamically and you don't have a big component with a huge NgSwitch
for all possible dynamic components. Only in the case you have a very small number of dynamic components and you don't expect new ones will be added it might be more easy to create them using NgSwitch
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With