5

We currently have a problem where we want to create a module dynamically (moduleFactory.create(this.injector)). But the injector of the dynamically created module has not the providers from the parent injector. We wish to implement some core stuff in our integration/main app (where the submodule is created), put it into the root injector and want to get it in our submodule and components via DI (through the injector, which is created with the main app's injector as the parent).

Our implementation based on this example: https://stackblitz.com/edit/angular-ifs7sp?file=src%2Fapp%2Flazy-loader.service.ts

const subsystems = await this.fetchSubsystems()

const promises = subsystems
  .map(async (subsystem) => await subsystem.load())

const subsystemModuleFactories = await Promise.all(promisses)

subsystemModuleFactories.map(async (ngModuleOrNgModuleFactory) => {
  let moduleFactory: NgModuleFactory<any>
  if (ngModuleOrNgModuleFactory instanceof NgModuleFactory) {
    moduleFactory = ngModuleOrNgModuleFactory
  } else {
    const compiler = this.injector.get(Compiler)
    moduleFactory = await compiler.compileModuleAsync(ngModuleOrNgModuleFactory)
  }

  const entryComponent = (<any>moduleFactory.moduleType).entry
  const moduleRef = moduleFactory.create(this.injector) // create the module ref with the injector of the main app as parent of the subsystem module
  // Here we can still get the instance console.log(moduleRef.injector.get(I18n))
  const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent)

  container.createComponent(compFactory)
})

The fetchSubsystems in short is using SystemJS to import the single bundle file and returns the exported AppModule:

SystemJS.import('subsystem') // mapped to an actual URL
    .then((m) => (m as any).AppModule)

And in the subsystem we want to use the service:

export class AppComponent {
  public constructor (@Inject(I18n) private i18n: II18n) {
  }
}

The I18n is the InjectionToken and the II18n defines the interface of the service:

import { InjectionToken } from '@angular/core'

export interface II18n {
  tr(key: string): string
}

export const I18n = new InjectionToken<II18n>('I18n')

Unfortunately, we got an error that there are no such InjectionToken in the injector:

ERROR Error: Uncaught (in promise): NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> InjectionToken I18n]: 
  StaticInjectorError(Platform: core)[AppComponent -> InjectionToken I18n]: 
    NullInjectorError: No provider for InjectionToken I18n!
NullInjectorError: StaticInjectorError(AppModule)[AppComponent -> InjectionToken I18n]: 
  StaticInjectorError(Platform: core)[AppComponent -> InjectionToken I18n]: 
    NullInjectorError: No provider for InjectionToken I18n!
    at NullInjector.get (core.js:778)
    at resolveToken (core.js:2564)
    at tryResolveToken (core.js:2490)
    at StaticInjector.get (core.js:2353)
    at resolveToken (core.js:2564)
    at tryResolveToken (core.js:2490)
    at StaticInjector.get (core.js:2353)
    at resolveNgModuleDep (core.js:26403)
    at NgModuleRef_.get (core.js:27491)
    at resolveNgModuleDep (core.js:26403)
    at resolvePromise (zone-evergreen.js:797)
    ...

Some words to our intention (in case there is a better solution for this, which we haven't found yet): We want to have something like a "micro frontend." We want to have an integration or main app which provides the core application and the base layout of the app. And we have multiple "subsystems," small apps which provide the GUI for specific business logic (yes, a kind of a classic microservice architecture). So according to this, the GUI is already built when the subsystem was built. The subsystem delivers the GUI as a single bundle which the integration app should load and mount it into the app.

Braincompiler
  • 186
  • 1
  • 8
  • Same problem here – Lucas Brogni Feb 11 '20 at 11:24
  • Are all micro front-ends in angular, or do you expect to have some apps that are in React/View/Vanilla-js+HTML – Християн Христов Mar 25 '21 at 13:16
  • Yes, all front-ends are written in angular. – Braincompiler Mar 26 '21 at 14:29
  • 2
    Can you please reproduce it in a simple github repository? – yurzui Mar 26 '21 at 16:37
  • 2
    Do you have a specific reason you want to also compile this modules dynamically like in the sample you base on? If you use Angular version 9 or greater it supports lazy loading of modules and also components without the need of compiling them at runtime. – Aleš Doganoc Mar 27 '21 at 20:34
  • @AlesD in the code example, the @angular/core is set to v8 so I assume he's sticked to that version of the framework. Will never understand why projects are not updated constantly, while the new releases are almost 100% backward compatible. – Andrea Alhena Mar 27 '21 at 21:19
  • Could you please tell me, which module the component is declared and the injection token is provided? – Ramanathan Chockalingam Mar 28 '21 at 23:40
  • When I see the error, did you set a value (or an instance) to your token I18n ? Since it is a custom injector you will need to set it as a provider in your main module: @ngModule({ ... providers: [ { provide: I18n, multi: true, useFactory: localeInitializer, deps: [LOCALE_ID] } ]}) – Quentin Fonck Mar 29 '21 at 13:20

0 Answers0