Solvedangular Dynamically loading multiple modules to the same view.

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?

20 Answers

✔️Accepted Answer

@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.

Other Answers:

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

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...

More Issues: