bind global value into ValidationErrors in angular - javascript

i try to bind value into ValidationErrors.
i have this method:
isUniqueEmail(control: FormControl): ValidationErrors {
if (control.value != null) {
console.log(control.value)
if(control.value == this.foundEmail){
console.log("found one");
return {isUniqueEmail: true}
}else{
return null;
}
}
}
this method check if control.value (email typing) equal email stored in global variable this.foundEmail then we have duplicate email.
My problem is: i can retreive data from foundEmail in this method because this method is private.
this method is located inside export class exampleComponent implements OnInit.
Error: ERROR TypeError: Cannot read properties of undefined (reading 'foundEmail')
But i check i have data into foundEmail

The validator function gets called from the FormControl so its context is not bound to the class you're defining the method on. You need to manually bind isUniqueEmail() to this.
Two options:
Use bind() when defining the FormControl:
name: new FormControl("", [
this.isUniqueEmail.bind(this),
]),
Define your validator as arrow function:
isUniqueEmail = (control: FormControl) => {
if (control.value != null) {
console.log(control.value)
if(control.value == this.foundEmail){
console.log("found one");
return {isUniqueEmail: true}
}else{
return null;
}
}
}

Related

Property 'subscribe' does not exist on type 'void | Observable<User>'. ts(2339)

login() {
this.auth.login(this.userName, this.password).subscribe((_: any) => {
this.router.navigateByUrl('app', {replaceUrl: true});
});
}
I dont know why i get this error, tried a few things but nothing works.
This here is my auth.login method - don’t know where the error could be - I am pretty new to Angular:
login(username: string, password: string) {
let userObject: User;
if (username === 'user' && password === 'user1234') {
userObject = {
name: 'Max Mustermann',
userRole: 'USER',
};
}
else if (username === 'admin' && password === 'admin1234') {
userObject = {
name: 'Erika Mustermann',
userRole: 'ADMIN',
};
}else{
return this.assertNever();
}
return of(userObject).pipe(
tap(user => {
Storage.set({ key: TOKEN_KEY, value: JSON.stringify(user) });
this.currentUser.next(user);
})
);
}
When deleting the assertNever() method, I get this error at the last return:
Variable 'userObject' is used before being assigned. ts(2454)
Adding some return types to your functions will help you in the future. From what I can see, your function this.assertNever(); always throws but is declared (or inferred) to return void.
Change its declaration to return never and TS will recognize that this function will not return.
void = returns, but does not return a value
never = will not return / return vaue will not be observable
You get that error because the your 'login' method should has a path which does not have a "return".
The others paths indeed should be returning an "Observable", which sounds good.
EDIT:
Now that I can see your code, it seems that the error locates in this line:
return this.assertNever();
Could "assertNever()" method return "void"/null/nothing???
Any way, in order to avoid the error, could you fake a dummy user and return it changing this line:
else {
\\return this.assertNever();
userObject = {
name: 'Fake',
userRole: 'NOLOGGED',
};
}

Pass a dynamic parameter in reactive form custom validator

I would like to pass a variable as a parameter in a custom validator like this
newSimulation: new FormControl('', [uniqNameValidator(this.options)])
Then use it in my custom validator
export function uniqNameValidator(list: any): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const simulationFlatList = list.map(val => val.closingPeriodSimulationName)
return simulationFlatList.indexOf(control.value) > -1 ? { message: "simulation exists" } : null;
}
}
The issue with this is that this.options is always empty. I initialize it to [] but when user interacts with the form ( first field ) I update it to an array of string, I think that the custom validator does not recheck the value of this.options ?
In this case how to pass a variable in custom validator ?
this may work, bind the function to component newSimulation: new FormControl('', [uniqNameValidator.bind(this)]) then in the function you can access this.options

Assign class instance property a value based on its type in Typescript Angular

I don't know if this is allowed in Typescript, but I'm working in an Angular 7 project and I want to instantiate a Page class fullfilling all his properties from DB object. These are my classes:
export class User {
id: number;
name: string;
created_at: string;
constructor(obj?: any) {
Object.assign(this, obj);
}
getName(): string {
return this.name;
}
}
export class Page {
id: number;
title: string;
author: User;
constructor(obj?: any) {
Object.assign(this, obj);
}
showTitle(): string {
return this.title;
}
}
Here is an example of my service method to retrieve the data:
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Page } from '../models/page';
#Injectable()
export class PageService {
constructor(httpClient: HttpClient) {}
getPage(id: number): Observable<Page> {
return this.httpClient
.get<Page>('http://<my-server-ip>:<port>/api/pages')
.pipe(
map((page: Page) => {
console.log('retrieved', page);
return new Page(page);
})
);
}
}
And here is an example of this function call in my component
export class MyCustomComponent implements OnInit {
constructor(pageService: PageService) {}
ngOnInit() {
this.pageService.getPage()
.subscribe((page: Page) => {
console.log(page.showTitle());
});
}
}
This example works, but when I want to access to User methods, like:
console.log(page.author.getName());
I don't have access to them because it is not an instantiation of User class.
The same would happen with Page if I do not return a new instance of page class as an observable, thats why I use return new Page(page) after retrieving the data.
The problem is that I want to keep my constructors as generic as possible, so creating a constructor to assign the value manually (e.g.: this.author = new User(obj.author);) is not a valid workaround, as I want to implement it in every model or create a GenericModel then extend all my models.
Is there a way to fill a property with defined type in a instantiated class depending in its type?
This is what I tried so far, but it doesn't work:
export class Page {
// ...
constructor(obj?: any) {
Object.keys(obj).forEach((key: string, index: number) => {
if (typeof(obj[key]) === 'object' && obj[key] !== null) {
this[key] = new (this[key].constructor)(obj[key]);
} else {
this[key] = obj[key]
}
});
}
}
ERROR TypeError: Cannot read property 'author' of null
ERROR TypeError: Cannot read property 'constructor' of undefined
I understand that this is null when constructor is called, but I couldn't find another way to fill author property with a new instance to access to methods. Also, if I get a standard/default object like { ... }, the if will trigger and probably will throw an error too, as it does not have a constructor.
You could use Object.assign like this:
getPage(id: number): Observable<Page> {
return this.httpClient
.get<Page>('http://<my-server-ip>:<port>/api/pages')
.pipe(
map((page: Page) => {
console.log('retrieved', page);
return Object.assign(new Page(), page);
})
);
}
This code creates a new Page instance and then copies over all of the properties from the returned response (page in this example).
Then you don't need to modify your constructors.
UPDATE
NOTE: The spread syntax only copies over the properties, so I changed to use Object.assign instead.

