Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular - Dynamic Component Loader from string

Tags:

angular

To allow customer customization, I need a way to load component dynamically using a string, taken from backend.

I see this usefull guide: https://medium.com/angular-in-depth/loading-components-dynamically-in-angular-cd13b9fdb715

but, as other tutorial like the official one, they always assume you already know classnames.

Look at this code from the tutorial:

async loadComponent(vcr: ViewContainerRef, isLoggedIn: boolean) {
    const { GuestCardComponent } = await import('./guest-card/guest-card.component');
    const { UserCardComponent } = await import('./user-card/user-card.component');
    ...

The string inside import is not a problem but the definition of class is.

I need a placeholder in my template for loading a component I know I have in the FileSystem but I don't know its name.

Something like this:

From backend i get:

var dynamic_loading_component = { "classname" : "AdidasCardComponent" , "path" : "/components/adidas.component" }

then I would like to use like this:

const { dynamic_loading_component["classname"] } = await import(dynamic_loading_component["path"]);

vcr.clear();
let component : any = dynamic_loading_component["classname"];
   
return vcr.createComponent(this.cfr.resolveComponentFactory(component))

Some sort of Reflection.

Is it possible?

like image 230
EviSvil Avatar asked May 10 '26 02:05

EviSvil


1 Answers

I finally found a way merging some other posts. Its the best I could get.

In the page where you have to dynamic load component, have this:

@ViewChild(PlaceHolderDirective, { static: true })
placeholderHost: PlaceHolderDirective;

...

ngAfterViewInit() 
{
  const viewContainerRef  = this.placeholderHost.viewContainerRef;
  var component_data = { "classname" : "ExampleComponent" }; //this is an example
  this._componentLoader.loadComponent(viewContainerRef, {} );
}

PlaceHolderDirective is:

import { Directive, ViewContainerRef } from '@angular/core';

@Directive({ selector: '[component-placeholder]' })
export class PlaceHolderDirective {
  constructor(public viewContainerRef: ViewContainerRef) {}
}

Now, create a Service for loading component dynamically:

import { Injectable,ComponentFactoryResolver, ViewContainerRef, NgModuleFactoryLoader, Injector, Type } from '@angular/core';

@Injectable({ providedIn: 'root' })

export class ComponentLoaderService 
{
  constructor(
    private cfr                   : ComponentFactoryResolver) 
  {
    //constructor
  }

  async loadComponent(vcr: ViewContainerRef, component_data = {} ) 
  {
    vcr.clear();
    const factories     = this.cfr['ngModule']['instance']['__proto__']['constructor']['__annotations__'][0]['declarations'];
    var factoryClass    = <Type<any>>factories.find((x: any) => x.name === component_data["classname"] );
    const factory       = this.cfr.resolveComponentFactory(factoryClass);
    return vcr.createComponent(factory);
  }
}

You still have to register your component in app.module but its the only section you need to write.

like image 105
EviSvil Avatar answered May 20 '26 01:05

EviSvil