I'm trying to validate an Angular form using an external service but I'm getting a cannot read property of undefined error.
I've created a simple form in my component:
this.myForm = this.fb.group({
username: ['', [this.validator.username]],
});
From there, I'm calling my username method:
#Injectable()
export class ValidatorService {
constructor(private auth: AuthService) {}
username(input: FormControl): {[key: string]: any} {
return { userInvalid: this.auth.validate(input.value) };
}
}
My ValidatorService, then, calls a method that checks the server if that username exists:
#Injectable()
export class AuthService {
validate(username: string): boolean {
return username !== 'demo';
}
}
I'm getting the following error: Cannot read property 'auth' of undefined. Any ideas, please?
Live demo
username method is executed like a function not like a method of ValidationService so you're loosing context this.
Function.prototype.bind method should help you:
username: ['', [this.validator.username.bind(this.validator)]],
Related
I've got a reactive form that I'm initializing oninit() like this, along with a couple other properties I'm using to grab the passed id out of the URL and tell whether or not the form is being used to update, or create a new entry in a mysql table. The issue I'm having is in using patchValue to pass the data returned from my service into my form:
component.ts
export class formComponent implements OnInit, AfterViewInit {
constructor(
private dataService: dataService,
private route: ActivatedRoute,
private router: Router,
private formBuilder: FormBuilder,
private ticketModel: ticketModel,
) {}
Form!: FormGroup;
isNewMode!: boolean;
id!: string;
ticket!: ticketModel[];
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
this.Form = this.formBuilder.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required]
});
}
ngAfterViewInit(){
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData;
},
});
this.Form.patchValue({
field1: this.ticket.field1, //error, "Property 'field1' does not exist on type 'ticketModel[]'"
field2: this.ticket.field2, //error, "Property 'field2' does not exist on type 'ticketModel[]'"
field3: this.ticket.field3, //error, "Property 'field3' does not exist on type 'ticketModel[]'"
field4: this.ticket.field4, //error, "Property 'field4' does not exist on type 'ticketModel[]'"
});
}
}
}
ticketModel.ts
export class ticketModel {
id: string = '';
field1: string = '';
field2: string = '';
field3: string = '';
field4: string = '';
}
service.ts
export class dataService {
constructor(private errorHandlerService: errorHandlerService, private http: HttpClient) {}
private url = "/api/tickets";
httpOptions:{ headers: HttpHeaders } = {
headers: new HttpHeaders({ "Content-Type": "application/json" }),
};
getById(id: string): Observable<ticketModel[]> {
return this.http
.get<ticketModel[]>(`${this.url}/${id}`, {responseType: "json"})
.pipe(tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel[]>("fetchAll", [])
)
);
}
and just in case it's helpful, this is an example of the response json I'm getting when this method is run
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
if there isn't an id that gets passed in, isNewMode is true and the form is initialized with blank values, works fine from there. When an id is passed in, I'm passing that to a method in the data service to query the database and return just that row. This also seems to work fine as I'm able to log that data in json format to the console. I just can't figure out how to get the data to patch into the form after trying this out a few different ways.
Currently, the way that I think this should work which is what this code is an example of, in patchValue() the compiler throws an error that "property field1 does not exist on type ticketModel[]" when it absolutely does exist as a property on that model.
I feel like I'm probably missing something pretty small and any help in figuring out what would be wildly appreciated, thank you!
You have declared ticket!: ticketModel[] as an Array type.
Your response is also an array -
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
then why are you not treating this.ticket as an array here ?
field1: this.ticket.field1,
Either use it this way - field1: this.ticket[0].field1 or use for loop on it to get the field1 and other values from this.
And you need to patch the form inside the subscribe block, because it's an async operation.
really your service getById should return one 'TicketModel' nor an array of TicketModel. better than mannage in the component, mannage in the service using map
//see that is an observable of "ticketModel"
getById(id: string): Observable<ticketModel> {
//you needn't use {responseType:'json'}, by defect Angular expect a Json
return this.http
.get<ticketModel[]>(`${this.url}/${id}`)
.pipe(
//just return the uniq element of the array
map(res=>res.length?res[0]:null),
tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel>("fetchAll", [])
)
);
}
Futhermore, you need use the "patchValue" inside subcription function, and you can use the patchValue wihout create a new object because has the same properties
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData; //<--really you needn't use ticketData
//unless you want compare old and new Values
//here you make the patchValue
this.form.patchValue(ticketData);
})
}
(you can also put in ngOnInit instead of ngAfterViewInit)
Update another aproach to the "clasic" problem to create a component to edit/create an element.
If you has a function like
getForm(data:TicketModel=null){
data=data || {} as TicketModel
return new FormGroup({
id: new FormControl(data.id,Validators.required),
field1: new FormControl(data.field1,Validators.required),
field2: new FormControl(data.field2,Validators.required),
field3: new FormControl(data.field3,Validators.required),
field4: new FormControl(data.field4,Validators.required)
})
}
You can in ngOnInit make some like
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
if (this.isNewMode)
this.Form=this.getForm()
else
{
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe(res=>{
this.Form=this.getForm(res)
})
}
}
TL;DR
I am trying to run mongoose query in my validator
Hello, I am trying to make a custom decorator which throws an error if a value for that field already exists. I am trying to use the mongoose model inside the class that validates the route. Unlike in resolver/controller, #InjectModel() does not work in validator class. My validator is like this
import { getModelToken, InjectModel } from "#nestjs/mongoose";
import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { Model } from "mongoose";
import { User } from "../schema/user.schema";
#ValidatorConstraint({ name: "IsUniqueUser", async: true })
export class UniqueValidator implements ValidatorConstraintInterface {
constructor(
#InjectModel(User.name)
private readonly userModel: Model<User>,
) {}
async validate(value: any, args: ValidationArguments) {
const filter = {};
console.log(this.userModel);
console.log(getModelToken(User.name));
filter[args.property] = value;
const count = await this.userModel.count(filter);
return !count;
}
defaultMessage(args: ValidationArguments) {
return "$(value) is already taken";
}
}
and my DTO that uses the above decorator is
#InputType({})
export class UserCreateDTO {
#IsString()
name: string;
#IsUniqueUser({
message: "Phone number is already taken",
})
#Field(() => String)
phone: string;
}
The console says
cannot read value count of undefined implying that userModel is undefined.
InShort
I want to run the query in my validator. How can I do so?
According to this issue (you can't inject a dependency)
You should to add in your main.ts
import { useContainer } from 'class-validator';
useContainer(app.select(AppModule), {fallbackOnErrors: true});
Then you need to add your UniqueValidator to your module like an #Injectable() class
so
...
providers: [UniqueValidator],
...
Then, in your DTO you can add:
#Validate(UniqueValidator, ['email'], {
message: 'emailAlreadyExists',
})
I got this error:
Type 'Observable<unknown[]>' is not assignable to type 'Observable<User[]>'.
I'm trying to fetch data from firestore.
I use Observable for the first time (I watch some videos about observable, but here I can't use why I do not understand).
Can you help me?
by the way, user is this:
export interface User {
email: string;
uid: string;
}
my codes here:
usersCollection: AngularFirestoreCollection<User>;
users: Observable<User[]>;
constructor(public auth: AngularFireAuth, public db: AngularFirestore) {
**this.users(problem here)** = this.db.collection("users").valueChanges();
}
You can fix it by passing the User type as a generic parameter to the collection like the following:
this.users = this.db.collection<User>("users").valueChanges();
This will return Observable<User[]> from the valueChanges.
You can use any[] after the variable name to get rid of this error
There are questions on stackoverflow for the same but their accepted answers are not working for me as none of them is using an object literal. Without wasting your time. Here's my code.
contribute.component.ts
(contribute is just a name of my component i created with ng generate component contribute).
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl } from '#angular/forms';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { AuthService } from '../auth.service';
#Component({
selector: 'app-contribute',
templateUrl: './contribute.component.html',
styleUrls: ['./contribute.component.css']
})
export class ContributeComponent implements OnInit {
makeNewPost={}; //THI IS MY EMPTY OBJECT TO WHICH I WANT TO ADD PROPERTIES LATER IN THIS CODE
constructor(private _auth: AuthService) { }
ngOnInit() {
//SOME CODE
}
//THIS FUNCTION WILL BE CALLED ON CLICK EVENT FROM `contribute.component.html`
onSubmit() {
var email = (<HTMLInputElement>document.getElementById("email")).value;
var password = (<HTMLInputElement>document.getElementById("password")).value;
console.log(email); //SEEMS GOOD ON CONSOLE
console.log(password); //SEEMS GOOD ON CONSOLE
//THESE TWO PROPERTIES I WANT TO ADD
this.makeNewPost.email=email;
this.makeNewPost.password=password;
this._auth.registerNewt(this.makeNewPost)
.subscribe (
res => console.log(res),
err => console.log(err)
);
}
}
But my knowledge says that objects are mutable in ts. Then why I am getting this error.
error TS2339: Property 'email' does not exist on type '{}'.
error TS2339: Property 'password' does not exist on type '{}'.
Please tell if I am wrong about objects in typescript.
I also tried declaring my object as:
makeNewPost= {
email: string;
password: string;
}
PS: It is an Angular 8 project
Most of the point of TypeScript is that it provides static typing for variables. When you do
makeNewPost={};
...since you haven't specified a type for makeNewPost, TypeScript infers it from {}, and makes it a type with no properties. Later, of course, you're trying to add properties, but while that's fine in JavaScript, with TypeScript's static typing, it's a problem.
The solution is to include the properties up front, and then just change their values:
makeNewPost = {
email: "",
password: ""
};
Now, TypeScript will infer the type as an object with email and password properties, both strings, and you can assign to them later.
You don't have to add the properties initially, though that's probably the cleanest solution. You could define a type for makeNewPost and make the properties optional (the ? in the property definition):
interface NewPost {
email?: string;
password?: string;
}
Then use that type:
makeNewPost: NewPost = {};
Later, you'll be able to assign the properties because they're allowed to exist.
I wouldn't do that, though, because when you need to actually make a new post, those properties aren't optional.
A third approach would be to define a type without optional properties:
interface NewPost {
email: string;
password: string;
}
...and declare makeNewPost to be Partial<NewPost>:
makeNewPost: Partial<NewPost> = {};
You'd have this._auth.registerNewt accept NewPost, not Partial<NewPost>, but use a type assertion after you've filled it in to say (in essence) "I've filled it in now:"
this.makeNewPost.email = email;
this.makeNewPost.password = password;
this._auth.registerNewt(this.makeNewPost as NewPost)
// ...
You're using TypeScript. You can create a type for 'Post':
export interface Post{
email : String
password : String
}
Then declare makeNewPost to be of type Post, and immediately initiate it with your values:
let makeNewPost: Post = {
email : email,
password : password
}
If you don't know the properties of the object beforehand, you could make a custom type like:
type NewObject = {[key: string]: any};
let newObject: NewObject = {};
newObject.property = 'value';
I'm trying to get the nest validator working as in the example in the 'pipes' document (https://docs.nestjs.com/pipes) section "Object schema validation" . I'm trying the example using Joi which works except the passing of the schema from the controller to the validate service.
import * as Joi from 'joi';
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException
} from '#nestjs/common';
#Injectable()
export class JoiValidationPipe implements PipeTransform {
constructor(private readonly schema) {}
transform(value: any, metadata: ArgumentMetadata) {
const { error } = Joi.validate(value, this.schema);
if (error) {
throw new BadRequestException('Validation failed');
}
return value;
}
}
The compiler complains:
Nest can't resolve dependencies of the JoiValidationPipe (?). Please
make sure that the argument at index [0] is available in the current
context.
In the controller
#Post()
#UsePipes(new JoiValidationPipe(createCatSchema))
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
there the compiler complains one argument given zero expected.
It looks like a declaration issue but I don't really know. Why doesn't this work and how should I pass the schema to the service?
As you stated yourself JoiValidationPipe must not be declared as a provider in any module.
I can only reproduce the error with this code (not passing a schema):
#UsePipes(JoiValidationPipe)
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
Make sure you do not have this anywhere in your code.
This works for me:
#UsePipes(new JoiValidationPipe(Joi.object().keys({ username: Joi.string().min(3) })))
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}