angular-auto-validate icon indicating copy to clipboard operation
angular-auto-validate copied to clipboard

Can we use this for Angular5

Open saheel-ahmed opened this issue 7 years ago • 4 comments

saheel-ahmed avatar Jan 05 '18 07:01 saheel-ahmed

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 avatar Jan 05 '18 07:01 saheel-ahmed

@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

jonsamwell avatar Jan 09 '18 01:01 jonsamwell

Awesome 👍

Thank you, Jon. As I was using Bootstrap 4 with Angular 5, and this "inputValidationDecorator " concept should work.

saheel-ahmed avatar Jan 09 '18 07:01 saheel-ahmed

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

gconey avatar Nov 06 '18 10:11 gconey