angularangular10injectable

"Can't resolve all parameters for service: (?)" when I try to use service from library in Angular 10


In my library I have a service with this code:

import { Inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DataInjectorModule } from '../../data-injector.module';

// @dynamic
@Injectable()
export class RemoteDataService<T> {
  @Inject('env') private environment: any = {};
  public type: new () => T;
  
  constructor(
    model: T,
  ) {
    this.http = DataInjectorModule.InjectorInstance.get(HttpClient);
  }

  // ...
}

The data-injector.module (existing reason is only avoid circular dependency):

import { NgModule, Injector } from '@angular/core';

// @dynamic
@NgModule({
  declarations: [],
  imports: [],
  providers: [],
  exports: [],
})
export class DataInjectorModule {
  static InjectorInstance: Injector;

  constructor(injector: Injector) {
    DataInjectorModule.InjectorInstance = injector;
  }

}

In my library's main module file:

import { ModuleWithProviders } from '@angular/compiler/src/core';
import { NgModule, Injector } from '@angular/core';
import { DataInjectorModule } from './data-injector.module';
import { RemoteDataService } from './services/remote-data/remote-data.service';

// @dynamic
@NgModule({
  declarations: [],
  imports: [
    DataInjectorModule,
  ],
  providers: [],
  exports: [],
})
export class DataCoreModule {
  static InjectorInstance: Injector;

  constructor(injector: Injector) {
    DataCoreModule.InjectorInstance = injector;
  }

  public static forRoot(environment: any): ModuleWithProviders {
    return {
      ngModule: DataCoreModule,
      providers: [
        RemoteDataService,
        { provide: 'env', useValue: environment }
      ]
    };
  }
}

Finally in my application's app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { DataCoreModule } from 'data-core';

import { AppRoutingModule } from './app-routing.module';
import { environment } from 'src/environments/environment';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    DdataCoreModule.forRoot(environment),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Build goes well, but in the browser I get this error:

Error: Can't resolve all parameters for RemoteDataService: (?).
    at getUndecoratedInjectableFactory (core.js:11338)
    at injectableDefOrInjectorDefFactory (core.js:11328)
    at providerToFactory (core.js:11371)
    at providerToRecord (core.js:11358)
    at R3Injector.processProvider (core.js:11256)
    at core.js:11230
    at core.js:1146
    at Array.forEach (<anonymous>)
    at deepForEach (core.js:1146)
    at R3Injector.processInjectorType (core.js:11230)

I checked several questions about this topic in StackOverflow, like this but almost everywhere just the @Injectable() was the missing part, but in this case I use this decorator.

Any idea how can I solve this issue?


Solution

  • I found a solution (actually a workaround). I think this isn't an elegant way, but it's wokring.

    I created an EnvService class what can pick up the environment parameter from the module, and doesn't have sideeffects with constructor attributes:

    import { Inject, Injectable } from '@angular/core';
    
    @Injectable({
      providedIn: 'root',
    })
    export class EnvService {
      public environment: any = {};
    
      constructor(@Inject('env') private env?: any) {
        this.environment = env ?? {};
      }
    }
    

    Then in my library's main module file I set up the EnvService instead of RemoteDataService:

    import { ModuleWithProviders } from '@angular/compiler/src/core';
    import { NgModule, Injector } from '@angular/core';
    import { DataInjectorModule } from './data-injector.module';
    import { EnvService } from './services/env/env.service';
    
    // @dynamic
    @NgModule({
      declarations: [],
      imports: [
        DataInjectorModule,
      ],
      providers: [],
      exports: [],
    })
    export class DataCoreModule {
      static InjectorInstance: Injector;
    
      constructor(injector: Injector) {
        DataCoreModule.InjectorInstance = injector;
      }
    
      public static forRoot(environment: any): ModuleWithProviders {
        return {
          ngModule: DataCoreModule,
          providers: [
            EnvService,
            { provide: 'env', useValue: environment }
          ]
        };
      }
    }
    

    Finally in my RemoteDataService changed the @Inject solution to an InjectorInstance.get(EnvService) solution:

    import { Injectable } from '@angular/core';
    import { HttpClient } from '@angular/common/http';
    import { DataInjectorModule } from '../../data-injector.module';
    import { EnvService } from '../env/env.service';
    
    // @dynamic
    @Injectable()
    export class RemoteDataService<T> {
      private env = DdataInjectorModule.InjectorInstance.get(EnvService);
      public type: new () => T;
      
      constructor(
        model: T,
      ) {
        this.http = DataInjectorModule.InjectorInstance.get(HttpClient);
      }
    
      // ...
    }
    

    So the RemoteDataService's constructor attributes are untouched, but the service can access to the environment variables over the EnvService.