Reputation: 6007
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?
Upvotes: 6
Views: 17235
Reputation: 4612
Error: Can't resolve all parameters for RemoteDataService: (?)
The error says it all. Angular can't resolve all the constructor parameters in RemoteDataService. When this service is instantiated, it expects the required parameters.
You can provide the required dependency via InjectionToken, please see this answer for details.
But your service uses generics
and you did not mention how you're using this service in your components, so I'd suggest you declare the providers in your components (or module will work too) and use @Inject()
to inject different versions of this service in your component like show below (checkout this StackBlitz and see the console for a log from service constructor)-
import { Component, Inject } from "@angular/core";
import { RemoteDataService } from "./custom/remote-data.service";
export class A {}
export class B {}
@Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
providers: [
{ provide: "env", useValue: {} },
{
provide: "ARemoteDataService",
useFactory: () => new RemoteDataService<A>(new A())
},
{
provide: "BRemoteDataService",
useFactory: () => new RemoteDataService<B>(new B())
}
]
})
export class AppComponent {
constructor(
@Inject("ARemoteDataService")
private aRemoteDataService: RemoteDataService<A>,
@Inject("BRemoteDataService")
private bRemoteDataService: RemoteDataService<B>
) {}
}
Also, not sure if you can use @Inject()
outside constructor. But you can always use the injector to get your other dependencies (env
in your case) -
// @Inject('env') private environment: any = {};
constructor(model: T) {
this.environment = DataInjectorModule.InjectorInstance.get("env");
}
Upvotes: 6
Reputation: 6007
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
.
Upvotes: 2
Reputation: 12196
Angular DI is done in constructor
in most cases. As you've written constructor like this
constructor(
model: T,
)
Angular thinks that you are trying to inject T
. you should be injecting all the things you want in the constructor
constructor( @Inject('env') private environment) {}
but make sure that env is provided correctly. and even better use InjectionToken
s for that reason
Upvotes: 1