7

I'm using ngx-chips to add a list of emails as tags into an input. A validator makes sure that each tag appears like an email.

How can I make sure that:

1) The validator only triggers when a tag is being added (i.e., user hits enter, space or comma)

2) If the email is not valid when enter/space/comma is hit, that the value persists (i.e., it does not clear...so that the user can fix it)

A stackblitz is here: https://stackblitz.com/edit/ngx-chips-example-2qdudc

Below is my email validator:

public validators = [ this.must_be_email ];
  public errorMessages = {
      'must_be_email': 'Please be sure to use a valid email format'
  };
  private must_be_email(control: FormControl) {        
      var EMAIL_REGEXP = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,3}$/i;
      if (control.value.length != "" && !EMAIL_REGEXP.test(control.value)) {
          return { "must_be_email": true };
      }
      return null;
  }

Below is the tag:

<tag-input [(ngModel)]='emails' 
name="emails" 
#email="ngModel" 
[errorMessages]="errorMessages"
[validators]="validators" 
[editable]='true' 
(onTagEdited)="onTagEdited($event)" 
[separatorKeyCodes]="[32,188,186,13,9]"
[placeholder]="'Add email'" 
[secondaryPlaceholder]="'Enter email address(es)'" 
[clearOnBlur]="true" 
[addOnPaste]="true"
[addOnBlur]="true"
[pasteSplitPattern]="splitPattern" 
theme='bootstrap' 
required >
</tag-input>

For 2), I tried changing "return null" to control.value in the validator...but that did not work

user749798
  • 5,210
  • 10
  • 51
  • 85
  • Are you are trying to remove error warning before the user hits enter? – MCMatan Mar 02 '19 at 15:33
  • Yes. Trying to remove warning from showing before user hits enter. Right now, it shows as they type, which is distracting for the users – user749798 Mar 02 '19 at 18:42
  • Did you try and use 'asyncValidators' for that? – MCMatan Mar 02 '19 at 18:59
  • From the example that you've added, the "problems" come from the module that you use. You may edit the module to your own need. But from quick look its the module side and not something that you can change easily – dAxx_ Mar 03 '19 at 22:00

2 Answers2

6

Ultimately the awkward implementation of this control makes things difficult. If it were a ControlValueAccessor with a model that includes the current input and array of tags, it would be a much simpler matter to find a solution.

As with @AlesD's answer, I went with a solution that utilizes onAdding. One issue he brings up is the problem with using this. To get around that problem I use the bind() function when necessary.

In order to implement your desired behavior I did three things:

  1. Modify the validator function so that it would only return an error if a field addFirstAttemptFailed was true. This will prevent the validator for executing.
  2. Add a callback to onAdding. It validates the tag, and if the validation fails, sets addFirstAttemptFailed to true and returns an error observable (I upgraded to rxjs 6). Throwing this error prevents the tag from being added.
  3. Once an item is successfully added addFirstAttemptFailed is set back to false so the behavior can begin again for the next tag.

Unfortunately the method that is called during onAdding has some rigs.

  • In order to get validation to occur, I had to get a reference to chip's TagInputComponent and call setInputValue(), passing the value that's already set. Believe me I tried a thousand variants before I stumbled upon this side effect. Trying to call updateValueAndValidty() on either the FormControl instance on your component or various instances of Form and FormControl within the TagInputComponent never quite worked.
  • In order to prevent adding (and not have the input get cleared), I have to return an error observable from throwError(). Unfortunately the way the subscription is setup internally is that the TagInput component only calls catchError() on it's subscription callback function, not on the source observable. So the error gets displayed in the console. Again - I tried a bunch of different ways to get around it.

Relevant code

@ViewChild('tagInput')
tagInput: SourceTagInput;


public validators = [ this.must_be_email.bind(this) ];
public errorMessages = {
  'must_be_email': 'Please be sure to use a valid email format'
};

public readonly onAddedFunc = this.beforeAdd.bind(this);

private addFirstAttemptFailed = false;

private must_be_email(control: FormControl) {        
  if (this.addFirstAttemptFailed && !this.validateEmail(control.value)) {
    return { "must_be_email": true };
  }
  return null;
}

private beforeAdd(tag: string) {

  if (!this.validateEmail(tag)) {
    if (!this.addFirstAttemptFailed) {
      this.addFirstAttemptFailed = true;
      this.tagInput.setInputValue(tag);
    }
    return throwError(this.errorMessages['must_be_email']);
  }
  this.addFirstAttemptFailed = false;
  return of(tag);
}
Daniel Gimenez
  • 18,530
  • 3
  • 50
  • 70
  • Thanks Daniel. That worked. As an FYI, this does seem to break pasting (i.e., separator codes no longer work when pasting), but it accomplishes the problem for clearing / validators. – user749798 Mar 08 '19 at 03:30
  • Great findings, but it looks like this doesn't work with newer version of the library :( Do you have any suggestion of how to make a workaround for that issue? the issue here is still open https://github.com/Gbuomprisco/ngx-chips/issues/860 – Stanislav Kvitash May 11 '19 at 19:54
0

The ngx-chips has an event onAdding you can use to do additional checking. Inside the event handler you can check if the component is valid and cancel the adding if the control is invalid. Then the text will stay. For Enter key it cannot work because the Enter key is coded to submit the form and always clears the text. You can see it in the GitHub source code of the tag-input-form component that is used internally by the tag-input. Check the onKeyDown method.

Here is a sample implementation of the onAdding handler in your component:

public onAdding(tag): Observable<string> {
  if (!this.hasErrors()) { // this is tricky the this here is actually the ngx-chips tag-input component
    return of(tag);
  } else {
    return EMPTY;
  }
}

As I already mention in the comment in the code due to how the event handler is called the this inside the function is actually the ngx-chips tag-input component and not your component what you would normally expect.

After you have this method you just bind it in the template and it should work.

<tag-input ... [onAdding]="onAdding">

I have created also a fork of your stackblitz with this implementation.

If this does not work for you will probably need to contact the author of the component for more details or use a different component. For example the Angular material components contain a chip input component which is similar.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Aleš Doganoc
  • 11,568
  • 24
  • 40
  • Thanks. But, as you pointed out, it still clears the value. I worked with this too and have not been able to get it to work. – user749798 Mar 04 '19 at 00:27
  • It only clears te value on pressing Enter for all other keys it works. I now looked at the code in GitHub more closely and this is hard coded in the control when you press Enter it considers you are submitting the form and clears the text. Will add this also to the answer. – Aleš Doganoc Mar 04 '19 at 22:39
  • Thanks! Is there any way for the validator to only show onAdding as well? – user749798 Mar 05 '19 at 01:48
  • No unfortunately you cannot change the validation behavior from what I see. It uses the default which is update on every value change. – Aleš Doganoc Mar 05 '19 at 21:44
  • AlesD, thank you for the help. I gave the 50 pts to Daniel because his solution covered both problems. However, I greatly appreciate the work that you put into helping find the answer. – user749798 Mar 08 '19 at 03:31