I have a global interceptor but I need not apply in a controller specified.
app.useGlobalInterceptors(new ResponseInterceptor());
#Get()
async method(#Query() query) {
code here......
}
Does anyone know how to do it?
If the interceptor is global, it will be applied to everything. The only way around it is to apply some sort of custom metadata to the route that tells the interceptor to not run for this route. You'll need to add a check in the interceptor to look for that metadata. Something like this may be what you're looking for:
export function OgmaSkip() {
return (
target: any,
key?: string | symbol,
descriptor?: TypedPropertyDescriptor<any>,
) => {
if (descriptor) {
Reflect.defineMetadata(OGMA_INTERCEPTOR_SKIP, true, descriptor.value);
return descriptor;
}
Reflect.defineMetadata(OGMA_INTERCEPTOR_SKIP, true, target);
return target;
};
}
Where this decorator applies metadata to either the controller or the method, and then in the interceptor you can apply a check like this:
public shouldSkip(context: ExecutionContext): boolean {
const decoratorSkip =
this.reflector.get(OGMA_INTERCEPTOR_SKIP, context.getClass()) ||
this.reflector.get(OGMA_INTERCEPTOR_SKIP, context.getHandler());
if (decoratorSkip) {
return true;
}
}
I have more logic to this check in my actual codebase, but it should be a start for you.
You can bind interceptor
#UseInterceptors(ResponseInterceptor)
#Get()
async method(#Query() query) {
code here......
}
https://docs.nestjs.com/interceptors
hope will help
Related
I have a service that connects with api
export class ConsolidadoApi {
constructor(private http: HttpClient) { }
getInvestiments(search?: any): Observable<any> {
return this.http.get<any>(`${environment.basePosicaoConsolidada}`);
}
}
Response this api:
https://demo5095413.mockable.io/consolidado
This one is responsible for the logic before reaching the component
#Injectable({
providedIn: 'root'
})
export class CoreService {
public test;
constructor(private api: ConsolidadoApi, private state: StateService) { }
public createMenu() {
this.api.getInvestiments()
.subscribe(response => {
console.log(response.carteiras[0])
this.products = response.carteiras[0]
return this.products;
})
}
In my component
export class MenuComponent implements OnInit {
constructor( private coreService : CoreService ) {}
ngOnInit(): void {
console.log(this.coreService.createMenu())
}
}
But when createMenu is called in menu.component.ts it comes undefined.
The raw response is an object. forEach works only on an array. If you are aiming for forEach in 'categorias', you should try
this.test.categorias.forEach()
When you return Observable<any>, that means the argument of the lambda you create when you do subscribe (which you named response) is type any. This doesn't necessary have the function forEach defined (unless the API returns an object with that prototype). That's generally why using any is not good practice; you can't have any expectations on what the object can contain. In fact, it's possible that it's not on object (it could be an array since any is not exclusively an object). If you do want to use forEach, you will want to make sure that response is type array. You can inspect the object's type before using it (e.g. using typeof) and make a judgement on what to call or even just check if the function you're trying to use is defined first, e.g. if (response.forEach !== undefined). You don't actually need to compare to undefined though, so if (response.forEach) suffices. In the examples, I used response, but you can use this.test since they are the same object after the first line in the lambda.
Based on the link you shared, the response is an object. You can log it to the console to confirm.
You can only call for each on an array, so for example, based on the response api, you can call forEach on the property ‘categorias’ and on that array’s children property ‘produtus’
Edit: this answer was based on the op original api and question
https://demo5095413.mockable.io/carteira-investimentos
public createMenu() {
return this.api.getInvestiments()
}
ngOnit() {
this.coreService.createMenu().subscribe(x => console.log(x.categorias))};
{
"codigo":1,
"categorias":[
{
"nome":"Referenciado",
"valorTotal":23000.0,
"codigo":"2",
"produtos":[
{
"nome":"CDB Fácil Bradesco",
"valor":2000.0,
"codigo":1,
"quantidade":0.0,
"porcentagem":0.5500,
"aplicacaoAdicional":500.0,
"codigoInvest":1,
"salaInvestimento":"CDB",
"permiteAplicar":true,
"permiteResgatar":true,
"movimentacaoAutomatica":false,
"ordemApresentacao":37,
"horarioAbertura":"08:30",
"horarioFechamento":"23:59",
"codigoGrupo":0,
"codigoMF":"001
I want to write a decorator which replace the function body or simple return from the function. Something like this:
function mock(target: (...args: any) => any) {
return () => 'new hi'
}
class Greeting {
#mock
sayHello() {
return 'hi'
}
}
Basically I use NestJS with GraphQL and I want to replace the function body to use "mock" version in some scenarios. So this #mock decorator will be used along with #Query in resolvers.
The other trouble that I have is to recognize a mock request. I will have flag ?mock=true in query or custom header param. Any ideas on how to check query params or headers in decorator?
NestJS uses validation with validation pipes and
#UsePipes(ValidationPipe)
If this fails it throws an exception. This is fine for REST APIs that return JSON.
How would one validate parameters when using HTML rendering and return
{ errors: ['First error'] }
to an hbs template?
You can create an Interceptor that transforms the validation error into an error response:
#Injectable()
export class ErrorsInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
call$: Observable<any>,
): Observable<any> {
return call$.pipe(
// Here you can map (or rethrow) errors
catchError(err => ({errors: [err.message]}),
),
);
}
}
You can use it by adding #UseInterceptors(ErrorsInterceptor) to your controller or its methods.
I've been driving myself half mad trying to find a "Nest like" way to do this while still retaining a degree of customisability, and I think I finally have it. Firstly, we want an error that has a reference to the exisiting class-validator errors, so we create a custom error class like so:
import { ValidationError } from 'class-validator';
export class ValidationFailedError extends Error {
validationErrors: ValidationError[];
target: any;
constructor(validationErrors) {
super();
this.validationErrors = validationErrors;
this.target = validationErrors[0].target
}
}
(We also have a reference to the class we tried to validate, so we can return our object as appropriate)
Then, in main.ts, we can set a custom exception factory like so:
app.useGlobalPipes(
new ValidationPipe({
exceptionFactory: (validationErrors: ValidationError[] = []) => {
return new ValidationFailedError(validationErrors);
},
}),
);
Next, we create an ExceptionFilter to catch our custom error like so:
#Catch(ValidationFailedError)
export class ValidationExceptionFilter implements ExceptionFilter {
view: string
objectName: string
constructor(view: string, objectName: string) {
this.view = view;
this.objectName = objectName;
}
async catch(exception: ValidationFailedError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
response.render(this.view, {
errors: exception.validationErrors,
[this.objectName]: exception.target,
url: request.url,
});
}
}
We also add an initializer, so we can specify what view to render and what the object's name is, so we can set up our filter on a controller method like so:
#Post(':postID')
#UseFilters(new ValidationExceptionFilter('blog-posts/edit', 'blogPost'))
#Redirect('/blog-posts', 301)
async update(
#Param('id') postID: string,
#Body() editBlogPostDto: EditBlogPostDto,
) {
await this.blogPostsService.update(postID, editBlogPostDto);
}
Hope this helps some folks, because I like NestJS, but it does seem like the docuemntation and tutorials are much more set up for JSON APIs than for more traditional full stack CRUD apps.
I would like to make my route Query parameter required.
If it is missing I expect it to throw 404 HTTP error.
#Controller('')
export class AppController {
constructor() {}
#Get('/businessdata/messages')
public async getAllMessages(
#Query('startDate', ValidateDate) startDate: string,
#Query('endDate', ValidateDate) endDate: string,
): Promise<string> {
...
}
}
I'm using NestJs pipes to determine if a parameter is valid, but not if it exists And I'm not sure that Pipes are made for that.
So how can I check in NestJS if my param exists if not throw an error?
Use class-validator. Pipes are definitely made for that !
Example :
create-user.dto.ts
import { IsNotEmpty } from 'class-validator';
export class CreateUserDto {
#IsNotEmpty()
password: string;
}
For more information see class-validator documentation :
https://github.com/typestack/class-validator
And NestJS Pipes & Validation documentation :
https://docs.nestjs.com/pipes
https://docs.nestjs.com/techniques/validation
NestJS does not provide a decorator (like #Query) that detects undefined
value in request.query[key].
You can write custom decorator for that:
import { createParamDecorator, ExecutionContext, BadRequestException } from '#nestjs/common'
export const QueryRequired = createParamDecorator(
(key: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest()
const value = request.query[key]
if (value === undefined) {
throw new BadRequestException(`Missing required query param: '${key}'`)
}
return value
}
)
Then use #QueryRequired decorator as you would use #Query:
#Get()
async someMethod(#QueryRequired('requiredParam') requiredParam: string): Promise<any> {
...
}
There hava a easy way to valide you parameter, https://docs.nestjs.com/techniques/validation
In addition to Phi's answer, you can combine the use of class-validator with the following global validation pipe:
app.useGlobalPipes(
new ValidationPipe({
/*
If set to true, instead of stripping non-whitelisted
properties validator will throw an exception.
*/
forbidNonWhitelisted: true,
/*
If set to true, validator will strip validated (returned)
object of any properties that do not use any validation decorators.
*/
whitelist: true,
}),
);
I use this in order to only allow parameters defined in the DTO class so that it will throw an error when unknown parameters are sent with the request!
In Phie's example, a post request with a body like {password: 'mypassword'} will pass the validation when {password: 'mypassword', other: 'reject me!'} won't.
I have a callback function which returns some data to the component.
export class AppComponent {
constructor(
private service: AppService
) {
this.processSomething(true);
this.processSomething(false);
}
private processSomething(isZoom: boolean = false) {
this.service.handleAsyncResponses(
this,
this.processDataReceived
);
}
private processDataReceived(
attributeValueList: any,
isZoom?: boolean
) {
console.log("isZoom:", isZoom);
}
}
I need to send some value isZoom parameter from the component and access the same in console.log("isZoom:", isZoom). Now console.log is loggin undefined.
A working sample is here: https://stackblitz.com/edit/angular-service-oqkfmf?file=app/app.component.ts
I think you're getting a little lost.
I took the freedom to clean your stackblitz from non-used code and show you how to use callbacks : you can check it there.
Let's start with the component :
constructor(
private service: AppService
) {
this.processSomething(true);
this.processSomething(false);
}
private processSomething(isZoom: boolean = false) {
this.service.handleAsyncResponses(isZoom, this.processDataReceived);
}
private processDataReceived(isZoom: boolean) {
console.log("isZoom:", isZoom);
}
You don't need to define your parameters as optional, since you give your isZoom value a default value, hence making it always defined.
As you can see, you don't need to pass your full object as an argument : the function can be called without it.
In your service, all you have left is
public handleAsyncResponses(zoom: boolean, callback: Function) {
callback(zoom);
}
Simply call the function as you would in any other context. simply rename this.processDataReceived(zoom) with the name of the parameter (here it being callback).
This is how callbacks are handled.
In your case, you need to wrap the function call in local closure:
private processSomething(isZoom: boolean = false) {
this.service.handleAsyncResponses(
this, (attributeValueList: any) => {
this.processDataReceived(attributeValueList, isZoom);
}
);
}
changed example