Unable to use a decorator into a NestJS service - decorator

Why we cant use an decorator within a NestJS service? Here is a example on how I tried to do, but it does not work.
Decorator: #User()
// user.decorator.ts
import { createParamDecorator } from '#nestjs/common';
export const User = createParamDecorator((data, req): {userId, email} => {
return data ? req.user[data] : req.user;
});
When I call this decorator into a service, I got this message: Unable to resolve signature of property decorator when called as an expression
// connect.service.ts
import { Injectable, Inject } from '#nestjs/common';
import { User } from '../account/user/user.decorator';
#Injectable()
export class ConnectService {
#User()
userInfo;
}

It looks like you are trying to add a decorator on an Injectable() I don't think you can do that. It would need to be on a method so that when it is called some magic can happen behind the scenes. Consider using class-validator and the validationPipe
For example:
#Get('vitalsByEncounterID')
async getVitalsByEncounterID(#Query() params: VitalsByEncounterPathDTO, #Headers(DFDHeaders.xRequestId) requestId: string): Promise<VitalSignsDTO[]> {}
Then you'd decorate the class
export class VitalsByEncounterPathDTO {
#IsString()
#IsNotEmpty()
#ApiModelProperty({ required: true, description: 'iCentra id for the patient' })
patientId: string;
#IsString()
#IsNotEmpty()
#ApiModelProperty({ required: true, description: 'an encounter id for the patient' })
encounterId: string;
}

You're trying to decorate a property with a ParamDecorator, that's why you get this error message.
Can you give some more details on your usecase? That would maybe help someone to give some insights on what you're trying to achieve.

Related

nested route using #ApiParam, how to use a custom validator in nestjs?

I’m looking for some help about custom validator & custom decorator in Nest.
FIRST CASE : working one
A DTO, with class-validator anotations :
import { IsNotEmpty, IsString } from 'class-validator';
import { IsOwnerExisting } from '../decorators/is-owner-existing.decorator';
export class CreatePollDto {
#IsNotEmpty()
#IsString()
#IsOwnerExisting() // custom decorator, calling custom validator, using a service to check in db
ownerEmail: string;
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
}
I use it in a controller :
#Controller()
#ApiTags('/polls')
export class PollsController {
constructor(private readonly pollsService: PollsService) {}
#Post()
public async create(#Body() createPollDto: CreatePollDto): Promise<Poll> {
return await this.pollsService.create(createPollDto);
}
}
When this endpoint is called, the dto is validating by class-validator, and my custom validator works. If the email doesn’t fit any user in database, a default message is displayed.
That is how I understand it.
SECOND CASE : how to make it work ?
Now, I want to do something similar but in a nested route, with an ApiParam. I’d like to check with a custom validator if the param matches some object in database.
In that case, I can’t use a decorator in the dto, because the dto doesn’t handle the "slug" property, it’s a ManyToOne, and the property is on the other side.
// ENTITIES
export class Choice {
#ManyToOne((type) => Poll)
poll: Poll;
}
export class Poll {
#Column({ unique: true })
slug: string;
#OneToMany((type) => Choice, (choice) => choice.poll, { cascade: true, eager: true })
#JoinColumn()
choices?: Choice[];
}
// DTOs
export class CreateChoiceDto {
#IsNotEmpty()
#IsString()
label: string;
#IsOptional()
#IsString()
imageUrl?: string;
}
export class CreatePollDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
#IsOptional()
#IsArray()
#ValidateNested({ each: true })
#Type(() => CreateChoiceDto)
choices: CreateChoiceDto[] = [];
}
So where should I hook my validation ?
I’d like to use some decorator directly in the controller. Maybe it’s not the good place, I don’t know. I could do it in the service too.
#Controller()
#ApiTags('/polls/{slug}/choices')
export class ChoicesController {
constructor(private readonly choicesService: ChoicesService) {}
#Post()
#ApiParam({ name: 'slug', type: String })
async create(#Param('slug') slug: string, #Body() createChoiceDto: CreateChoiceDto): Promise<Choice> {
return await this.choicesService.create(slug, createChoiceDto);
}
}
As in my first case, I’d like to use something like following, but in the create method of the controller.
#ValidatorConstraint({ async: true })
export class IsSlugMatchingAnyExistingPollConstraint implements ValidatorConstraintInterface {
constructor(#Inject(forwardRef(() => PollsService)) private readonly pollsService: PollsService) {}
public async validate(slug: string, args: ValidationArguments): Promise<boolean> {
return (await this.pollsService.findBySlug(slug)) ? true : false;
}
public defaultMessage(args: ValidationArguments): string {
return `No poll exists with this slug : $value. Use an existing slug, or register one.`;
}
}
Do you understand what I want to do ? Is it feasible ? What is the good way ?
Thanks a lot !
If you're needing to validate the slug with your custom rules you have one of two options
make a custom pipe that doesn't use class-validator and does the validation directly in it.
Use #Param() { slug }: CreatePollDto. This assumes that everything will be sent via URL parameters. You could always make the DTO a simple one such as
export class SlugDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
slug: string;
}
And then use #Param() { slug }: SlugDto, and now Nest will do the validation via the ValidationPipe for you.
If it didn't work with you with service try to use
getConnection().createQueryBuilder().select().from().where()
I used it in custom decorator to make a isUnique and it works well, but niot with injectable service.
public async validate(slug: string, args: ValidationArguments): Promise<boolean> { return (await getConnection().createQueryBuilder().select(PollsEntityAlias).from(PollsEntity).where('PollsEntity.slug =:slug',{slug}))) ? true : false; }
That’s so greeat! Thanks a lot, it’s working.
I’ve tried something like that, but can’t find the good way.
The deconstructed { slug }: SlugDto, so tricky & clever ! I’ve tried slug : SlugDto, but it couldn’t work, I was like «..hmmm… how to do that… »
Just something else : in the controller method, I was using (as in documentation) #Param('slug'), but with the slugDto, it can’t work. Instead, it must be just #Param().
Finally, my method :
#Post()
#ApiParam({ name: 'slug', type: String })
public async create(#Param() { slug }: SlugDto, #Body() createChoiceDto: CreateChoiceDto): Promise<Choice> {
return await this.choicesService.create(slug, createChoiceDto);
}
And the dto :
export class SlugDto {
#IsNotEmpty()
#IsString()
#NotContains(' ', { message: 'Slug should NOT contain any whitespace.' })
#IsSlugMatchingAnyExistingPoll()
slug: string;
}
Personally, I wouldn't register this as a class-validator decorator, because these are beyond the scopes of Nestjs's dependency injection. Getting a grasp of a service/database connection in order to check the existence of a poll would be troublesome and messy from a validator constraint. Instead, I would suggest implementing this as a pipe.
If you want to only check if the poll exists, you could do something like:
#Injectable()
export class VerifyPollBySlugPipe implements PipeTransform {
constructor(#InjectRepository(Poll) private repository: Repository<Poll>) {}
transform(slug: string, metadata: ArgumentsMetadata): Promise<string> {
let found: Poll = await repository.findOne({
where: { slug }
});
if (!found) throw new NotFoundException(`No poll with slug ${slug} was found`);
return slug;
}
}
But, since you're already fetching the poll entry from the database, maybe you can give it a use in the service, so the same pipe can work to retrieve the entity and throw if not found. I answered a similar question here, you'd just need to add the throwing of the 404 to match your case.
Hope it helps, good luck!

Using refs with react-redux 6.0.0 and decorators

I have the following component:
export default
#connect(null, dispatch => ({ dispatch }))
class MyComponent extends React.PureComponent {
}
And I have a component wrapping it:
export default class MyWrapper extends React.PureComponent {
comp = React.createRef();
render() {
return <MyComponent ref={this.comp}/>
}
}
How do I access MyComponent using refs?
I tried {withRef: true}, then tried {forwardRef: true} and got all sorts of errors.
Per the React-Redux connect docs, you need to pass {forwardRef : true} as an option to connect:
connect(mapState, mapDispatch, mergeProps, {forwardRef : true}
This is the same whether you're using connect as a separate function, or as a decorator.
On which note: for reference, we do not recommend using connect as a decorator.
The problem is with "react-redux": "7.0.0". The connect function has been implemented as a functional react component, so #decorators is not working anymore.

How to extend DocumentManager of JupyterLab

I would like to add my own custom behavior (server-side) to the closing tab(s) operation, so I think I should extend this method closeFile() of the
DocumentManager https://github.com/jupyterlab/jupyterlab/blob/master/packages/docmanager/src/manager.ts#L177.
I have a custom file browser plugin (extension) that works well if I use it with JupyterLab's core IDocumentManager but not if I use it with the extended/overwritten IMyDocumentManager like here:
const browserPlugin: JupyterLabPlugin<void> = {
id: "my:fs",
requires: [IMyDocumentManager, ISettingRegistry, IMainMenu],
activate: activateMyBrowser,
autoStart: true
};
My IMyDocumentManager looks like this:
import { IDocumentManager, DocumentManager } from '#jupyterlab/docmanager';
import { Token } from '#phosphor/coreutils';
export interface IMyDocumentManager extends IDocumentManager {}
export const IMyDocumentManager = new Token<IMyDocumentManager ('#jupyterlab/docmanager:IDocumentManager');
export class MyDocumentManager extends DocumentManager implements IMyDocumentManager {
closeFile(path: string): Promise<void> {
console.log("Test override a method for closing: " + path);
return super.closeFile(path);
}
}
When I load JupyterLab in the browser I get the error:
It's probably because I have to define the Token differently. Instead of new Token<IMyDocumentManager ('#jupyterlab/docmanager:IDocumentManager') I should use some other string than '#jupyterlab/docmanager:IDocumentManager' but I don't really know what. It's hard to infer from the PhosphorJS code.

How do I defer loading a component until my signalr service is initialized?

I have the following types:
DataService - Gets data from the server using signalr hub.
AppComponent - which is the entry point for my main application
The data service constructor is as follows.
constructor(private http: Http) {
var hub = $.connection.spwHub;
hub.client.loadEmployees = this.loadEmployees;
$.connection.hub.start().done(() => {
...
});
}
My AppComponent is as follows:
constructor(service: DataService) {
this.company = service.getCompany();
service.getEmployees().then(employees => this.employees = employees);
this.departments = service.getDepartments();
}
I get the following error of course because the hub async call has not returned before the hub connection is made.
EXCEPTION: Error in ./AppComponent class AppComponent_Host - inline template:0:0 caused by: SignalR: Connection has not been fully initialized. Use .start().done() or .start().fail() to run logic after the connection has started.
What is the best way to deal with this issue in AngularJs2?
You can use the APP_INITIALIZER hook to perform logic, get something prepped, whatever, that you need before the rest of the application runs.
In your app.module.ts (or whatever your main module is):
import { APP_INITIALIZER, NgModule } from "#angular/core";
export function init_app(employeeService: EmployeeService) {
return () => employeeService.getEmployees();
}
#NgModule({
<...>
providers: [EmployeeService, {
provide: APP_INITIALIZER,
useFactory: init_app,
deps: [ EmployeeService ],
multi: true
}]
})
export class AppModule { }
The service is returning a Promise which will be automatically handled:
getEmployees() {
return <...function stuff ...>
.toPromise();
}
And here's the github issue where this is documented (no doc on the angular.io site yet): https://github.com/angular/angular/issues/9047
After searching and finding nothing I gave the idea that components that don't need to be loaded should probably be deferred by default. This means that the answer is a no-brainer.
// start.component.ts
constructor() {
// Start the connection
var hub = $.connection.spwHub;
$.connection.hub.start().done(() => {
// This loads the next component and runs the constructor
this.initialized = true;
});
}
// start.component.html
<div *ngIf="initialized">
<main-component></main-component>
<div>
// This type is lazy loaded as soon as the initialized equals true.
// main.component.ts
constructor(employeeService: EmployeeService) {
// Finally, load the needed data.
this.employees = employeeService.getEmployees();
}

