In nest.js, is it possible to get service instance inside a param decorator?
It is not possible to inject a service into your custom decorator.
Instead, you can create an AuthGuard
that has access to your service. The guard can then add a property to the request
object, which you can then access with your custom decorator:
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const bearerToken = request.header.Authorization;
const user = await this.authService.authenticate(bearerToken);
request.principal = user;
// If you want to allow the request even if auth fails, always return true
return !!user;
}
}
import { createParamDecorator } from '@nestjs/common';
export const Principal = createParamDecorator((data: string, req) => {
return req.principal;
});
and then in your controller:
@Get()
@UseGuards(AuthGuard)
get(@Principal() principal: Principal) {
// ...
}
Note that nest offers some standard modules for authentication, see the docs.
NestJs - Calling a method from a service inside a Custom Decorator
Its unclear if you are using the NestJS cache-manager wrapper, if so you need to use the provider token when injecting and the type would be Cache.
export function ClearCache() {
const injector = Inject(CACHE_MANAGER)
return (target: any, _key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>) => {
injector(target, 'cacheManager')
const originalMethod = descriptor.value
descriptor.value = async function (...args: any[]) {
try {
const service: Cache = this.cacheManager
service.delete(args[1])
return await originalMethod.apply(this, args)
} catch (err) {
console.error(err)
return originalMethod(args)
}
}
}
}
If it is a different service, ensure that you are using the correct provider token, it is in the current modules scope and instantiated. If it isn't in current module, check it is global
Uploaded minimal repo of above code here.
Update:
tsconfig.json has "strict":true
. If it is enabled, then "this" would be strictly use the PropertyDescriptor interface.
Fixed the issue by changing "strict": false
in the tsconfig.json.
Use global nest module in decorator
Okay, found a solution. In case anyone else stumbles upon this. First please keep in mind how decorators work – they are class constructor based, not instance based.
In my case I wanted to have my logger service injected in the class instance. So the solution is to tell Nest in the decorator to inject the LoggerService into the instance of the class that contains the decorated method.
import { Inject } from '@nestjs/common';
import { LoggerService } from '../../logger/logger.service';
export function logErrorDecorator(bubble = true) {
const injectLogger = Inject(LoggerService);
return (target: any, propertyKey: string, propertyDescriptor: PropertyDescriptor) => {
injectLogger(target, 'logger'); // this is the same as using constructor(private readonly logger: LoggerService) in a class
//get original method
const originalMethod = propertyDescriptor.value;
//redefine descriptor value within own function block
propertyDescriptor.value = async function(...args: any[]) {
try {
return await originalMethod.apply(this, args);
} catch (error) {
const logger: LoggerService = this.logger;
logger.setContext(target.constructor.name);
logger.error(error.message, error.stack);
// rethrow error, so it can bubble up
if (bubble) {
throw error;
}
}
};
};
}
This gives the possibility to catch errors in a method, log them within the service context, and either re-throw them (so your controllers can handle user resp) or not. In my case I also had to implement some transaction-related logic here.
export class FoobarService implements OnModuleInit {
onModuleInit() {
this.test();
}
@logErrorDecorator()
test() {
throw new Error('Oh my');
}
}
How to integrate dependency injection with custom decorators
You should consider using an Interceptor
instead of a custom decorator as they run earlier in the Nest pipeline and support dependency injection by default.
However, because you want to both pass values (for cache timeout) as well as resolve dependencies you'll have to use the mixin
pattern.
import {
ExecutionContext,
Injectable,
mixin,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { TestService } from './test/test.service';
@Injectable()
export abstract class CacheInterceptor implements NestInterceptor {
protected abstract readonly cacheDuration: number;
constructor(private readonly testService: TestService) {}
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
// Whatever your logic needs to be
return call$;
}
}
export const makeCacheInterceptor = (cacheDuration: number) =>
mixin(
// tslint:disable-next-line:max-classes-per-file
class extends CacheInterceptor {
protected readonly cacheDuration = cacheDuration;
},
);
You would then be able to apply the Interceptor to your handler in a similar fashion:
@Injectable()
class UserService{
@UseInterceptors(makeCacheInterceptor(1000))
async getUser(id:string):Promise<User>{
// Make a call to db to get all Users
}
}
Access an injected service from within a Class decorator? Typescript / NestJS
Based on the official class-decorator docs, here's a way that should work:
function CustDec<T extends new(...args: any[]) => {}>(Target: T) {
return class extends Target {
constructor(...args: any[]) {
super(...args);
(args[0] as InjectedService).doSomething();
}
}
}
@CustDec
@Injectable()
class MyService {
constructor(
private readonly injectedService: InjectedService,
) {}
}
I've also created an example on TS-Playground.
Related Topics
Typescript Module Has No Exported Members - React
Textarea With Initial Auto Height by Content With Pure CSS
Howto Trigger Input Event Programmatically on Input Text Field
Javascript Validation: Block Special Characters
Ng2-Smart-Table Bind 'Add New' Button Event to an External Button
Android Webview Not Loading Website Properly
Counting Records in Json Array Using JavaScript and Postman
Charts.Js Graph Not Scaling to Canvas Size
Have a Div Move on Scroll Up and Down
Merge Array of Objects by Same Key-Value
Hide Elements If User Logged In
How to Convert from Google Sheet Date Number Value to JavaScript Date
Html2Canvas Generates Blurry Images
Detect If String Contains Any Spaces
How to Use Owl Carousel Vertically
How to Format Numbers by Prepending 0 to Single-Digit Numbers