Access service from Custom Validator in Angular

I'm trying to access my service in order to make check for the validator but all i get is console full of errors I'm sure I'm just bad with syntax stuff =/
validator:
import { DataService } from './services/data.service';
import { AbstractControl, FormGroup } from '#angular/forms';
export function titleValidator(control: AbstractControl,dataService:DataService) {
console.log(dataService.moviesArray) -->> How can I access this service?
if (control && (control.value !== null || control.value !== undefined)) {
if (control.value=="test") {
return {
isError: true
};
}
}
return null;
}
component:
this.movieForm = this.fb.group({
title: ['', [Validators.required,titleValidator]],
...
});
}
If anyone has even another solution to make the custom validation in the component itself I would like any help.. thanks!
update: the errors:
AddMovieComponent_Host.ngfactory.js? [sm]:1 ERROR TypeError: Cannot read property 'moviesArray' of undefined
at titleValidator (validator.ts:8)
at forms.js:602
at Array.map (<anonymous>)
at _executeValidators (forms.js:602)
at FormControl.validator (forms.js:567)
at FormControl.push../node_modules/#angular/forms/fesm5/forms.js.AbstractControl._runValidator (forms.js:2510)
at FormControl.push../node_modules/#angular/forms/fesm5/forms.js.AbstractControl.updateValueAndValidity (forms.js:2486)
at new FormControl (forms.js:2794)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder.control (forms.js:5435)
at FormBuilder.push../node_modules/#angular/forms/fesm5/forms.js.FormBuilder._createControl (forms.js:5473)
You have to pass the service to the validator, there is no dependency injection here as this is not an Angular directive, it is a pure function. The way to do this is to use a factory method that accepts the service and creates a validator function.
export function titleValidator(dataService:DataService): ValidatorFn {
return (control: AbstractControl) => {
console.log(dataService.moviesArray) // now you can :)
// Test for control.value only, for eg:
if (control.value && dataService.moviesArray.includes(control.value))
return null;
else
return { 'movieNotFound' : { value: control.value } };
}
}
Usage:
this.movieForm = this.fb.group({
title: ['', [
Validators.required,
titleValidator(this.dataService)
]],
...
});
There is no need to check for the presence of control as Angular only calls the validator function with a valid control. Test only the value. More info here

Angular 6: How to prevent 2 subscribed variables in one Observable from cascading each other

I'm facing a problem wherein there are 2 variable objects subscribed to the same Observable.
the name of the 2 variable objects are User and OriginalUser where:
User: is the object where its properties are editable.
OriginalUser: is the object where its properties should remain constant.
I construct the code like so in my component:
#Component({
...
})
export class HomeComponent implements OnInit {
constructor(private httpService: HttpClient) { }
User: any;
OriginalUser: any;
userId: number;
ngOnInit() {
httpService.get(`https://myservername.exampledomain.com:1234/user/${userId}`)
.subscribe((returnUser) => {
this.User = returnUser;
this.OriginalUser = returnUser;
});
}
inputValueChange(formControlName) {
if(this.User[formControlName] != this.OriginalUser[formControlName]) {
console.log('not equal');
}
else if(this.User[formControlName] == this.OriginalUser[formControlName]) {
console.log('is equal');
}
}
}
In the html of my component:
<div>
First Name:
<input type="text" name="firstName" [(ngModel)]="User.firstName" (input)="inputValueChange($event.target.name)" />
</div>
When I test this and edit the value in the input text field. the console always displays the 'is equal' message instead of the 'not equal' message.
When I check to see what's going on to the properties of these 2 objects, I saw that the value in property of OriginalUser is cascaded by the User variable that makes it always equal value.
My problem is how can I make the value of OriginalUser's properties be constant?
The attribute value of OriginalUser also changes, because this.User and this.OriginalUser are referencing the same object instance.
To make your OriginalUser's attributes to be constant you need to create another instance of the User, so this.User and this.OriginalUser don't share the same object instance.
You can use lodash clone function to create a new object with the same attribute values:
this.User = _.clone(returnUser);
Or you can do this manually, like following:
this.User = new Object();
for (key in returnUser) { // Iterates over all object attribute names
this.User[key] = returnUser[key]; // Assigns the value of returnUser to this.User
}
Or
this.User = new Object();
let objProp = Object.keys(returnUser); // Gets all attribute names of the object as array
objProp.forEach((p: string) => { // Iterates over all got attribute names
this.User[p] = returnUser[p]; // Assigns the value of returnUser to this.User
});

Categories