1

I've been looking for a clean way (without using private API) solution to a widget style dashboard. I want to dynamically load components onto it based on user role.

Is there a way to import a component and dependencies that are included in components' module dynamically and is such solution production ready?

Stackblitz reproduction:

https://stackblitz.com/edit/angular-dynamically-loaded-dashboard?file=src/app/dashboard/dashboard.component.html

Any help towards a clean solution is welcome.

Edit: I have tried resolving components using ViewContainerRef, while the solution works (component is rendered), it just gets appended to the view, and I want to be able to get the reference of the component with resolved dependencies and pass it into a *ngComponentOutlet so I can use gridster to move and resize component.

Beckilicki
  • 63
  • 11
  • 1
    Have you looked at https://angular.io/api/core/ComponentFactoryResolver – Shashank Vivek Apr 19 '21 at 15:50
  • So do you need to load the widget modules (with possibly their own services and other dependencies) based on a runtime configuration (logged in user)? And I assume you don’t want to load everything so lazy load those modules? If so, yes, this is possible. – MikeOne Apr 19 '21 at 16:16
  • @ShashankVivek - I have, but I will have multiple components and even possibly multiple instances of the same component. – Beckilicki Apr 19 '21 at 20:24
  • @MikeOne - the idea is that each role will have a defined set of widgets it can see. The configuration won't be in a static array as it is in the example, it will be loaded from a backend. I want to lazy load the modules if the role has permission to see the module, without routing to a different page. – Beckilicki Apr 19 '21 at 20:26
  • I’m doing something similar in an app I’m currently working on so yes it is very possible (and not hacky or anything). You can conditionally do module imports, at runtime. Let me know if you would like to see some code and I might be able to find some time to write an answer here.. – MikeOne Apr 20 '21 at 07:43
  • @MikeOne it would be nice to see what you're working with. I have been trying different things and concluded using ViewContainerRef does not work for me, my goals are to wrap the resolved components into a gridster and drag / resize them. For gridster to work I need to pass the component into a *ngComponentOutlet, and even after resolving component with ViewContainerRef, passing the module and injector when it gets into outlet it still throws the same injection error. – Beckilicki Apr 20 '21 at 07:57

2 Answers2

2

I think what you are looking for is a ComponentFactoryResolver. Refer this demo code

  public render(): void {
    const index = Math.round(Math.random());
    const currentComponent = this.components[index];
    
    let componentFactory = this.componentFactoryResolver.resolveComponentFactory(currentComponent as any);

    let viewContainerRef = this.adHost.viewContainerRef;
    viewContainerRef.clear();

    let componentRef = viewContainerRef.createComponent(componentFactory);
  }
Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
  • How would I render multiple components using view container ref? The example I made is with a single widget, but in real application it will have more. I will use ng-dynamic as a renderer most likely, which resolves components using the mentioned method, but it still does not resolve dependencies. – Beckilicki Apr 19 '21 at 20:23
1

I've managed to solve it! The missing link was the ngModuleFactory part:

  <ng-container
    *ngComponentOutlet="widget.component; ngModuleFactory: widget.module"
  ></ng-container>

And in widget.service.ts I've resolved the module:

   const { TasksWidgetModule } = await import('../tasks-widget/tasks-widget.module');
   module = await this.compiler.compileModuleAsync(TasksWidgetModule);
Beckilicki
  • 63
  • 11