The Setup:

I am on a project for a fairly large client. There are multiple teams, and they all depend on each other to varying degrees. This client requested an Angular application with features which would essentially be sub-applications. A proof-of-concept (POC) was spiked out a few months prior, which dynamically imported one Angular app into another. So my team and I were asked to get a base model of the production app running using the POC as a guideline.

Examining how it worked we realized some modifications would be needed to meet requirements. One of the newer architecture decisions was that the sub-apps would NOT be full-fledged Angular apps, because the parent app needed to communicate with the sub-apps. Also an old package, rollup, which had vulnerabilities and required the use of deprecated functionality in SystemJS, was the POC’s key to success. So we scrapped the POC. We found ourselves back at ground zero with three key requirements to meet, and with the aforementioned inter-team dependencies, a lot was riding on this.

The three key requirements were:

  1. The sub-apps must live in separate repositories
  2. Ensure only *enabled *sub-apps load into the user’s browser
  3. Use Ahead of Time (AOT) compiling

The Problem:

We were fairly certain the sub-apps would be Angular libraries, so our goal was to dynamically load external libraries. This was one of the hits on which we based many attempts and tried to modify for our own purposes. We made about 4 or 5 attempts at modifying examples like this one. At the end of each one, we hit a brick wall, trying to force Angular’s hand to do something it wasn’t designed for. Angular had to know about everything at compile time. Introducing unknown libraries at run-time was not allowed. This was clearly for security purposes. We eventually saw that lazy loading our library as a node module was in essence what we wanted. This seemed to satisfy the three requirements. And of course, what do we find when we Google “lazy-loading a library from node_modules”? https://github.com/angular/angular-cli/issues/6373. But the story doesn’t end there.

The Solution:

A team member found a work-ar ound buried in the thread of comments for the issue, and based his spike effort off of it. The approach is quite elegant. A wrapper module is required for every dynamic library, and each wrapper module is the only file referencing its respective external node-module library. Below is an example of a wrapper module: dynamic-cool-library-wrapper.module.ts.

import { NgModule } from "@angular/core"; import { CoolLibrary } from "cool-library"; @NgModule ({ imports: [ CoolLibrary, ] }) export class DynamicCoolLibraryWrapperModule;

The example in the work-around references the wrapper modules in the app.module.ts, but our app uses an app-routing.module.ts file. Below is how we referenced the wrapper modules in the app-routing.module.

const appRoutes: Routes = [ { path: 'coolLibrary', loadChildren: './dynamic-wrappers/dynamic-cool-library.module#DynamicCoolLibraryWrapperModule', } ];

Only when the application routes to the associated path will those wrapper components get loaded with their dependencies, because loadChildren indicates lazy loading to the compiler.

I strongly recommend this approach.