components icon indicating copy to clipboard operation
components copied to clipboard

bug(MAT-AUTOCOMPLETE): on option select on chrome android 13, dropdown collapses without selection

Open mattiLeBlanc opened this issue 1 month ago • 4 comments

Is this a regression?

  • [ ] Yes, this behavior used to work in the previous version

The previous version in which this bug was not present was

No response

Description

I have am having a bit of an issue with a mat-autocomplete. I have deployed the code base from https://stackblitz.com/run?file=src%2Fexample%2Fautocomplete-require-selection-example.html&startScript=start to http://resparke-indigo-backup.s3-website-ap-southeast-2.amazonaws.com/ so I can test it on my Android 13 device. On Android 10, IOS Safari 17, 18 etc, macbook Chrome, it all works good. But not on Android 13' Chrome.

What I can find from debugging is that when I click it sometimes doesnt select a value but just collapses the soft keyboard.

I created a recording with onclick debug output so you can see which element is clicked:

Image

I am not sure how to debug this, because the example I am using is pure and simple and 100% from angular mat website. I think the issues is related to the softkeyboard closing which does something with the overlay.

Reproduction

As mentioned above, the stackblitz example is deployed on https://app-test.resparke.com but you would have to run it on an Android 13 tablet , Chrome, with a soft keyboard and see this issue happening

Expected Behavior

When option is selected, I expect that selection to be recorded.

Actual Behavior

When I open the test app on http://resparke-indigo-backup.s3-website-ap-southeast-2.amazonaws.com/, and I click on the formfield, the dropdown appears and the softkeyboard opens, I select option 2. The soft keyboard closes and no option is selected. When I click again on the formfield, the dropdown appears again and now I can select and option. That seems to be a consistent behaviour on Android 13/Chrome.

Environment

  • Angular: 20.3.x
  • CDK/Material: 202.2.12
  • Browser(s): Chrome
  • Operating System (e.g. Windows, macOS, Ubuntu): Android 13

mattiLeBlanc avatar Nov 07 '25 02:11 mattiLeBlanc

Further investigation: Chatgpt suggested to cancel all events on the mat-option:

<mat-option [value]="option.value"
        (mousedown)="$event.preventDefault()"
        (touchstart)="$event.preventDefault()"
        (pointerdown)="$event.preventDefault()">
          {{option.viewValue}}
        </mat-option>

which does make the autocomplete select the value on Android 13, but it no longer selects any value on other browsers which work with default code. This because all events are cancelled and I probably have to handle the select now manually.

mattiLeBlanc avatar Nov 07 '25 03:11 mattiLeBlanc

Apparently I should only cancel mousedown:

<mat-option [value]="option.value"
        (mousedown)="$event.preventDefault()"
        >

Selection now works for all browsers but keyboard doesnt close, so I have to handle this manually.

Overall, I do think there is a bug in the mat-autocomplete how it handles the option select in conjunction with they keyboard close event on Android 13.

mattiLeBlanc avatar Nov 07 '25 03:11 mattiLeBlanc

My working code is:

<div class="flex flex-col">
  <mat-form-field [appearance]="appearance" [attr.appearance]="appearance" [subscriptSizing]="subscriptSizing">
    <input
      #input
      type="text"
      matInput
      [placeholder]="placeholder"
      [formControl]="control"
      [matAutocomplete]="auto"
      (input)="filter()"
      (focus)="filter()"
      >
    <mat-autocomplete  #auto="matAutocomplete" [displayWith]="displayFn.bind(this)">
      @for (option of filteredOptions; track option.value) {
        <mat-option [value]="option.value"
        (mousedown)="$event.preventDefault()"
        (click)="selectOption(option)"
        >
          {{option.viewValue}}
        </mat-option>
      }
    </mat-autocomplete>
    <mat-icon matSuffix (click)="iconClick()">{{ icon }}</mat-icon>
    @if (!hideErrorMessage) {
      <mat-error>{{ getErrorMessages() }}</mat-error>
    }
  </mat-form-field>


</div>

import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule, MatAutocompleteSelectedEvent, MatAutocompleteTrigger } from '@angular/material/autocomplete';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input';
import { ViewValue } from '@resparke/models';
import { getErrorMessages } from '@resparke/utils/form';
import { requireMatch } from '../../validators';

@Component({
  selector: 'app-autocomplete',
  standalone: true,
  imports: [ReactiveFormsModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, MatIconModule],
  templateUrl: './autocomplete.component.html',
  styleUrls: ['./autocomplete.component.scss']
})
export class AutocompleteComponent implements OnInit {
  @ViewChild(MatAutocompleteTrigger) trigger!: MatAutocompleteTrigger;
  @ViewChild('input') input: ElementRef<HTMLInputElement>;
  @Input() options: ViewValue[];
  @Input() control: FormControl;
  @Input() creatable = false;
  @Input() icon = 'search';
  @Input() resetOnIconClick = false;
  @Input() hideErrorMessage: boolean = false;
  @Input() appearance: 'outline' | 'fill' = 'outline';
  @Input() subscriptSizing: 'fixed' | 'dynamic' = 'fixed';
  @Input() placeholder: string;
  filteredOptions: ViewValue[];
  getErrorMessages = () => getErrorMessages(this.control);

  ngOnInit(): void {
    this.filteredOptions = this.options.slice();

    if (!this.creatable) {
      // check if input matches the options list provided
      this.control.addValidators(requireMatch(this.options))
    }
  }

  selectOption(e: any) {
    this.trigger?.closePanel();
    // defer blur so selection finishes cleanly
    setTimeout(() => this.input.nativeElement.blur(), 0);
  }

  filter(): void {
    const filterValue = this.input.nativeElement.value.toLowerCase();
    this.filteredOptions = this.options.filter(o => o.viewValue.toLowerCase().includes(filterValue));
  }

  displayFn(value?: string) {
    const match = this.options.find(_ => _.value === value);
    return match ? match.viewValue : undefined;
  }

  iconClick() {
    if (this.resetOnIconClick) {
      this.control.reset();
    }
  }
}

So the selectOption was necessary to close the softkeyboard. I tried using optionSelected but that was not emitting any value. And the (mousedown)="$event.preventDefault()" on the mat-option helps selecting a value on Android 13 Chrome. I hope my investigation and problem solving allows for a fix in mat-autocomplete code so that I dont have to do these hacks?

mattiLeBlanc avatar Nov 07 '25 03:11 mattiLeBlanc

@adolgachev hi, is this something that can be resolved easily so I don't have to do anything extra in my code to resolve the issue?

mattiLeBlanc avatar Dec 02 '25 05:12 mattiLeBlanc