Different Results from HTTP calls to angular2-in-memory-web-api with the same collection name

I am trying to use angular2-in-memory-web-api in my angular 2 project. I'm having trouble finding any documentation that shows how to return different data objects for different requests with the same collection name. I believe the class I use for my SEED_DATA is off but I'm not sure how to structure it the right way to get what I want.
Here is my main.ts
import {provide} from "#angular/core";
import {bootstrap} from "#angular/platform-browser-dynamic";
import {HTTP_PROVIDERS, XHRBackend} from "#angular/http";
import {AppComponent} from "./app.component";
import {SEED_DATA, InMemoryBackendService} from "angular2-in-memory-web-api/in-memory-backend.service";
import {AppTestData} from "./AppTestData";
bootstrap(AppComponent, [
HTTP_PROVIDERS,
provide(XHRBackend, {useClass: InMemoryBackendService}),
provide(SEED_DATA, {useClass: AppTestData})
]);
My AppTestData.ts file would look something like this
export class AppTestData {
createDb() {
let studentData = {
name: "Student Name",
grade: "B"
};
let otherStudentData = {
name: "Other Student Name",
grade: "A"
};
let httpPaths = {
somepath: {
student: studentData,
anotherPath: {
student: otherStudentData
}
}
}
return httpPaths;
}
}
My attempt at the httpPaths object is off. But the idea would be me calling a get http call to "something/student" and getting back studentData and calling another get http call to "something/anotherPath/student" and getting back otherStudentData.
The following does seem to work but I would like to specify my complete path incase I want post to “something/student” and “something/anotherPath/student” and get different results.
let httpPaths = {
student: studentData
}
I believe you missed the brackets in the return statement:
return {httpPaths};
The developer created an example which can be found here
If you want to "mock" two different databases you have to follow this syntax:
export class AppTestData {
createDb() {
let heroes = {
name: "Batman",
city: "Gotham"
};
let countries = {
name: "America",
code: "GR"
};
return {heroes,countries};
}
}
In your service you can consume it with a Promise and the url for the http request would be :
private httpUrl = '../contacts/contacts';
My file structure is something like:
repo
-app
-shared
myservice.ts // this consumes the data from the InMemory DB
data.service.db.ts // this implements the InMemoryDbService
...
At least that's how I solved the problem.
Indeed the official documentation is poor so far.

Resources