Gbuomprisco / ngx-chips

Tag Input component for Angular
MIT License
902 stars 359 forks source link

Async validator breaks everything #853

Open eyalhakim opened 5 years ago

eyalhakim commented 5 years ago

PLEASE MAKE SURE THAT:

I'm submitting a ... (check one with "x")

[x] bug report => search github for a similar issue or PR before submitting
[ ] support request/question

Notice: feature requests will be ignored, submit a PR if you'd like

Current behavior

While an async validator exists, tags can't be added

Expected behavior

Tags should be added if validation passes

Minimal reproduction of the problem with instructions (if applicable)

This happens in your own demo

What do you use to build your app?. Please specify the version

angular-cli

Angular version:

7.2.2

ngx-chips version:

2.0.0-beta.0

Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

Chrome

dsajdl01 commented 5 years ago

I have the same problem. If validateAsync has any delay then the tag (in my case email) is not added on the enter key. However, if the space key (32) or comma key (188) is pressed then two same tags (emails) are added. Here is the code that I have:


@Component({
  selector: 'email-input',
  template: `
    <h5>Email</h5>
    {{emails | json}}
    <br/>
    <tag-input [ngModel]="emails"
               (onAdd)="onItemAdded($event)"
               [theme]="'ark-tag-theme'"
               [clearOnBlur]="true"
               [blinkIfDupe]="true"
               [addOnBlur]="true"
               [disable]="!hasPermission"
               [inputClass]="'input-of-tag-area-class'"
               [placeholder]="'Enter email'"
               [secondaryPlaceholder]="'Enter email'"
               (onRemove)="onRemoveEmail($event)"
               [errorMessages]="errorMessages"
               [validators]="validators"
               [asyncValidators]="asyncValidators"
               [separatorKeyCodes]="[32,188]"
               [separatorKeys]="[' ', ',']">
    </tag-input>
    `
})

export class EmailInputComponent implements OnInit {

  @Input() emails: DisplayItem[];
  @Input() hasPermission: boolean;

  private readonly emailExist = 'Email address already exist';
  private readonly emailInvalid = 'It is not valid email address';

  errorMessages: {'emailAlreadyExist@': string, 'invalidEmail@': string};
  validators: ValidatorFn[];
  asyncValidators: AsyncValidatorFn[];

  ngOnInit(): void {
    this.setEmailValidation();
  }

  setEmailValidation = (): void => {
    this.asyncValidators = [this.validateAsync.bind(this)];
    this.validators = [this.emailValidation.bind(this)];
    this.errorMessages = {
      'emailAlreadyExist@': this.emailExist,
      'invalidEmail@': this.emailInvalid
    };
  }

  onItemAdded(newEmail: TagModel): void {
    const email = newEmail as DisplayItem;
    this.emails.push(email);
  }

  onRemoveEmail(removedEmail: TagModel): void {
    const email = removedEmail as DisplayItem;
    this.emails = this.emails.filter(e => e.display !== email.display);
  }

  private validateAsync = (control: FormControl): Observable<any> => {
    if (this.validateEmail(control.value) && !this.emailExistInAddEmailArea(control.value)) {
      // in real app line below is doing to the rest API  to check if entered email exist in the System.
      // If yes it return Observable object { emailAlreadyExist@: true} if not it returns Observable(null)
      return of(null).pipe(delay(1000));
    }
    return of(null);
  }

  private emailExistInAddEmailArea = (email: string): boolean => {
    return !(!this.emails.find( e => e.display === email));
  }

  private validateEmail = (email: string): boolean => {
    const regEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return regEx.test(email);
  }

  private emailValidation = (control: FormControl) => {
    if (!control.value) {
      return null;
    }
    if (!this.validateEmail(control.value)) {
      return {'invalidEmail@': true};
    }
    if (this.emailExistInAddEmailArea(control.value)) {
      return {'emailAlreadyExist@': true};
    }
    return null;
  }
}

Node if the function validateAsync = (control: FormControl): Observable => {} does not have delay then everything works as expected. Once observable has delay then tag (email) can not be added and whole tag-input is broken.

Angular version: ~7.2.0 ngx-chips: 2.0.0-beta.0

Browser: tested on Chrome and Firefox