2

I'm trying to dynamically import modules in Angular 14 where the modulepaths are set at runtime, but get the following error:

Error: Cannot find module 'src/app/plugin1/plugin1.module'

Github repro

It works when I manually type the import path:

let module = await import("src/app/plugin1/plugin1.module").then(m => (m as any)[Object.keys(m)[0]]);

But not for dynamic values:

@Input() path: string;
...
let module = await import(this.path).then(m => (m as any)[Object.keys(m)[0]]);

The plugin module is included in the tsconfig, tsconfig.app.json:

{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "outDir": "./out-tsc/app",
    "types": [],
    "module": "esnext"
  },
  "files": [
    "src/main.ts",
    "src/polyfills.ts"
  ],
  "include": [
    "src/**/*.d.ts",
    "src/**/*.ts",
    "src/app/plugin1/plugin1.module"
  ],
  "exclude": [
    "src/**/*.spec.ts",
    "src/test.ts",
    "src/environments/*"
  ]
}

tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2020",
    "module": "es2020",
    "lib": [
      "es2020",
      "dom"
    ]
  },
  "angularCompilerOptions": {
    "enableI18nLegacyMessageIdFormat": false,
    "strictInjectionParameters": true,
    "strictInputAccessModifiers": true,
    "strictTemplates": true
  }
}

I'm trying to achieve what we previously used @herodevs/hero-loader for, which doesn't work with ng14.


Update

Created this monstrosity which made it work. Since the strings has to be explicitly typed out, they cannot be loaded through a for-loop. The entries are not added to any tsconfig-files.

Any other solutions are still appreciated.

@Injectable({ providedIn: "root" })
export class ModuleLoaderService {

    preloadedModulesByPath: { [path: string]: any };
    isModulesPreLoaded = false;

    async preloadLazyModules() {
        let map = {};
        map["path/to/module1.module"] = await import("path/to/module1.module");
        map["path/to/module2.module"] = await import("path/to/module2.module");
        map["path/to/module3.module"] = await import("path/to/module3.module");
        map["path/to/module4.module"] = await import("path/to/module4.module");
        this.preloadedModulesByPath = map;
        this.isModulesPreLoaded = true;
    }
}
import { Component, ComponentRef, createNgModule, Injector, Input, ViewContainerRef } from "@angular/core";
import { ModuleLoaderService } from "../services/module-loader.service";

@Component({
    selector: 'hero-loader',
    template: "",
})
export class HeroLoaderTempComponent {

    // In format path/to/feature.module#FeatureModule
    @Input() moduleName: string;

    _componentRef: ComponentRef<unknown>;

    constructor(
        private _injector: Injector,
        private _viewRef: ViewContainerRef,
        private moduleLoader: ModuleLoaderService
    ) { }

    async ngOnInit() {
        if (!this.moduleName) {
            return;
        }

        let path = this.moduleName.split("#")[0];
        let moduleName = this.moduleName.split("#")[1];
        let module = this.moduleLoader.preloadedModulesByPath[path];
        module = module[Object.keys(module)[0]];

        try {
            let moduleRef = createNgModule(module, this._injector);
            const component = (moduleRef as any)._bootstrapComponents[0];
            this._componentRef = this._viewRef.createComponent(component, { injector: this._injector, ngModuleRef: moduleRef })
        } catch (e) {
            console.error("Err when loading dynamic module: " + moduleName, e);
        }
    }
}

baltzar
  • 446
  • 4
  • 8

0 Answers0