angular-auto-validate
angular-auto-validate copied to clipboard
Can we use this for Angular5
I have used this module in AngularJS and it works awesome and I have used more than 10 projects.
I have now requirement for Angular5 implementation so I was think to use this module. What is the best way to use it? Alternatively I'm thinking to go ng2-validation package. But I love this module.
@saheel-ahmed I haven't got around to porting this to angular 5 yet. However, it is pretty easy to do with a directive and using the mat-error element from angular material. Basic code is below. If I get some time I might try and port this over. Contribution are welcome!
export abstract class ValidationDecoratorDirectiveBase implements Validator, OnChanges, OnDestroy, OnInit {
@Input()
public ngModel: any;
@Input()
public errorContainerId: string; // the id of the mat-error element
@Input()
public preValidate: boolean = false;
@Input()
public getValidationErrorMessage: (errors: ValidationErrors) => string;
@Input()
public scrollIntoViewWhenInvalid: boolean = false;
private statusChangeSubscription: Subscription;
public constructor(protected parentForm: NgForm,
protected ngControl: NgControl,
protected element: ElementRef,
protected renderer: Renderer2,
protected utilService: UtilService,
protected validationMessageService: ValidationMessageService) {}
@HostListener("blur")
public onElementBlurred(markAsTouched: boolean = false): void {
if (document.activeElement !== this.element.nativeElement) {
if (this.canMarkInValid()) {
this.markAsTouched();
const primaryErrorKey = this.getFirstErrorKey(this.ngControl.errors);
const remoteErrors = this.getRemoteErrors(this.ngControl.errors);
let errorMessage = this.element.nativeElement.validationMessage ||
this.validationMessageService.getErrorMessage(primaryErrorKey,
this.ngControl.errors[primaryErrorKey] ?
this.ngControl.errors[primaryErrorKey].data ||
this.ngControl.errors[primaryErrorKey] :
undefined);
if (remoteErrors.length > 0) {
errorMessage = remoteErrors[0].message;
}
if (this.getValidationErrorMessage !== undefined) {
errorMessage = this.getValidationErrorMessage(this.ngControl.errors) || errorMessage;
}
setTimeout(() => {
const el = this.getErrorElement();
this.makeInvalid(el, errorMessage);
if (this.scrollIntoViewWhenInvalid && el.scrollIntoView) {
el.scrollIntoView();
}
}, 100);
} else if (this.canMarkValid()) {
setTimeout(() => {
this.markAsTouched();
this.makeValid();
}, 100);
}
}
}
public markAsTouched(): void {
const formControl = this.parentForm.controls[this.ngControl.name] ||
this.parentForm.controls[this.errorContainerId];
if (!this.ngControl.touched && formControl) {
formControl.markAsTouched();
}
}
public ngOnInit(): void {
this.statusChangeSubscription = this.ngControl
.statusChanges
.subscribe((status) => {
if (status === "INVALID") {
this.onFormSubmitHandler(false);
} else if (status === "VALID") {
setTimeout(() => this.makeValid(), 100);
}
});
this.parentForm.ngSubmit.subscribe((evt) => {
this.onFormSubmitHandler(true);
});
}
public ngOnChanges(changes: SimpleChanges): void {
let change = changes.ngModel;
if (!this.utilService.isNullOrUndefined(change) &&
this.utilService.isNullOrUndefined(change.previousValue) &&
!this.utilService.isNullOrUndefinedOrEmpty(change.currentValue)) {
setTimeout(() => this.onElementBlurred(true), 100);
}
}
public validate(control: AbstractControl): ValidationErrors|null {
return null;
}
public ngOnDestroy(): void {
if (this.statusChangeSubscription) {
this.statusChangeSubscription.unsubscribe();
}
}
// tslint:disable-next-line:no-empty
public registerOnValidatorChange(fn: () => void): void {}
protected canMarkInValid(): boolean {
let canValidate = false;
const controlValue = this.ngControl.value;
const isTouched = this.ngControl.touched;
if (this.ngControl.invalid) {
canValidate = isTouched || !this.utilService.isNullOrUndefinedOrEmpty(controlValue) ||
(this.preValidate);
}
return canValidate;
}
protected canMarkValid(): boolean {
let canValidate = false;
const controlValue = this.ngControl.value;
const isTouched = this.ngControl.touched;
if (this.ngControl.valid) {
canValidate = (isTouched && !this.utilService.isNullOrUndefinedOrEmpty(controlValue)) ||
(this.preValidate && !this.utilService.isNullOrUndefinedOrEmpty(controlValue));
}
return canValidate;
}
protected getErrorElement(): HTMLElement {
return document.getElementById(this.errorContainerId);
}
protected abstract makeInvalid(errorElement: HTMLElement, errorMessage: string): void;
protected abstract makeValid(): void;
private onFormSubmitHandler(markAsTouched = false): void {
if (this.ngControl.invalid) {
if (markAsTouched) {
this.markAsTouched();
}
this.onElementBlurred(markAsTouched);
}
}
private getRemoteErrors(errors: ValidationErrors): IRemoteValidationError[] {
let remoteErrors: IRemoteValidationError[] = new Array<IRemoteValidationError>();
for (let key in errors) {
if (errors.hasOwnProperty(key) && errors[key].remote === true) {
remoteErrors.push({ propertyName: errors[key].propertyName, message: errors[key].message, level: 1 });
}
}
return remoteErrors;
}
private getFirstErrorKey(errors: ValidationErrors): string {
const properties = Object.keys(errors).sort();
return properties[0];
}
}
// tslint:disable-next-line:max-classes-per-file
@Directive({
selector: "[inputValidationDecorator]"
})
export class InputValidationDecoratorDirective extends ValidationDecoratorDirectiveBase {
public constructor(@Host() parentForm: NgForm,
@Host() private parentContainer: MatFormField,
ngControl: NgControl,
element: ElementRef,
renderer: Renderer2,
utilService: UtilService,
validationMessageService: ValidationMessageService) {
super(parentForm, ngControl, element, renderer, utilService, validationMessageService);
}
protected makeInvalid(errorElement: HTMLElement, errorMessage: string): void {
if (!this.utilService.isNullOrUndefined(errorElement)) {
errorElement.innerText = errorMessage;
}
}
protected makeValid(): void {
if (this.ngControl.touched) {
this.renderer.addClass(this.parentContainer._elementRef.nativeElement, "mat-focused");
}
}
}
You would then use it like this:
<mat-form-field>
<input name="EmailAddress"
type="email"
placeholder="Email Address (optional)"
[(ngModel)]="worker.emailAddress"
matInput
minLength="1"
inputValidationDecorator errorContainerId="mdErrorEmail" [preValidate]="inEditMode">
<mat-error id="mdErrorEmail"></mat-error>
</mat-form-field>
I hope this helps
Thanks,
Jon
Awesome 👍
Thank you, Jon. As I was using Bootstrap 4 with Angular 5, and this "inputValidationDecorator " concept should work.
Inspired by your AngularJS version and using the above code as a starting point I created an couple of Angular directives that work well together to standardise and simplify form validation. They are built to work in the ASP.NET Zero Framework but could easily be adapted for general use. You'd need to implement the localizationservice yourself as it is something that's built into the framework. See https://github.com/gconey/aspnet-zero-core-auto-validate