The way we add dependencies in NestJS is by using the #Injectable annotation:
export class ConversationService {
constructor(
#Injectable() private userService: UserService,
#Injectable() private cacheService: CacheService,
#Injectable() private emailService: EmailService,
) {}
}
Do you know if there is a way to add dependencies like an interface of injectables? I couldn't find anything like that in the docs:
interface ConversationServiceDependencies {
userService: UserService;
cacheService: CacheService;
emailService: EmailService;
}
export class ConversationService {
constructor(
#Injectable() private dependencies: ConversationServiceDependencies,
) {}
}
Thank y'all!
the #Injectable() should be in the class. And you'll have to define some token in #Inject(), like so:
#Injectable() // not needed in this case
export class ConversationService {
constructor(
#Inject('ConversationServiceDependencies') private dependencies: ConversationServiceDependencies,
) {}
}
because interfaces didn't exists at runtime (a typescript thing).
and then you'll need to define what's the value of that provider at your nestjs module. This is covered in the docs btw.
Or you could use this lib: nestjs-injectable
Related
Im struggling with the following issue and I cant find a solution. I have multi module project. I set up all the modules and dependencies but im still getting this error for firestore: error: [Dagger/MissingBinding] com.google.firebase.firestore
My DI setup is following
FirebaseDiModule(part of the firestore module)
#Module
#InstallIn(SingletonComponent::class)
object FirebaseDiModule {
#Singleton
#Provides
fun provideFirebaseAuth(): FirebaseAuth {
return FirebaseAuth.getInstance()
}
#Singleton
#Provides
fun provideFirestoreFirebase(): FirebaseFirestore {
return FirebaseFirestore.getInstance()
}
#Singleton
#Provides
fun provideUserFirebaseDataSource(
firebaseFirestore: FirebaseFirestore,
firebaseAuth: FirebaseAuth,
): UserFirestoreDataSource {
return UserFirestoreDataSource(
firebaseFirestore = firebaseFirestore,
firebaseAuth = firebaseAuth,
)
}
}
UserFirestoreDataSource (part of the firebase module)
class UserFirestoreDataSource #Inject constructor(
private val firebaseFirestore: FirebaseFirestore,
private val firebaseAuth: FirebaseAuth,
)
Then I have authentication module which is beasicly a feature module contaning jetpack compose and viewmodel's.
The ViewModel i use is set as this:
#HiltViewModel
class OnBoardingViewModel #Inject constructor(
userFirestoreDataSource: UserFirestoreDataSource,
) : ViewModel() {
The authentication module is added to the app module where I use the OnBoardingViewModel and composables.
In the app module I have this:
#HiltAndroidApp
class AppController : Application()
class MainActivity : ComponentActivity()
When I run the project I get the following error:
error: [Dagger/MissingBinding] com.google.firebase.firestore.FirebaseFirestore cannot be provided without an #Inject constructor or an #Provides-annotated method.
public abstract static class SingletonC implements AppController_GeneratedInjector,
But this error only occurs when I add #HiltViewModel annotation to the OnBoardingViewModel. Im really frustrated by this problem and I cant figure it out what am I doing wrong.
There is nothing to try because it's a DI setup.
You need to create a #HiltViewModel class and annotate it with the #HiltViewModel annotation.
here is an example code:
#HiltViewModel
class OnBoardingViewModel #ViewModelInject constructor(
userFirestoreDataSource: UserFirestoreDataSource
) : ViewModel() {
//ViewModel code
}
it's like telling DI (#ViewModelInject) hey this is a ViewModel class inject the necessary dependencies into the ViewModel
Is there a way better than below to inject a service or component inside an imported module?
export interface AmqpInterceptor{
after(message:any):Promise<void>;
}
export class AmqpInterceptors extends Array<AmqpInterceptor>{
}
//generic library module
#Module({
providers:[{
provide: AmqpInterceptors,
useValue: []
}]
}
export class AMQPModule implements OnModuleInit{
static register(options: AMQPOptions): DynamicModule {
const providers = options.providers || []
return {
module: AMQPModule,
providers: [
...providers,
OtherProvider
]
}
}
}
//end user module
#Module({
imports: [
AMQPModule.register(({
// I had to create a factory method to pass providers as an argument.
// I would think that it is not a good practice
providers: [{
provide:AmqpInterceptors,
useValue:[MyCustomInterceptor]
}]
})
],
providers: [
]
})
export class QueueModule {
}
Current working solution: I declare a default empty array in the generic module and a factory method that allows to pass custom value in module construction.(In my happiest world I declare multiple instances of an interface and then, DI collects all of these, but I thinks this is really impossible in NestJs)
you can use a package like #golevelup/nestjs-discovery to help you with.
Basically you've to do the following:
// AMQP Module
// amqp-interceptor.decorator.ts
import { SetMetadata } from '#nestjs/common';
export function AmqpInterceptor() {
return SetMetadata('AMQP_INTERCEPTOR', true)
}
// amqp-explorer.ts
import { OnModuleInit } from '#nestjs/common'
#Injectable()
export class AmqpExplorer implements OnModuleInit {
constructor(
private readonly discoveryService: DiscoveryService,
) {}
async onModuleInit(): Promise<void> {
const amqpInterceptorProviders = await this.discoveryService.providers('AMQP_INTERCEPTOR')
// you can store this list, to be queried by some
// other provider to use interceptors, etc.
}
}
// amqp.module.ts
#Module({ providers:[AmqpExplorer]})
export class AMQPModule { }
// end user module
// end-user.module.ts
#Module({ imports:[AMQPModule], providers: [SomeAmqpInterceptor] })
export class EndUserModule { }
// some-amqp.interceptor.ts
#Injectable()
#AmqpInterceptor()
export class SomeAmqpInterceptor {
run(): void { console.log('intercepting') }
}
Obs:
Interceptor decorator: you can add any params which would help you to improve your api
I suggest this (list of providers) instead of injecting decorated providers into your module, because I don't know how do you plan to use them. This way, you've a list of providers and can invoke each one.
AMQP_INTERCEPTOR string can collide with some other metadata, it's a good practice add something more specific to avoid two metadata with same one in a module
To invoke the interceptors later:
import { ExternalContextCreator } from '#nestjs/core/helpers/external-context-creator'
export class Runner {
constructor(
private readonly handler: DiscoveredMethodWithMeta,
private readonly externalContextCreator: ExternalContextCreator,
) { }
run(): void {
const handler = this.externalContextCreator.create(
this.handler.discoveredMethod.parentClass.instance,
this.handler.discoveredMethod.handler,
this.handler.discoveredMethod.methodName,
)
const handlerResult = await handler(content)
}
Didn't run local, but I think this is a good way to start
I'm attempting to create a custom ViewResolver class (extending angular's built-in class) to augment my systems' style metadata with a custom service (I'm loading my styles from external systems). However I'm running into a problem with the dependency injection system and ViewResolver.
I have my system setup something like the following:
Boot.ts:
bootstrap(App, [
MyStyleService, // my custom service
SomeOtherService, // another custom service used by MyStyleService
{
provide: ViewResolver,
useClass: MyViewResolver // my custom ViewResolver
}
])
MyViewResolver.ts:
#Injectable()
export class MyViewResolover extends ViewResolver {
constructor(
private _reflector: ReflectorReader,
// I want to reference 'StyleService' from the providers array in boot.ts
private _styleService: StyleService
) {
super(_reflector);
}
public resolve(comopnent: Type) {
let meta: ViewMetadata = super.resolve(component);
let styles = this._styleService.someMethod(meta);
}
}
However inside MyViewResolver, this._styleService has NOT been injected and is currently undefined. It should be noted that MyStyleService also depends on another injected service SomeOtherService, so I need to make sure that that provider is also defined and available for the injector.
I want all of these services to be "provided" by the bootstrap, so that in the future I can provide alternate versions of any of my services on a per-system basis.
For reference this is angular's core ViewResolver:
view_resolver.ts (Angular2 core):
import {Injectable, ViewMetadata, ComponentMetadata,} from '#angular/core';
import {ReflectorReader, reflector} from '../core_private';
import {Type, stringify, isBlank, isPresent} from '../src/facade/lang';
import {BaseException} from '../src/facade/exceptions';
import {Map} from '../src/facade/collection';
#Injectable()
export class ViewResolver {
constructor(private _reflector: ReflectorReader = reflector) {}
resolve(component: Type): ViewMetadata {
... stuff here ...
}
}
You could try to configure your class with useFactory:
bootstrap(App, [
MyStyleService,
SomeOtherService,
{
provide: ViewResolver,
useFactory: (_reflector: ReflectorReader, styleService: StyleService) => {
return MyViewResolver(_reflector, _styleService);
},
deps: [ ReflectorReader, StyleService ]
}
]);
Using Angular2 RC1
Suppose I have two custom services: for example a ConfigService and an AuthService.
And also assume that AuthService needs data from the ConfigService.
ConfigService is like that
import { Injectable } from '#angular/core';
#Injectable()
export class ConfigService {
public baseURL: string;
constructor() {
this.baseURL = 'http://localhost:8080';
}
}
My ConfigService has to be a singleton, so I've declare it in my bootstrap provider array:
bootstrap(MyWayAppComponent, [
HTTP_PROVIDERS,
...
ConfigService
]);
No problem when I try to inject my ConfigService in any Component, I always fetch the same instance.
But now I want to inject this ConfigService in my AuthService (AuthService is not a singleton and is provided in my auth component)
I inject the ConfigService in my AuthService as followed :
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import { ConfigService } from '../services/ConfigService';
#Injectable()
export class AuthService {
constructor(private http: Http, private configService: ConfigService) {
}
}
And response is :
EXCEPTION: Error: Uncaught (in promise): EXCEPTION: Error in :0:0
ORIGINAL EXCEPTION: No provider for ConfigService!
ORIGINAL STACKTRACE:
Error: DI Exception
at NoProviderError.BaseException [as constructor] (http://localhost:4200/vendor/#angular/core/src/facade/exceptions.js:17:23)
at NoProviderError.AbstractProviderError [as constructor] (http://localhost:4200/vendor/#angular/core/src/di/reflective_exceptions.js:39:16)
at new NoProviderError (http://localhost:4200/vendor/#angular/core/src/di/reflective_exceptions.js:75:16)
at ReflectiveInjector_._throwOrNull (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:776:19)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:804:25)
at ReflectiveInjector_._getByKey (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:767:25)
at ReflectiveInjector_.get (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:576:21)
at ElementInjector.get (http://localhost:4200/vendor/#angular/core/src/linker/element_injector.js:23:48)
at ElementInjector.get (http://localhost:4200/vendor/#angular/core/src/linker/element_injector.js:23:48)
at ReflectiveInjector_._getByKeyDefault (http://localhost:4200/vendor/#angular/core/src/di/reflective_injector.js:801:24)
ERROR CONTEXT:
[object Object]
I've carefully read the excellent Pascal Precht article but I don' see where I'm wrong... http://blog.thoughtram.io/angular/2015/09/17/resolve-service-dependencies-in-angular-2.html
Note : If I my AuthService I try to inject a "regular" service (like http) I have no pb... So I should forget something in my service declaration but what ???
Providers can only be defined when bootstrapping your application or at the level of components.
So you can define the ConfigService at this level.
See this question for more details:
What's the best way to inject one service into another in angular 2 (Beta)?
Regarding the AuthService, I don't know how you defined its providers but if you leverage useFactory for this, you need to explicitly define its dependencies (included the ConfigService). Here is à sample:
provide(AuthService, {
useFactory: (config) => {
return new...
},
depend: [ ConfigService ]
});
In my app I use DI to pass information about the UserLogged around the different Components that need such info.
This means that I have a main.ts class like this
import {AppComponent} from './app.component';
import {UserLogged} from './userLogged'
bootstrap(AppComponent, [UserLogged]);
and the components that need to use the instance of UserLogged have a constructor like this
constructor(private _user: UserLogged)
Now I would like to use the same instance of UserLogged also in simple TypeScript classes (which are not #Component). Is this possible? In other words, can I get hold of the same instance of UserLogged injected by DI also if I am outside a #Component?
This constructor also works for services (other classes created by DI)
bootstrap(AppComponent, [OtherClass, UserLoggged]);
#Injectable()
export class UserLogged {
log(text) {
console.log(text);
}
}
#Injectable()
export class OtherClass {
constructor(private _user: UserLogged) {}
}
class SomeComponent {
constructor(private otherClass:OtherClass) {
this.otherClass._user.log('xxx');
}
}
If you create these classes using new SomeClass() then you can inject it like
class SomeComponent {
constructor(private _injector:Injector) {
let userLog = this._injector.get(UserLogged);
new SomeClass(userLog);
}
}
In the file where you bootstrap angular:
import { AppComponent } from './app.component';
import { UserLogged } from './userLogged';
declare global {
var injector: Injector;
}
bootstrap(AppComponent, [UserLogged]).then((appRef) => {
injector = appRef.injector;
});
In your other file:
import { UserLogged } from '../path/to/userLogged';
class TestClass {
private userLogged: UserLogged;
constructor() {
this.userLogged = injector.get(UserLogged);
}
}
To be able to use dependency injection in classes you need to have a decorator, the #Injectable one.
#Injectable()
export class SomeClass {
constructor(private dep:SomeDependency) {
}
}
The name Injectable isn't really self-explanatory. It's to make possible the injection within the class (and not into another class).
The provider for the SomeDependency class need to be visible from the component that initiates the call.
See this question for more details about dependency injection and hierarchical injectors:
What's the best way to inject one service into another in angular 2 (Beta)?
Im taking a wild guess here, but do you mean to say you want to Inject the class with having to use providers : [UserLogged] ?
If that is the case, this will work
providers: [ provide(UserLogged, {useClass: UserLogged} ]
add the above to your bootstrap and you are good to go when you 'do not want to use #Component'
sample.ts
export class Sample{
constructor(private ulog : UserLogged){}
}
In your case the bootstrap would be :
import {provide} from 'angular2/core';
import {HTTP_PROVIDERS} from 'angular2/http';
bootstrap(AppComponent,[HTTP_PROVIDERS,provide(UserLogged, { useClass : UserLogged})]);
Ive added the HTTP_PROVIDERS to demonstrate how to add multiple providers.
Cheers!