Skip to content

Dynamically loading multiple modules to the same view. #16387

Closed

Description

@roby2001

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

What is the motivation / use case for changing the behavior?

Angular is a great framework for building modular apps. Therefore we have the use case to build a framework that can aggregate multiple modules, dynamically at run-time and in the same view.

Imagine an app such as a dashboard ( e.g. https://blackrockdigital.github.io/startbootstrap-sb-admin-2/pages/index.html ) where each item (app/bundle) from the side-menu is dynamically obtained from the backend.
We have a use case where we can have multiple apps such as those loaded in the same view. You can add 1, 2 or n apps to the same view, therefore, the number of router outlets/proxy components isn't known beforehand. Each app can also be a standalone app if bootstrapped by itself, so they might contain advanced routeing or any normal functionality of an Angular app.

We managed to achieve the use case above by extending the router a bit and playing around with named router outlets. However, we feel that this is risky and might be prone to lots of changes depending on how Angular itself evolves.

This is the "best" solution we found so far if anyone else is attempting this and has a better, smarter or whatever solution we would be really glad to hear about it!

It would be outstanding if Angular would offer some support for those type of apps out of the box. Are there any plans to implement this in the future or is this out of the scope of Angular?

Activity

  1. vicb commented on Apr 28, 2017

    @vicb
    Contributor

    Could you please re-formulate your request to be more generic ? I am not sure to understand exactly what you need.

  2. dherges commented on Apr 28, 2017

    @dherges
    Contributor

    @roby2001 There are ViewContainerRef APIs for child components and child views.

    Your writing is pretty confusing to me though. What exactly is your goal? What is your current code?

  3. onixie commented on Jun 20, 2017

    @onixie

    We have the same problem. Also I think we use a similar tricky method as @roby2001 does by modifying router config and by playing around with named router-outlet dynamically. But it is really cumbersome. We just want to load multiple components from another module into a component view without depending on router and lazy loading.

    @dherges By using ViewContainerRef and ComponentFactoryResolver sort of APIs, you can add components dynamically in a view. But the component must also be declared as a entryComponent in the same module. There are no simple methods to load a component from another module dynamically yet.

  4. dherges commented on Jun 20, 2017

    @dherges
    Contributor

    @onixie There are methods to load a component from another module dynamically. Their names are ViewContainerRef and ComponentFactoryResolver.

    Samples:

  5. mlc-mlapis commented on Jun 20, 2017

    @mlc-mlapis
    Contributor

    ... and also loading of any module in anytime (de facto lazy loading) by the same way as router does but without the router.

  6. dherges commented on Jun 20, 2017

    @dherges
    Contributor

    @mlc-mlapis This will be a great addition! 🇨🇿 🇪🇺

  7. onixie commented on Jun 27, 2017

    @onixie

    @dherges
    I knew ViewContainerRef and ComponentFactoryResolver. But I don't see any way to use them loading a component from another module (I mean another ngModule not imported or as a loadChildren in current ngModule).

    I read your gist and wonder how this resolveComponentFactory call can successfully create a ComponentFactory if the component is not declared or as an entry in the containing ngModule or as so from any imported ngModules.

    I would be grateful if you can show me a complete sample about loading component from any other module in anytime (de facto lazy loading) as @mlc-mlapis said?

  8. mlc-mlapis commented on Jun 27, 2017

    @mlc-mlapis
    Contributor

    The principle of loading any NgModule is following:

    • Injecting SystemJsNgModuleLoader through DI in the constructor as
    constructor(
         private _loader: SystemJsNgModuleLoader
    ) {}
    
    • Using the loader to load any module on-demand like and processing NgModuleFactory:
    this._loader.load('app/tasks/tasks.module#TasksModule')
    .then((_ngModuleFactory: NgModuleFactory<any>) => {
         ...
    }
    
    • The syntax is the same as loadChildren used by Angular router.

    Working demo of the same in a bit different form you can see here:
    https://github.com/Toxicable/module-loading.

    Relevant Angular source code:
    https://github.com/angular/angular/blob/master/packages/core/src/linker/system_js_ng_module_factory_loader.ts

  9. dherges commented on Jun 27, 2017

    @dherges
    Contributor

    @onixie the question is about lazy-loading a module or about dynamically creating a component?

    In the above posts, I was talking about dynamic component loading. Lazy loading afaik is only possible with the router for time being.

  10. onixie commented on Jun 29, 2017

    @onixie

    @mlc-mlapis Thank you for the demo.

    SystemJsNgModuleLoader was the way we used for a long period for dynamic module loading.
    But as angular-cli is becoming the de facto tool to create angular application, SystemJsNgModuleLoader might not be working under angular-cli since the webpack adoption. Moreover, I don't think SystemJsNgModuleLoader works well with AOT, do I?

    BTW, although angular-cli builds chunks for lazy-loading modules, it still lacks a way to build chunks for modules which intended to be used as lazy-loading or dynamic-loading (more precisely, the loadChildren route will not be pre-wired in its parent module route, but will be intended to be added dynamically by Router.resetConfig() (we are using such a method) or some sort of webpack module loader (which is a missing part).

  11. mlc-mlapis commented on Jun 29, 2017

    @mlc-mlapis
    Contributor

    Yes, that is true. This part of Angular has some missing points and it would be great if something new would be introduced in Angular 5. For AOT it is possible to go as https://github.com/Toxicable/module-loading/blob/master/static/src/app/dynamic-loader.component.ts. Another thing is how to create separate AOT modules to be able to load them later when you want.

  12. EricABC commented on Jul 12, 2017

    @EricABC

    I open-sourced @sharpangles/angular-dynamic from an enterprise app due to some folks asking for it. It solves alot of other compexities that arise in these scenarios, such as projecting state into dynamic loaded components, 3rd parties, dynamic loading scenarios outside of systemjs, etc...

  13. mlc-mlapis commented on Jul 12, 2017

    @mlc-mlapis
    Contributor

    @EricABC ... interesting ... I have to read it detaily. 👍

  14. webmutation commented on Nov 9, 2017

    @webmutation

    I looked at both @Toxicable and @EricABC solutions, I must be missing something because, with @Toxicable we still need to build the base application everytime we want to load a new version of the "dynamic" myplugin module...

    @EricABC I see no example code, just text that looks nice... but how exactly are you dynamically loading modules without requiring the base application to be either reloaded (webpack HMR) or compiled and served again with the updated chunks.

    I am currently looking for a solution to have modules deployed in different servers and (somehow) dynamically loaded (in a host application) whenever a new version is available...

    Currently the solution I have looked at require either the use of systemJS (so goodbey AOT and bigger size) or force the user to reload the application (new version has to be built and deployed). Perhaps someone has a more mature solution.

  15. EricABC commented on Nov 9, 2017

    @EricABC

    @webmutation The key to dynamic loading is to decouple the devops from the code. First, be sure you write code that can be truly dynamic. Sharpangles is the library I use to accomplish that. Here is a simple example:

    First, I use an interface to map from data sent over the wire.

    import { TypeReference } from '@sharpangles/angular-dynamic';
    
    export interface DynamicFrame<TState = any> {
        typeReference: TypeReference;
        state?: TState;
    }

    Next, here is a component that takes in a dynamicFrame.

    import { Component, Input } from '@angular/core';
    import { DynamicFrame } from '@abcoa/functional-ui-dynamic-frame';
    
    @Component({
        selector: 'abc-dynamic-frame',
        templateUrl: './dynamic-frame.component.html',
        styleUrls: ['./dynamic-frame.component.scss']
    })
    export class DynamicFrameComponent<TState = any> {
        @Input() dynamicFrame: DynamicFrame<TState> | undefined;
    }

    Finally, the template that uses both lazy loading and state projection. These could be combined into a single directive to simplify the syntax, but I tend to prefer separating the concern of lazy loading from state projection.

    <ng-template [saStateOutlet]="dynamicFrame?.state" [saLazyComponentOutlet]="dynamicFrame?.typeReference" #lazy="saLazyComponentOutlet" [saStateOutletComponentRef]="lazy.componentRef">
        <div>Loading dynamic component...</div>
    </ng-template>

    One thing to note about this code and sharpangles is that up to now no decision has been made on how the lazy loading is performed. Webpack, systemjs, or native module loading already appearing in chrome are all still possible with the example above.

    OK, now to make a decision on dynamic loading. There are really only 2 choices: static analysis and dynamic. Neither has anything to do with AOT, which is possible in both.


    Static analysis = webpack. Its designed to be able to know about every bit of code in your app before compiling. 'Lazy' loading in webpack is not dynamic at all, just delayed. This is one of the most confusing parts about it, but understand that chunks cannot be updated. This is because something in your new code might impact the minification algorithms used in a previous one. If you change any part, the entire package it rebuilt. This does not prevent you from deploying complex 'dynamic' scenarios by persisting builds produced for different targetted users, audiences, or versions. But if you are looking to have a dashboard with a dynamic tile in it where the developer just freshly minted the code and pushed a build out, then webpack does not support this. When you break down most systems, very few need this level of dynamic and are content with just versioning or tracking production minified build outputs and selecting the correct set of js files for the current user. The big advantage of using webpack is that it produces the most optimal build size and all the tooling requires it (ng-cli).

    Using sharpangles with webpack a little more than just dropping in StatefulModule.forRoot(). If you are using the router module, it already registers the NgModuleFactoryLoader to be the SystemJS one. Don't worry, webpack doesnt actually use systemJS, it knows how to replace all this. What you must do is make sure webpack will learn about ALL code in advance. Either you must reference the modules so they are not tree-shaken, or you must use the router. You don't have to actually use the router, but you must register routes like this:

        {
            path: 'dashboard',
            loadChildren: '../../modules/functional/ui/system-dashboard-angular/src/system-dashboard.module#SystemDashboardModule',
            component: SystemDashboardContainerComponent
        },
    

    Two things to note here: the string-based loadChildren will be 'learned' by ng-cli and webpack will create a lazy chunk for it. Second, the reference cannot use tsconfig paths (must use real relative paths) as ng-cli doesn't work with them yet (or at least as of version 1.4.*).


    Clearly, this isn't really dynamic. You have a reference to the module. Webpack required everything in advance to build. What is useful is that you can write future-proof code that will not change (except that router bit) with an alternate devops process.

    Lets say you do actually want TRUE dynamic. Then native module loading (for which you can think of systemJS as the polyfill) is what you must use. This does not prevent the use of AOT (just include the ngfactories in your build, typically using rollup so the ng dependencies get included), and buried in the other branches in sharpangles have been working examples of this approach. However, this is a MASSIVE project. It can be made to work with some simple examples, but when you start looking at production it becomes alot more. You have to think about things like dynamic client-side module loading configuration injected by dependent libraries, shared dependency reconciliation, minification with the ability to chain mappings between builds (no library does this yet), etc....


    To summarize, this just isn't realistic today, unless you can satisfy yourself with simple minification and module resolution scenarios. It really has nothing to do with angular which is why no simple angular solution has been presented across the numerous versions of this issue. It is really a devops problem. Sharpangles does have branches that had stages of a working prototype, but it is on hold for a bit because the ideas that evolved were basically a full-fledged 'actor-oriented architecture' devops system, where the script finally dies and we model developer behaviors directly. To do that to its fullest potential I will need to apply a new philosophy we devised that I am currently implementing in an enterprise system called behavior-oriented architecture (which solves the new problems introduced by actors). Unfortunately this is a problem for 2018.

  16. webmutation commented on Nov 14, 2017

    @webmutation

    @EricABC WOW, thanks for the super complete answer. You have given me a lot of material to digest.

  17. added this to the Backlog milestone on Jan 23, 2018
  18. rodrigoEclipsa commented on Apr 22, 2018

    @rodrigoEclipsa

    Hello everyone, the best way to load modules at run time without the "core" know anything about the module is using angular elements, if someone has any questions you can contact me and I will explain, I will make a demonstration when I have time

  19. jelbourn commented on Oct 9, 2020

    @jelbourn
    Contributor

    Tracking the main underlying concern here in #18093

  20. angular-automatic-lock-bot commented on Nov 9, 2020

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  21. locked and limited conversation to collaborators on Nov 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: routerfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions