12

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.

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
jano42
  • 193
  • 1
  • 1
  • 8

1 Answers1

26

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

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • your approach is very interesting, is that possible using this way to import a remotely hosted component/module ? – Seb Sep 20 '17 at 09:31
  • 1
    @Seb, yes, but you need to use SystemJS for that – Max Koretskyi Sep 20 '17 at 09:45
  • great that could do the trick for me ! have you any example of how to integrate systemJs and webpack ? And an example of a System.import of a remote component ? – Seb Sep 20 '17 at 09:47
  • do you use angular-cli? – Max Koretskyi Sep 20 '17 at 10:30
  • Yes I do, should i use something else ? – Seb Sep 20 '17 at 10:43
  • @Seb, create a new question and post a question here, I'll take a look later – Max Koretskyi Sep 20 '17 at 10:45
  • Thanks @AngularInDepth.com, here it is : https://stackoverflow.com/questions/46321477/get-and-load-a-remote-component-module-in-an-angular-4-4-app – Seb Sep 20 '17 at 12:00
  • It's not possible to combine SystemJs with Angular-Cli. Am I wrong? @AngularInDepth.com – Dani Andújar Nov 14 '17 at 15:58
  • You can not load an umd library created by rollup and loaded as a script because it is not able to find its own angular classes @AngularInDepth.com – Dani Andújar Nov 14 '17 at 15:59
  • @user3757628, it depends on how you bundle everything together. If you bundle in UMD bundle, SystemJS will pick it up – Max Koretskyi Nov 14 '17 at 16:21
  • @AngularInDepth.com the only problem is that angular is not able to solve the path where the library is, for example @angular/core – Dani Andújar Nov 15 '17 at 06:51
  • @AngularInDepth.com hello again, I have created a repository with advances in this topic, but I have an error that I am unable to solve. Could you help me? https://github.com/xino1010/angular-external-modules – Dani Andújar Nov 15 '17 at 12:11
  • @user3757628, can you maybe create a separate question with details? – Max Koretskyi Nov 15 '17 at 17:27
  • of course @AngularInDepth.com https://stackoverflow.com/questions/47312031/error-importing-external-angular-module-at-runtime – Dani Andújar Nov 15 '17 at 20:18
  • @AngularInDepth.com you use CLI to package it as UMD too ? or are you not doing that? and if not how would you handle the vendor, inline and main bundle files? Is there any progress on this already? – Lars Meijdam Apr 26 '18 at 07:01
  • _Then, each plugin should be packaged as a module and all exported components should be added to the entryComponents of that module._ - Is this as UMD? @AngularInDepth.com – Lars Meijdam Apr 26 '18 at 07:03
  • Btw nice solution, but AComponent is not 'known' yet so how can you then make a hard reference to its class name? Thanks – Lars Meijdam Apr 26 '18 at 07:48
  • @LarsMeijdam, I'm not using CLI. But the newest CLI should have library support. I haven't looked at it yet. Yes, `AComponent` is not known, so the components should be defined as providers and later retrieved from the injector. Watch this talk about this approach https://www.youtube.com/watch?v=pERhnBBae2k – Max Koretskyi Apr 26 '18 at 11:23
  • Thanks! Already in my watch later! clear, I actually noticed while building the app for production it is still making chunk files with the use of the angular cli. I'm trying to extend routing with loadChildren and then system.import, but that is creating chunk files (which i dont want), now I try using http.get to fetch the umd bundle and add it dynamically, but loadChildren is not accepting that :( – Lars Meijdam Apr 26 '18 at 13:45
  • @AngularInDepth.com Interesting talk done by you! nice to see you put a lot of effort in reverse engineering this loading mechanism. I was wondering if you faced challenges to inject extension modules during runtime (by extending the route with a new loadChildren) after adding the .js files to the index.html (in runtime) ? – Lars Meijdam May 02 '18 at 06:25
  • The reason why I ask is because the company i now work at creates 'shippable' software which runs on premise, they want to extend periodically but don't want to 'recompile' the entire application on premise – Lars Meijdam May 02 '18 at 06:27
  • @LarsMeijdam, to inject a new route you need to use `resetRoute` API. I hope I'll find time to write an article some time in the future. – Max Koretskyi May 02 '18 at 12:52
  • @AngularInDepth.com Thanks for your response! I've tried and use that indeed! and its working, only thing is while building the application is creating chunk files for every route mentioned in the loadchildren of resetConfig(), which is something I dont want, because it might be a module which is not 'there' yet, I've found https://stackoverflow.com/questions/48590455/angular-5-load-modules-that-are-not-known-at-compile-time-dynamically-at-run in the meantime to try out! Thanks! – Lars Meijdam May 02 '18 at 12:56
  • 1
    I created a repository on github with a solution which might help. It uses Angular 6 libraries and 1 base applications which load up the UMD bundled libraries lazily; https://github.com/lmeijdam/angular-umd-dynamic-example If you have any suggestions, please feel free to add! PS: it's far from perfect, but it is at least loading 'unknown' libraries during run-time. Need to look for an 'eval' alternative – Lars Meijdam May 09 '18 at 06:53
  • @MaximKoretskyi looking at angular 8 it looks like NgModuleFactoryLoader was deprecated and replaced with a dynamic "import" function. IE import('src/app/somemodule/some.module').then(..). Any idea if "import" has support for loading modules from external servers? – lostintranslation Jan 08 '20 at 01:12