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.