L. Aames
L. Aames

Reputation: 121

Angular Material, Mat Chips Autocomplete Bug, matChipInputTokenEnd executed before optionSelected

When typing some text into the input and selecting an option from the autocomplete by hiting enter, it saves as chips both of the strings. Image here

However, this doesn't happen when selecting an option from the autocomplete with the mouse.

In the example provided on Angular Material Autocomplete Chips, in the case described, the optionSelected fires first, while in the same code on my local machine it is executed after matChipInputTokenEnd, thus, leading to the bug.

Has anyone encountered this problem?

Upvotes: 12

Views: 10430

Answers (8)

vivisabadoti
vivisabadoti

Reputation: 1

I spent a lot of time and finally found the combination of two solutions to get things working properly in Angular 17.

First: make sure MatAutocompleteModule is called before MatChipsModule in your import list. This triggers MatAutocompleteSelectedEvent before MatChipInputEvent. Example :

  imports: [
...
    MatAutocompleteModule,
    MatChipsModule,
]

Source : https://stackoverflow.com/a/67399007

Second : I did not want to set [matChipInputAddOnBlur] to false or anything related because I needed this functionality as well. So to prevent blur from taking the place of option selection events (if an option is selected by a mouse click) add (mousedown)="$event.preventDefault()" Example :

 <mat-autocomplete 
#chipAutocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
 @for (option of filteredOptions$ | async; track option) {
<mat-option [value]="option" (mousedown)="$event.preventDefault()">{{ option }}</mat-option>
      }
</mat-autocomplete>

Source : https://stackoverflow.com/a/73234929/23391275

Upvotes: 0

BDurand
BDurand

Reputation: 450

Setting [matChipInputAddOnBlur] to false (or deleting it) can solve the issue.

When set to true it means that an event is emitted when the element is blurred, eg it's not on focus anymore.

That's why the event is emitted from the input when you click on the dropdown.

https://v6.material.angular.io/components/chips/api#:~:text=Whether%20or%20not%20the%20chipEnd%20event%20will%20be%20emitted%20when%20the%20input%20is%20blurred.

https://developer.mozilla.org/en-US/docs/Web/API/Element/blur_event

Upvotes: 0

Engin Volkan
Engin Volkan

Reputation: 49

I am on Angular 12.
In my case removing [matChipInputAddOnBlur]=true directive from the input tag did the trick.

Upvotes: 3

I get the same behavior and the solution was simply put the [matAutocomplete] before [matChipInputFor]

Upvotes: 0

Vo Minh Khanh
Vo Minh Khanh

Reputation: 99

it worked for me when I changed the order when importing modules. Import MatAutocompleteModule before MatChipsModule.

Upvotes: 7

Sandeep Jain
Sandeep Jain

Reputation: 1

I combined the answer from @ama and this other answer to get the functionality I think you're describing. The key was to combine the optionActivated global variable with manually closing the autocomplete panel:

  1. Add optionActivated to your mat-autocomplete
<input #autoInput (matChipInputTokenEnd)="inputTokenEnd($event)" ... >
<mat-autocomplete (optionActivated)="optionActivated($event)"
                  (optionSelected)="optionSelected($event)... >
  1. Add the optionActivated function and global var
autocompleteTagsOptionActivated = false;

  optionActivated(event: MatAutocompleteActivatedEvent) {
    if (event.option) {
      this.autocompleteTagsOptionActivated = true;
    }
  }
  1. Add a accessor for the MatAutocompleteTrigger of your input
@ViewChild('autoInput', { read: MatAutocompleteTrigger }) matAutocompleteTrigger: MatAutocompleteTrigger;

  1. Alter your inputTokenEnd to only process the input if there tags weren't activated and conditionally close the autocomplete
  inputTokenEnd(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim() && !this.autocompleteTagsOptionActivated) {   
      this.value.push(value.trim());
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }
    this.controlsForm.autocomplete.setValue(null);

    // If no autocomplete options were activated, then issue the command to close the panel.  The enter key that
    // triggers the input will inadventantly open the panel
    if (!this.autocompleteTagsOptionActivated) {
      this.matAutocompleteTrigger.closePanel()
    } 
  }
  1. Alter your optionSelected to reset the activation var

  optionSelected(event: MatAutocompleteSelectedEvent): void {
    this.autocompleteTagsOptionActivated = false;

    ...
  }

Upvotes: 0

ama
ama

Reputation: 1595

While the solution presented by André Dias works when you have a strict selection choice, it won't do if you need to add a substring of one in the selection (think of "java" when you have only "javascript" in autocomplete). For this also to work you can use the optionActivated event with a global variable (this does not solve completely the problem, you still have the issue if you select with the mouse). Below an example. The html part:

        <mat-autocomplete
          #auto="matAutocomplete"
          (optionActivated)="optionActivated($event)"
          (optionSelected)="selectedTag($event)">
          <mat-option *ngFor="let tag of filteredTags | async" [value]="tag">
            {{ tag }}
          </mat-option>
        </mat-autocomplete>

The component part:

  autocompleteTagsOptionActivated = false;

  optionActivated($event: MatAutocompleteActivatedEvent) {
    if ($event.option) {
      this.autocompleteTagsOptionActivated = true;
    }
  }

and then check the boolean variable and set it to false when actually added from keyboard selection:

  addTag(event: MatChipInputEvent): void {
    const input = event.input;
    const value = event.value;

    if ((value || '').trim()  && !this.autocompleteTagsOptionActivated) {
      this.formArrayTags.push(this.formBuilder.control(value.trim().toLowerCase()));
    }

    // Reset the input value
    if (input) {
      input.value = '';
    }

    this.tagsControl.setValue(null);
    this.formArrayTags.markAsDirty();
  }
  selectedTag(event: MatAutocompleteSelectedEvent): void {
    this.formArrayTags.push(this.formBuilder.control(event.option.viewValue));
    this.tagInput.nativeElement.value = '';
    this.tagsControl.setValue(null);
    this.autocompleteTagsOptionActivated = false;
  }

Upvotes: 1

Andr&#233; Dias
Andr&#233; Dias

Reputation: 173

Regarding the selection event, when you press ENTER, both the matChipInputTokenEnd, from your input, and the optionSelected, from mat-autocomplete, will fire. Normally, this happens with the optionSelected first, so that when the input event fires, the chip will already be added and the input will have no value to add. This is the reason why you don't get this issue by clicking with your mouse, since only the optionSelected event will be fired.

Now I said normally because I've also been getting this problem on a module imported component. If this is your case, this is probably a bug.

However, I did find a quick solution. What I did was check if the mat-autocomplete dialog was open and prevent the mat-chip-input from adding a new element. Checking for a selected item on the options list is also a possibility but it's less performant and not the behavior I was looking for.

Hope this helps:

@ViewChild('chipAutocomplete') chipAutocomplete: MatAutocomplete;

addElement(event: MatChipInputEvent) {
  if ((event.value || '').trim() && !this.chipAutocomplete.isOpen) {
    this.value.push({
      name: event.value.trim()
    });
  }

  if (event.input) {
    event.input.value = '';
  }

  this.chipInputControl.setValue(null);
}

Upvotes: 6

Related Questions