'How do you load Components dynamically in Angular 13?

I was tasked to update an Angular library from 7 to 13. This Library used Compiler and ComponentFactory to dynamically load Components. But those classes are now deprecated, and I found no real guide on how to do this without using these classes. Here's the code in question:

dynamic-content.component.ts:

...
   private createCompiledTemplateFactory = (template: string, extensionType: string): ComponentFactory<any> | undefined => {
        const metadata = {
            selector: `dynamic-extended-component-${extensionType}`,
            template: template
        };

        const extType = ClassInjector.get(extensionType);
        if (!extType) {
            throw new Error('Type not found: ' + extensionType);
        }

        return this.createComponentFactorySync(metadata, null, extType);
    }

    private createComponentFactorySync = (metadata: Component, componentClass: any, extensionType: Type<any>)
        : ComponentFactory<any> | undefined => {
        const cmpClass = componentClass || class RuntimeComponent extends extensionType { };
        const decoratedCmp = Component(metadata)(cmpClass);
        const externalImports = this.externalImports;

        @NgModule({
            imports: [
                CommonModule,
                FormsModule,
                DynamicGridModule.forChild(),
                DxButtonModule,
                DxSwitchModule,
                TranslateModule,
                RouterModule,
                externalImports,
                DynamicPageCommonComponentsModule
            ],
            declarations: [decoratedCmp]
        })
        class RuntimeComponentModule { }

        const module: ModuleWithComponentFactories<any> = this.compiler.compileModuleAndAllComponentsSync(RuntimeComponentModule);
        return module.componentFactories.find(f => f.componentType === decoratedCmp);
    }
...

class-injector.ts

export class ClassInjector {
    private static registry: { [key: string]: Type<any> } = {};

    static register(key: string, value: Type<any>) {
        const registered = ClassInjector.registry[key];
        if (registered) {
            throw new Error(`Error: ${key} is already registered.`);
        }

        ClassInjector.registry[key] = value;
    }

    static get(key: string): Type<any> {
        const registered = ClassInjector.registry[key];
        if (registered) {
            return registered;
        } else {
            throw new Error(`Error: ${key} was not registered.`);
        }
    }
}

export function RegisterActionHandler(name: string) {
    return (target: Type<any>) => ClassInjector.register(name, target);
}

I would appreciate it if anyone could guide me in the right direction.



Solution 1:[1]

We found out that the component could work without any mention of compiler:

dynamic-content.component.ts:

...
private createComponent = (template: string, extensionType: string) : Type<any> => {
  const metadata = {
      selector: `dynamic-extended-component-${extensionType}`,
      template: template
  };

  const extType = ClassInjector.get(extensionType);
  if (!extType) {
      throw new Error('Type not found: ' + extensionType);
  }

  return Component(metadata)(class RuntimeComponent extends extType { });
}

private createModule = (component: Type<any>) : Type<any> => {
    const externalImports = this.externalImports;
    const module = NgModule({
        imports: [
            CommonModule,
            FormsModule,
            DynamicGridModule.forChild(),
            DxButtonModule,
            DxSwitchModule,
            TranslateModule,
            RouterModule,
            externalImports,
            DynamicPageCommonComponentsModule
        ],
        declarations: [component]
    })(
    class RuntimeComponentModule { });

    return module;
}
...

class-injector.ts stayed the same.

Solution 2:[2]

You can use cdkPortalOutlet from here ,

Here is a demo code to play around with as well.

You can also listen to events of rendering as below

<ng-template [cdkPortalOutlet]="portal" (attached)="onComponentRendering($event)"></ng-template>

in the component

this.portal = new ComponentPortal(SomeComponent);

and listen to onComponentRendering to do more

Solution 3:[3]

Without using the factory, you have to use ViewContainerRef.createComponent. refer the below link https://stackoverflow.com/a/72174262/19077843

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 tsavinho
Solution 2 Shashank Vivek
Solution 3