Am new to angular2 and i would like to validate an email address from the server but it fails
This is my form
this.userform = this._formbuilder.group({
email: ['', [Validators.required], [this._validationService.emailExistsValidator.bind(this)]],
});
Then the _ValidationService i have
The validation service is a service with #Injector
#Injectable()
export class ValidationService{
constructor(
private _authService:AuthService
){}
emailExistsValidator(control) {
if (control.value != undefined) {
return this._authService.checkExists("email")
.map(response => {
if (!response) {
return {'emailNotExists': true};
}
});
}
}
}
And in the authservice i have (which is another service performing the http requests)
#Injectable()
export class AuthService {
checkExists(value):Observable<any>{
return this._http.get(this.authurl+value)
.map(response => {
return response //this is either true or false
});
}
}
I have followed This link in setting up my form but it fails
The error returned is
Cannot read property 'checkExists' of undefined
at UsersComponent.webpackJsonp.187.
ValidationService.emailExistsValidator
What could be wrong
If this should point to the ValidationService, bind() needs to be
this.userform = this._formbuilder.group({
email: ['', [Validators.required], [this._validationService.emailExistsValidator.bind(this._validationService)]],
});
Related
Hi I am trying to set/ take away custom validators for different elements in a form array that can change around, so far what I have tried to do is create a switch statement and loop through all of the input types that are set so I could set the validation rule as well as send a message to the user if the rule isn't met. The problem I am having is the form is initialized before the form data is set.
So my question is how can I loop through the array and set the validation rules. If someone could let me know if im along the right tracks with using a switch statement but have code in the wrong place or if there is a different and better approach it would be most helpful thank you
export class ReactiveComponent implements OnInit {
public form: FormGroup;
public fieldList: any;
types: Array<any>;
formData: any;
Param: string;
setData: any;
formLabelNames: any;
get contactFormGroup() {
return this.form.get('inputs') as FormArray;
}
constructor(
private route: ActivatedRoute,
private fb: FormBuilder,
private api: FormService,
private notifiy: NotificationService,
private auth: AuthService,
private router: Router) { }
ngOnInit() {
this.form = this.fb.group({
name: ['', Validators.compose([Validators.required])],
organization: ['', Validators.compose([Validators.required])],
inputs: this.fb.array([this.createForm()])
});
this.route.paramMap.subscribe(params => {
this.Param = params.get('id');
this.getForm(this.Param);
});
// set fieldslist to this field
this.fieldList = this.form.get('inputs') as FormArray;
}
// formgroup
createForm(): FormGroup {
return this.fb.group({
type: ['', Validators.compose([Validators.required])],
name: ['', Validators.compose([Validators.required])],
value: ['', this.validators()]
});
}
getForm(id) {
this.api.getForm(id).subscribe(
(data: any) => this.setForm(data)
);
}
getFieldsFormGroup(index): FormGroup {
const formGroup = this.fieldList.controls[index] as FormGroup;
return formGroup;
}
getContactsFormGroup(index): FormGroup {
const formGroup = this.fieldList.controls[index] as FormGroup;
return formGroup;
}
setForm(data) {
const d = data.results;
this.setData = d;
this.formLabelNames = d[0].fields;
this.form.patchValue({
name: [d[0].form_name],
organization: [d[0].org],
});
this.form.setControl('inputs', this.setExistingFields(d[0].fields));
}
setExistingFields(fields: any): FormArray {
const formArray = new FormArray([]);
this.fieldList = formArray;
fields.forEach(f => {
formArray.push(this.fb.group({
name: f.name,
type: f.type,
value: f.value
}));
});
return formArray;
}
/* This is where I have tried to create a switch statement but I get a undefined error because the setform function is being called after this one */
validators() {
this.formLabelNames.type.forEach((field: any) => {
switch (field.type) {
case 'email':
}
});
}
submit() {
if (this.form.valid) {
const formId = this.Param;
const local = this.auth.decodePayload();
const userId = local.sub;
this.router.navigateByUrl('/dashboard');
this.api.sendForm(this.form.value, formId, userId).subscribe();
this.form.reset();
} else {
this.notifiy.showFailure('Form is not valid', 'Error');
}
}
}
You have several problems, as far as I can judge:
You don't initialize neither the formControls nor the formArray correctly inside of you formGroup. It should rather look like this:
this.form = this.fb.group({
name: new FormControl( "", {validators: [Validators.required]}),
organization: new FormControl( "", {validators: [Validators.required]}),
inputs: this.fb.array([new FormControl(), new FormControl()]),
});
Besides: What is the point of using a formArray when it consists of only one formGroup?
Off course you can set the validators for any abstractControl be it a formControl or a formGroup. For the array it would look like something like this:
this.formArray.controls.map((ctrl) => {
ctrl.setValidators([Validators.required, Validators.email]);
});
It doesn't matter where in the component class you put your method. Though it surely matters when that method is invoked!
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
Subscribe to Observable:
checkAllowEmail(control: FormControl) {
this.userService.getUserByEmail(control.value)
.subscribe((user: UserDto) => {
console.log(user);
if (user !== undefined) {
console.log(this.isAllowEmail);
this.isAllowEmail = false;
console.log(this.isAllowEmail);
}
});
}
Return Observable from method:
getUserByEmail(email: string): Observable<UserDto> {
return this.http.get(`http://localhost:9092/api/v1/users?email=${email}`)
.map((response: Response) => response.json())
.map((user: UserDto) => user ? user : undefined);
}
Class UserDto:
export class UserDto {
constructor(
public email: string,
public name: string,
public role: string
) {}
}
Response from BE-side:
{"name":"art","email":"art#mail.ru","role":"user"}
Why I can change variable isAllowEmail to false in if statement in checkAllowEmail method?
I don't think you still have to use the map function, when using the HttpClient (since angular version 4). Try using
getUserByEmail(email: string): Observable<UserDto> {
return this.http.get<UserDto>(`http://localhost:9092/api/v1/users?email=${email}`);
}
Else use something like Postman or even curl from the console to verify that you api returns the correct/expected responses.
Also check if you use 'use strict'; in that case this will refere to the callable you pass to the subscribe method and not the surrounding component class.
I get my data from http with rjsx in component (let name it customer).
Then i'm using inner component in customer:
<customer>
<customer-form [customer]="customer"></customer-form>
</customer>
<!-- [customer]="customer" // here is data from http -->
and in customer-form i have:
#Input() customer:ICustomer;
complexForm : FormGroup;
constructor(fb: FormBuilder) {
this.complexForm = fb.group({
'name': [this.customer['name'], Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
});
}
but i get:
Cannot read property 'name' of undefined
TypeError: Cannot read property 'name' of undefined
if i understood correctly: it's due to the fact that constructor is called, but data isn't fetched yet from http, so customer is empty. But how to fix this?
upd: my http data get:
getCustomer(id) {
this.customerService.getCustomer(id)
.subscribe(
customer => this.customer = customer,
error => this.errorMessage = <any>error);
}
----
#Injectable()
export class CustomerService {
private customersUrl = 'api/customer';
constructor (private http: Http) {}
getCustomers (): Observable<ICustomer[]> {
return this.http.get(this.customersUrl)
.map(this.extractData)
.catch(this.handleError);
}
getCustomer (id): Observable<ICustomer> {
return this.http.get(this.customersUrl + '/' + id)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
let body = res.json();
return body || { };
}
private handleError (error: Response | any) {
// In a real world app, we might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}
}
as #Bhushan Gadekar stated, you are accessing customer when it has not been initialized.
There are multiple way to handle this correctly :
Using a setter:
#Input("customer")
set _customer(c:ICustomer){
this.customer=c;
this.complexForm.get("name").setValue(c.name,{onlySelf:true});
}
customer:ICustomer;
complexForm : FormGroup;
constructor(fb: FormBuilder) {
this.complexForm = fb.group({
'name': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
});
}
Using an Observable
Here, the customer needs to be an Observable of ICustomer
#Input() customer:Observable<ICustomer>;
complexForm : FormGroup;
constructor(fb: FormBuilder) {
this.complexForm = fb.group({
'name': [this.customer['name'], Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
});
}
ngOnInit(){
this.customer.map(c=>this.complexForm.get("name").setValue(c.name,{onlySelf:true}))
.subscribe();
}
Mixing both :
#Input("customer")
set _customer(c:ICustomer){
this.customer.next(c);
}
customer=New Subject<ICustomer>();
complexForm : FormGroup;
constructor(fb: FormBuilder) {
this.complexForm = fb.group({
'name': [null, Validators.compose([Validators.required, Validators.minLength(3), Validators.maxLength(255)])]
});
}
ngOnInit(){
this.customer.map(c=>this.complexForm.get("name").setValue(c.name,{onlySelf:true}))
.subscribe();
}
Case for multiple properties :
If you don't want to write every form update one by one, and if your form's field names are the same as your Object you can loop over customer properties:
Object.keys(customer).forEach(k=>{
let control = this.complexForm.get(k);
if(control)
control.setValue(customer[k],{onlySelf:true});
});
Note that this code will work only if your form's controls are named the same way as customer's properties are. If not, you may need to make a hash mapping customer properties name to formControls name.
Important point:
Yous should never access inputs from the constructor as they are not populated yet, all inputs should get populated (at least the synchronous ones) just before the ngOnInit hook. Take a look at the Lifecycle hooks documentation
I can see that you are trying to access customer object when it is not populated.
Issue here is that http call takes some time to be resolved.thus, your view is trying to access customer object even when it is undefined.
try this:
<customer *ngIf="customer">
<customer-form [customer]="customer"></customer-form>
</customer>
Though the way you are accessing name property is also not good.
Best approach is to create a customer model and use your property as className.propertyName
Hoe this helps.
Instead of ngOnInit , try ngAfterViewInit
do not use subscribe in component.ts and add async pipe in component.html, like so:
<customer-form [customer]="customer | async"></customer-form>
I am using Ionic 2 with Angular 2 beta 11.
I have a custom validator that works perfectly. However, I now need to add another custom validation method, but can't seem to work out how to pass a parameter to it.
As you can see below, I have a custom validation function:
ValidationService.userNameExists(control, this.employeeService)
'control' does not exist
How do I pass the control (FormControl) to the userNameExists function? Or, I need to pass the field value (value of username).
Thanks
register.ts
constructor(private nav: NavController, private builder: FormBuilder, employeeService: EmployeeService) {
this.employeeService = employeeService;
this.registerForm = builder.group({
'username': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(55), ValidationService.userNameValidator, ValidationService.userNameExists(control, this.employeeService)]],
'password1': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45), ValidationService.passwordValidator]],
'password2': ['', [Validators.required, Validators.minLength(5), Validators.maxLength(45), ValidationService.passwordValidator]]
}, { 'validator': ValidationService.matchPassword('password1', 'password2') }
);
}
'control' does not exist
ValidationService.ts
public static userNameExists(control: FormControl, employeeService: EmployeeService): any {
return (control: FormControl) => {
let userNameInput = control.value;
console.log('userNameExists: '+control.value);
let promise: Promise<EmployeeModel> = employeeService.getEmployeByUserName(userNameInput);
promise.then((employeeModel: EmployeeModel) => {
if (employeeModel.userName === userNameInput) {
return { userNameExists: true };
} else {
return null;
}
});
}
}
Remember that when you are instantiating FormControl/FormGroup, you are passing the validation function in, not calling it. Change your username declaration as following:
'username': ['',
[Validators.required,
Validators.minLength(5),
Validators.maxLength(55),
ValidationService.userNameValidator,
(control) => ValidationService.userNameExists(control, this.employeeService)]]