简体   繁体   中英

Get injector from module in angular 8

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:

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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM