How to load dynamic external components into Angular application
I'm facing a problem with Angular application.
I would like to have an angular application written in Typscript build with (aot).
The aim is to display a user dashboard with some widgets. A widget is an angular component.
My app comes with some embedded widgets. But widgets should be extended by something like a market place; or created manually.
The market should download files (js/ts/bunlde..??) into a specific folder.
Then my app should be able to load the new widgets (= ng component) and instanciate them.
My folder structure (production)
|- index.html
|- main.f5b448e73f5a1f3f796e.bundle.js
|- main.f5b448e73f5a1f3f796e.bundle.js.map
|- .... (all other files generated)
|- externalWidgets
|- widgetA
|- widjetA.js
|- widjetA.js.map
|- assets
|- image.png
|- widgetB
|- widjetB.ts
|- widjetB.html
|- widjetB.css
Then when loading the user page, the database say that there is a widgetA. So the aim is to dynamically load files and instanciate included component.
I've tried many solutions, using "require" and "System.import" but both fails when the path to load is dynamically generated.
Is this should be possible ? I can change my code structure; change external widgets.. (for example widgetB is not yet transpiled,...)
In fact I'm looking for a "plugin system" with an Angular4/webpack application.
I'm doing exactly the same. And I explain the details in this talk at NgConf.
The first thing to understand is that Webpack cannot dynamically load modules that are unknown during build time. This is inherent to the way Webpack builds dependency tree and collects modules identifiers during build time. And it's perfectly fine since Webpack is a modules bundler, not modules loader. So you need to use a module loader and the only viable option now is SystemJS.
Then, each plugin should be packaged as a module and all exported components should be added to the entryComponents
of that module.
During runtime, you will load that module to get access to the components declared inside if it. You don't really need a module but that's a unit of packaging in Angular so you can't avoid using it. Now, once you get a module, you have to options depending on whether the module is built using AOT or not.
If it's built using AOT, you just get the exported factory class from the module and create a module instance:
System.import('path/to/module').then((module)=>{
const moduleFactory = module.moduleFactoryClassName;
const moduleRef = moduleFactory.create(this.parentInjector);
const resolver = moduleRef.componentFactoryResolver;
const compFactory = resolver.resolveComponentFactory(AComponent);
}
If it's not built using AOT, you have to compile it using JIT compiler:
System.import('path/to/module').then((module)=>{
const moduleFactory = this.compiler.compileModuleSync(module.moduleClassName);
const moduleRef = moduleFactory.create(this.parentInjector);
const resolver = moduleRef.componentFactoryResolver;
const compFactory = resolver.resolveComponentFactory(AComponent);
}
Then you can add dynamic components wherever you want using techniques described in this article: Here is what you need to know about dynamic components in Angular