Reputation: 1623
I am using the same component for read/edit routines. async-validator
works perfectly with new entries. the problem starts if the user accidentally changes the value and tries to revert back to the saved value. my current code will run regardless and returns the value as existing. I want to pass more data along with control value so I can validate if that pair value exist already or not.
I am posting the relevant code,
this is my form control
,
patientEmail: new FormControl(
null,
[Validators.email, Validators.required],
FormControlValidator.createEmailAsyncValidator(
this.asyncValidatorService
),
),
my async validator creator class is,
export class FormControlValidator {
static createEmailAsyncValidator(asyncValidatorService: AsyncValidationService) {
return (control: AbstractControl) => {
if (!control.pristine) {
control.markAsPristine();
return asyncValidatorService
.validateEmailNotTaken(control)
.map((response: HttpResponse<boolean>) => {
return !response.body ? null : { taken: true };
});
}
return Observable.of(null);
};
}
and finally my service,
@Injectable()
export class AsyncValidationService {
constructor(private httpService: HttpClientService) {}
public validateEmailNotTaken(control: AbstractControl) {
return this.httpService.getRequest(
'PatientsRegistration/IsPatientEmailExist?email=' + control.value,
);
}
}
I want to be able to pass another param to my createEmailAsyncValidator
, something like another control value from the form if possible.
Upvotes: 6
Views: 8224
Reputation: 16837
If you need to validate control based on another control, you need to lift the validator to the parent control. Here's an example of validator that checks if the email starts with person's name (another control value).
app.component.ts
import { Component } from '@angular/core';
import {FormBuilder, FormControl, FormGroup, ValidatorFn} from '@angular/forms'
const personalEmailValidator: ValidatorFn = (ctrl: FormGroup) => {
const email = ctrl.get('email') as FormControl;
const name = ctrl.get('name') as FormControl;
const valid = (email.value || '').startsWith(name.value);
return valid ? null : { personalEmailError: 'Email must start with a person name'}
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
email: [null],
name: [null]
}, { validator: personalEmailValidator })
}
}
app.component.html
<input [formControl]="form.get('name')" placeholder="name" />
<input [formControl]="form.get('email')" placeholder="email" />
{{ form.hasError('personalEmailError') ? form.getError('personalEmailError') : 'form is valid'}}
Upvotes: 1
Reputation: 1623
So I worked it in another way, inspired by Tomasz Kula answer
I created an Async Directive
that implements AsyncValidator
interface,
I pass extra params as an `object, in my case it looks like this,
{
coupledControl: AbstractControl,
method: {
apiUrl: string
}
}
this is my directive
code,
import { Directive, forwardRef, Input } from '@angular/core';
import { NG_ASYNC_VALIDATORS, Validator, AbstractControl } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { HttpClientService } from '../../../../../shared/services/httpclient.service';
import { HttpResponse } from '@angular/common/http';
import { IAsyncMethod } from './../interfaces/async-methods-interface';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[asyncValidator][formControlName], [asyncValidator][ngModel]',
providers: [
{
provide: NG_ASYNC_VALIDATORS,
useExisting: forwardRef(() => AsynValidatorDirective),
multi: true,
},
],
})
export class AsynValidatorDirective implements Validator {
@Input() asyncValidator: { coupledControl: AbstractControl; method: IAsyncMethod };
validate(
control: AbstractControl,
): Promise<{ [key: string]: any }> | Observable<{ [key: string]: any }> {
return this.validateIsExistAsync(control);
}
constructor(private httpService: HttpClientService) {}
validateIsExistAsync(control: AbstractControl) {
if (!control.pristine) {
const coupledControl: AbstractControl = this.asyncValidator.coupledControl;
const method: IAsyncMethod = this.asyncValidator.method;
return this.httpService
.getRequest(method.apiUrl + '?control=' + control.value + '&id=' + coupledControl.value)
.map((response: HttpResponse<boolean>) => {
return !response.body ? null : { asyncInvalid: true };
});
}
return Observable.of(null);
}
}
in my HTML
,
<input [asyncValidator]="{coupledControl: patientRestrationForm.get('patientFileId'), method: this.asyncMethods.validatePatientEmail }" [errorStateMatcher]="matcher" matInput autocomplete="off" formControlName="patientEmail">
and in my backend I check for both exist and match, simple logic!
Would appreciate any input,
Upvotes: 0