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
Related
I am new to nest.js and I have a question.
I have a Roles Guard like this
import { CanActivate, ExecutionContext, Injectable } from '#nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '#nestjs/core';
#Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {
}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return user.role.some(role => !!roles.find(item => item === role));
}
}
Now I want to use this guard as a global guard like this
app.useGlobalGuards(new RolesGuard())
But it says that I need to pass argument(the reflector) to the guard as I mentioned in the constructor, now will it be okay to initialize the reflector like this?
const reflector:Reflector = new Reflector();
app.useGlobalGuards(new RolesGuard(reflector))
Or is there a better way to do this?
On the official Nest JS fundamentals course, in lecture "54 Protect Routes with Guards", the instructor specifies it is not best practice to create instance of reflector yourself.
A better way to resolve dependencies is to create a common module, and register your guard there. That way, reflector instance is resolved by nest runtime and you can also specify imports array for any other dependencies.
import { Module } from '#nestjs/common';
import { APP_GUARD } from '#nestjs/core';
import { AuthTokenGuard } from './guards/auth-token.guard';
import { ConfigModule } from '#nestjs/config';
#Module({
imports: [ConfigModule],
providers: [
{
provide: APP_GUARD,
useClass: AuthTokenGuard,
},
],
})
export class CommonModule {}
app.useGlobalGuards(new RolesGuard(new Reflector()));
It is working also. Could not find any better solution.
Although my answer might not add much value, I just want to re-iterate that is the intended way to get the reflector, this is a quote from NestJS's creator
kamilmysliwiec
When you create instance manually, you can create Reflector by
yourself:
new RoleGuard(new Reflector());
Source: https://github.com/nestjs/nest/issues/396#issuecomment-363111707
2023, NestJs 9, related problem:
In case you inject request-scoped dependency into the globally registered guard, the reflector will be undefined.
You can solve this issue by resolving such dependencies using ContextIdFactory and moduleRef.resolve() instead of injecting them normally:
const req = context.switchToHttp().getRequest();
const contextId = ContextIdFactory.getByRequest(req);
this.moduleRef.registerRequestByContextId(req, contextId);
this.authorizationService = await this.moduleRef.resolve(
RequestScopedService,
contextId
);
References:
https://docs.nestjs.com/fundamentals/module-ref
code example
https://discord.com/channels/520622812742811698/1060904277607985172
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 ]
}
]);
Is there any way to inject a service dependency into a #Component decoration, something like this?
#Component({
selector: injectedService.getPrefix() + 'my-component'
})
export class MyComponent { }
Or, if not, might it be possible to subsclass #Component and inject a dependency into the subclass to achieve a similar result?
update >= RC.5
#NgModule({
...
})
export class AppModule {
ngDoBootstrap(moduleRef) {
appInjector(moduleRef.injector);
}
}
appInjector implementation see below
original <= RC.5
This is not directly supported by Angular2. You can store the injector outside of your Angular app and then reference it from there like demonstrated as a workaround for the #CanActivate() decorator in https://github.com/angular/angular/issues/4112#issuecomment-153811572.
(Plunker example)
In main.ts the injector is assigned to appInjector
bootstrap(App, [
Auth,
HTTP_PROVIDERS,
ROUTER_PROVIDERS,
provide(LocationStrategy, {useClass: HashLocationStrategy})
]).then((appRef: ComponentRef) => {
// store a reference to the application injector
appInjector(appRef.injector);
});
app-injector.ts
let appInjectorRef: Injector;
export const appInjector = (injector?: Injector):Injector => {
if (injector) {
appInjectorRef = injector;
}
return appInjectorRef;
};
then you can get a reference to the injector like
appInjector()...
This won't work if the component is created before bootstrap() is completed.
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!
I have had mixed success with resolving issues with DI. I have read a few tutorials and I get the jist but after making some nested DIs with my custom services things have started to fall apart.
Could someone explain when to use useFactory instead of useClass? I have seen the ng2 docs and I have seen the examples but I cannot map them to my problems. Currently my bootstrapper looks like this:
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
provide(MsgService, { useClass: MsgService }),
provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
provide(AuthService, { useFactory: (HttpAdvanced) => new AuthService(HttpAdvanced), deps: [HttpAdvanced, HTTP_PROVIDERS, MsgService] }),
provide(FormBuilderAdvanced, { useFactory: (FormBuilder, HttpAdvanced) => new FormBuilderAdvanced(FormBuilder, HttpAdvanced), deps: [FormBuilder, HttpAdvanced] }),
provide(MsgServiceInternal, { useClass: MsgServiceInternal })
]
);
And my latest issue is:
EXCEPTION: Error during instantiation of AuthService! (HeaderBar -> AuthService).
ORIGINAL EXCEPTION: TypeError: this.http.get is not a function
My dependencies work like
HttpAdvanced -> Http(ng2), MsgService
MsgService -> MsgServiceInternal
AuthService -> HttpAdvanced
FormBuilderAdvanced -> FormBuilder(ng2), HttpAdvanced
1. Am I properly using provide / useClass / useFactory and how do I provide services that have other dependencies?
Also, I have at one place in my code:
static isUserInjector() {
return (next, prev) => Injector.resolveAndCreate([AuthService, provide(HttpAdvanced, { useClass: HttpAdvanced })]).get(AuthService).isUser();
}
Because I want to have a function that I provide to
#CanActivate(AuthService.isEditorInjector())
but I can't use constructor injection because #CanActivate is outside of the class scope so I can't inject the service inside the controller and then reference like #CanActivate(this.authService.isEditor())
2. What would be a good solution for this?
SOME CODE:
#Component({
selector: 'ShowStats',
templateUrl: './dest/views/showStats/showStats.html',
directives: [ COMMON_DIRECTIVES, UsersCount, AdminsList, GlobalWishlist, PopularTrack ]
})
export class ShowStats {
authService : AuthService;
constructor( authService : AuthService ){
this.authService = authService;
}
}
... next file ...
#Injectable()
export class HttpAdvanced {
msgService: MsgService;
http: Http;
constructor(msgService: MsgService, http: Http) {
this.msgService = msgService;
this.http = http;
}
/*
* This is for plain ol' GET requests .. with callback of course.
*/
public get(url, callback) {
return this.http.get(url).subscribe((res) => {
let data = res.json().data;
callback(data);
}, this.msgService.httpErrorHandler);
}
.... other code for HttpAdvanced
3.
Does the order of importing files matter for DI? I think i noticed that since I have MsgService and MsgServiceInternal in the same file and MsgService depends on Internal that I had to put Internal before but I'm not 100% Is it the same of order of importing ?
4.
So if I simply do:
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
MsgService,
HttpAdvanced,
AuthService,
FormBuilderAdvanced,
MsgServiceInternal
]
);
I get:
Cannot resolve all parameters for 'FormBuilderAdvanced'(?, ?).
Make sure that all the parameters are decorated with Inject or have
valid type annotations and that 'FormBuilderAdvanced' is decorated
with Injectable.
I used to remove this error with the useFactory but I'm confused now. Does this mean the deps aren't injected because it can't see them or what?
The Form class:
export class FormBuilderAdvanced {
http: HttpAdvanced;
fb: FormBuilder;
constructor(fb: FormBuilder, http: HttpAdvanced) {
this.fb = fb;
this.http = http;
}
create(controlNames: string[], submissionUrl: string, getter?: any) {
return new Form(this.fb, this.http, controlNames, submissionUrl, getter);
}
}
You question doesn't provide enough information to know for sure but this is probably enough
bootstrap(
App,
[
FORM_PROVIDERS,
ROUTER_PROVIDERS,
HTTP_PROVIDERS,
provide(LocationStrategy, { useClass: PathLocationStrategy }),
provide(RequestOptions, { useClass: DefaultRequestOptions }),
// provide(MsgService, { useClass: MsgService }),
MsgService, // is just fine when no special behavior is required
// provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
provide(Http, {useClass: HttpAdvanced});
AuthService,
provide(FormBuilder, { useClass: FormBuilderAdvanced}),
MsgServiceInternal)
]
);
If you want to make a class available for injection, just add the type to the providers list (provide(AuthService) and just AuthService do the same).
If you want inject a different class than the one requested, use useClass.
Example
#Injectable()
export class MyService {
constructor(private http: Http) {
}
}
If your providers contain
provide(Http, {useClass: HttpAdvanced});
then MyService (which has a dependency on Http) gets injected HttpAdvanced instead.
Ensure you have this line after HTTP_PROVIDERS to override the default Http provider, contained in HTTP_PROVIDERS.
If DI can't resolve dependencies by itself because they aren't just other providers, then use useFactory.
As mentioned above,
Ensure you have this line after HTTP_PROVIDERS to override the default Http provider, contained in HTTP_PROVIDERS.
the order in the list of providers does matter.
When the providers list contains multiple providers for a type then only the last one is used.
The order of the imports doesn't matter.
The order of dependent classes within one file does matter because classes are not hoisted.
Example
export class ClassA {
// constructor(private b: ClassB) {} // ClassB is unknown here
constructor(#Inject(forwardRef(() => DataService)) {
}
}
export class ClassB {
}
If the code of ClassB is above ClassA, forwardRef() isn't necessary.
See also Class is not injectable if it is defined right after a component with meta annotation
To complete a bit the Günter's great and clear answer, I would say that I would use useFactory to instantiate by myself classes associated with providers in the following use cases:
When you want to select the element to inject into the contructor of the element associated with the provider. For example, if you want to extend the HTTP class and provide explicitly a specific concrete class for parameters (here XHRBackend instead of ConnectionBackend). Here is a sample:
bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS,
new Provider(Http, {
useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions),
deps: [XHRBackend, RequestOptions]
})
]);
#Langley provides interesting hints in this question: Handling 401s globally with Angular.
Another cool usage of the useFactory is to instantiate a class from a third-party library into the context of Angular2 (Zones)
bootstrap(App, [
provide(Mousetrap, { useFactory: () => new Mousetrap() })
]);
#alexpods provides this very elegant solution in this question: View is not updated on change in Angular2.
Hope it helps you,
Thierry