Reputation: 167
Problem:
I am setting up lazy loading for non-routed module in angular. At version 7 I used NgModuleFactoryLoader
and it's function load
to lazy load module and get first entry point to the module (service in out case)
this.loader.load('path-to-module')
.then(factory => {
const module = factory.create(this._injector);
return module.injector.get(EntryService);
});
But in Angular 8 NgModuleFactoryLoader
is deprecated so instead I have to load module in that way:
import('path-to-module')
.then(m => m.MyModule)
.then(myModule => {
...
});
The problem here that I can not retrieve factory and get provider here in a new lazy loading (one of ideas of IVY - no factories).
What I have already tried:
First solution (work only with JIT compiler which is not suits us as I am using AOT compiler for prod)
import('path-to-module')
.then(m => m.MyModule)
.then(myModule => {
return this._compiler.compileModuleAsync(myModule)
.then(factory => {
const module = factory.create(this._injector);
return module.injector.get(EntryService);
});
});
Second solution (dirty and not fully checked. It is using ngInjectorDef
which is new feature of IVY and has no any described API yet):
import('path-to-module')
.then(m => m.MyModule)
.then(myModule => {
const providers = myModule['ngInjectorDef'].providers; // Array<Providers>
... find EntryService in providers
});
ngInjectorDef
- is a static module class property which is added by angular and has properties factory, providers and imports.
Sources:
No NgFactory file anymore
)Upvotes: 3
Views: 5881
Reputation:
I wouldn't say that accessing the ngInjectorDef
property is a "dirty hack". Yes, it is not documented anywhere because, as Igor Minar said, Ivy is opt-in preview in Angular 8. But a lot of private functions were already mentioned in some articles of Viktor Savkin, for example directiveInject
.
You should not search for your service in the providers
property. Imagine that there are 20+ providers and also the EntryService
name will be minified in the production to something like t
or k
.
If you use Ivy - there is a private function called createInjector
, that accepts module constructor as an argument.
@Injectable()
export class EntryService {
public logFromEntryService(): void {
console.log('Logging from EntryService...');
}
}
@NgModule({
providers: [EntryService]
})
export class EntryModule {
public static getEntryService: () => EntryService = null;
constructor(injector: Injector) {
EntryModule.getEntryService = () => injector.get(EntryService);
}
}
Assume you've got such code, let's use a dynamic import to load this EntryModule
:
import { ɵcreateInjector as createInjector } from '@angular/core';
export class AppComponent {
constructor(private injector: Injector) {
this.loadEntryModule();
}
private async loadEntryModule(): Promise<void> {
const { EntryModule } = await import('./entry.module');
createInjector(EntryModule, this.injector);
EntryModule.getEntryService().logFromEntryService();
}
}
createInjector
is used for instantiating EntryModule
, after instanting - the getEntryService
static method doesn't equal null
.
We could also expose the injector
property, like:
public static injector: Injector = null;
constructor(injector: Injector) {
EntryModule.injector = injector;
}
This might be treated as a service locator, which is kind of anti-pattern. But up to you!
Upvotes: 4