Angular App dynamically load plugin without recompile

3.1k views Asked by At

I'm trying to develop the frontend of my Web Api (NET CORE) pluginable application. I would like to use Angular 9 but im not an expert in angular.

My backend was designed to be extensible and at startup it watches in a specified folder and if exists one or more dll files that contains logic to extend base application (like a plugin) it loads them. I want to use a similar approach in the frontend. I tried different solutions and read a lot of articles but is difficult to find somebody that want to import unknown plugin at compile time.

I tried lazy modules (starting from this: https://www.mokkapps.de/blog/manually-lazy-load-modules-and-components-in-angular/) that would be perfect but using this I have to know implemented plugin (modules) before compiling my angular app because if I want to use the modules I have to use Import function in my main app.

So I searched more and after the article Load new modules dynamically in run-time with Angular CLI & Angular 5 I tried System.Js approach but I can't find a working solution for angular 9.

I'am pretty sure that I am not the only one that would create a pluginable Angular app that load plugins without recompile main app.

I need some suggest to the right approach to follow or a working example of an angular app that use plugins architecture.

1

There are 1 answers

0
Fabio Cavallari On

I'm not sure this is the better and elegant solution because I'm a newbie in Angular but it works for me at the moment.

I assume that a plugin is an Element Web Component (https://angular.io/guide/elements). To create my first Element (Plugin) I followed this tutorial: https://www.techiediaries.com/angular/angular-9-elements-web-components/.

By the way at this point I cant load my plugins dinamically because I have to know the component's name that I deployed in my element before compile the project to use it. I found the solution using Extensions Element (https://angular-extensions.github.io/elements/#/home). So I created a dynamic component that I use to show the plugin's component at runtime.

This is the code of the dynamic component:

export class DynamicComponentContainerComponent implements OnInit {
  plugin: Plugin
  sub: any;

  constructor(private route: ActivatedRoute, private pluginService: PluginService) { }

  ngOnInit() {
    this.sub = this.route
      .data
        .subscribe(data => {
           this.pluginService.getPlugin(data['pluginName'])
            .subscribe(
              (res) => {
                this.plugin = res;
              },
              (err) => {
                console.error(err)
              } 
            );
          });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}

And its html template:

<div *ngIf="plugin != null">
<ng-template #loading>Loading plugin {{plugin.tag}} ...</ng-template>
<ng-template #error>Error Loading plugin {{plugin.tag}}</ng-template>
<ax-lazy-element
    *axLazyElementDynamic="plugin.tag; url: plugin.url; module: plugin.isModule; 
errorTemplate: error; loadingTemplate: loading;">
</ax-lazy-element>
</div>

It can works because my backend serves the plugin's JS compiled file (Element Web Component) so I had to register my plugin (because I need some values to handle it like the component's name or the route's path) before using it. In fact, the axLazyElementDynamic attribute in the dynamic component need the url of the JS Element Web Component file and the component name to work.

Now I had to add dinamically the route paths to every plugin component. In my App Component I created this simply method:

loadPlugins() {
  this.pluginService.getPlugins()
    .subscribe(plugins => {
      plugins.forEach(plugin => {
        this.router.config.unshift({ path: plugin.path, component: 
DynamicComponentContainerComponent, data: { pluginName: plugin.name } });
        this.links.push({ text: plugin.description, path: plugin.path });
      });
    });
}

The plugin service simply obtains plugins data from backend (where I registered plugins before).

I hope this can helps someone.