Create a true plugin architecture using angular ivy

Viewed 1565

I'm working at a project where we wanna build a web frontend using a "true" plugin architecture. Well, what do I mean saying "true".

Imagine having a web app for configuration. The frontend of this app can dynamically change as we are able to install functionality on runtime. We are therefore able to add some new features at runtime. My idea is to have a simple UI that can load multiple plugins. Those plugins can be loaded into this simple UI as single pages.

At the time I'm compiling this simple UI for configuration I don't know the plugins that will be installed later. Unfortunately. My idea is to have a rest API that provides the plugins (maybe as webpack modules?) and that the simple UI will just load those plugins on startup.

I did some research today and I'm quite not sure if doing something like this is possible with angular Ivy. As I'm not an expert maybe someone could answer this, before I'm going to dig deeper.

Is it possible to create angular components that are not known at compile time to the app but that are later loaded via a backend?

Thanks for a short response

Best Regards

2 Answers

It is possible to load a component that has been compiled separately from the main app you are working on and load it in at runtime. There is however some drawbacks.

Let's take the case where you have pre-rendered 5 different plugins to your main app for it to consume. You have these compiled bundled .js files which you serve from a server on demand. Angular gets an update from 8 -> Angular 9 (just an example) and Angular 9 has breaking changes so it is not compatible with 8 anymore (which your plugins were created in).

Your plugins will be rendered useless if you update your main application without re-compiling and fixing all of your plugins which can be a problem if you didn't create the plugins yourself.

is posible.

  1. Compile your plug-in how Library [1]: https://angular.io/guide/creating-libraries
  2. Create a Component or Directive add to constructor

  constructor(private router: Router,
    private _injector: Injector,
    ...
    ) { ... }

  1. Load plugIn

    @AngularCore.ViewChild('customContent', { read: ToolDirective, static: true })
  customContent: ToolDirective;

  loadTool(_Library: string) {
    this.selectedNode = node;
    this.opened = false;

    const tool = Tools[node.tipo];
    const library: string = tool.library;

    fetch(library)
      .then(response => response.text())
      .then(source => {
        const exports = {}; // this will hold module exports
        const modules = {   // this is the list of modules accessible by plugin
          '@angular/core': AngularCore,
          '@angular/common': AngularCommon,
        };

        const require = (module) => modules[module]; // shim 'require'

        //source = source.replace(/\/\/# sourceMappingURL=.*/g, `//# sourceMappingURL=${library}.map`)
        eval(source); // interpret the plugin source
        let _module = _library

        const mwcf = this.compiler.compileModuleAndAllComponentsSync(exports[`${_module}Module`]);

        var __module = mwcf.ngModuleFactory.create(this._injector);

        const componentFactory = mwcf.componentFactories
          .find(e => e.selector === `lib-${_library}`); // find the entry component
        if (componentFactory) {
          this.openTool = true;
          this.customContent.viewContainerRef.clear();

          const componentRef = this.customContent.viewContainerRef.createComponent(componentFactory);
          componentRef.instance.data = { ...CustomInitialData.. };
          componentRef.instance.mwcf = mwcf;

        }
          


      });
  }

Write me for any help

Related