Skip to content
This repository has been archived by the owner on Aug 15, 2021. It is now read-only.

Can we use this for Angular5 #173

Open
saheel-ahmed opened this issue Jan 5, 2018 · 4 comments
Open

Can we use this for Angular5 #173

saheel-ahmed opened this issue Jan 5, 2018 · 4 comments

Comments

@saheel-ahmed
Copy link

No description provided.

@saheel-ahmed
Copy link
Author

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.

@jonsamwell
Copy link
Owner

jonsamwell commented Jan 9, 2018

@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

@saheel-ahmed
Copy link
Author

Awesome 👍

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

@gconey
Copy link

gconey commented Nov 6, 2018

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

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants