Skip to content

bug(mat-autocomplete): Dropdown Option <mat-option> Not Visually Selected After Setting Value with patchValue in mat-autocomplete #30154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
1 task
naithagoni opened this issue Dec 9, 2024 · 3 comments
Labels
needs triage This issue needs to be triaged by the team

Comments

@naithagoni
Copy link

naithagoni commented Dec 9, 2024

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

Steps to Reproduce:

  1. Open the application and navigate to the stepper component.
  2. Select a user in Step 1.
  3. Proceed to Step 2, where the getProducts method sets the value of the productCtrl control programmatically using patchValue.
  4. Open the autocomplete dropdown for the product selection.

Code Snippet:

<mat-stepper
  linear
  #stepper
  (selectedIndexChange)="selectedStepperIndex.set($event)"
>
  <mat-step [stepControl]="userFormGroup">
    <form class="example-form" [formGroup]="userFormGroup">
      <ng-template matStepLabel>Step 1</ng-template>
      @defer (when selectedStepperIndex() === 0; prefetch on idle) {
      <user [form]="userFormGroup"></user>
      }
      <div>
        <button mat-button matStepperNext>Next</button>
      </div>
    </form>
  </mat-step>
  <mat-step [stepControl]="productFormGroup">
    <form [formGroup]="productFormGroup">
      <ng-template matStepLabel>Step 2</ng-template>
      @defer (when selectedStepperIndex() === 1; prefetch on idle) {
      <product
        [form]="productFormGroup"
        [selectedUserId]="userFormGroup.controls.userCtrl.value?.id ?? ''"
      ></product>
      }
      <div>
        <button mat-button matStepperPrevious>Back</button>
        <button mat-button matStepperNext>Next</button>
      </div>
    </form>
  </mat-step>
  <mat-step>
    <ng-template matStepLabel>Done</ng-template>
    <p>You are now done.</p>
    <div>
      <button mat-button matStepperPrevious>Back</button>
      <button mat-button (click)="stepper.reset()">Reset</button>
    </div>
  </mat-step>
</mat-stepper>


import { Component, inject, signal } from '@angular/core';
import {
  FormBuilder,
  Validators,
  FormsModule,
  ReactiveFormsModule,
  FormControl,
  FormGroup,
} from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatStepperModule } from '@angular/material/stepper';
import { MatButtonModule } from '@angular/material/button';
import { UserComponent } from 'src/user/user.component';
import { ProductComponent } from 'src/product/product.component';

export interface User {
  id: string;
  name: string;
}

export interface Product {
  id: string;
  desc: string;
}

export interface UserForm {
  userCtrl: FormControl<User | null>;
}

export interface ProductForm {
  productCtrl: FormControl<Product | null>;
}

@Component({
  selector: 'stepper-editable-example',
  templateUrl: 'stepper-editable-example.html',
  styleUrl: 'stepper-editable-example.css',
  imports: [
    MatButtonModule,
    MatStepperModule,
    FormsModule,
    ReactiveFormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    UserComponent,
    ProductComponent,
  ],
})
export class StepperEditableExample {
  private _formBuilder = inject(FormBuilder);
  selectedStepperIndex = signal(0);

  userFormGroup: FormGroup<UserForm> = this._formBuilder.group({
    userCtrl: [null as User | null, Validators.required],
  });

  productFormGroup: FormGroup<ProductForm> = this._formBuilder.group({
    productCtrl: [null as Product | null, Validators.required],
  });
}


<mat-form-field class="example-full-width">
<mat-label>User</mat-label>
<input
  type="text"
  placeholder="Pick one"
  matInput
  [formControl]="form.controls.userCtrl"
  [matAutocomplete]="auto"
/>
<mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
  @for (user of users; track user) {
  <mat-option [value]="user">{{ user.name }}</mat-option>
  }
</mat-autocomplete>
</mat-form-field>


import { Component, Input } from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { User, UserForm } from 'src/example/stepper-editable-example';

@Component({
  selector: 'user',
  templateUrl: 'user.component.html',
  styleUrl: 'user.component.css',
  imports: [
    FormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    ReactiveFormsModule,
  ],
})
export class UserComponent {
  @Input() form: FormGroup<UserForm>;
  users: User[] = [
    {
      id: '1',
      name: 'John',
    },
    {
      id: '2',
      name: 'David',
    },
    {
      id: '3',
      name: 'Smith',
    },
  ];

  displayFn(user: User): string {
    return user ? user.name : '';
  }
}


<div style="display: flex; flex-direction: column;">
<mat-form-field class="example-full-width">
  <mat-label>Product</mat-label>
  <input
    type="text"
    placeholder="Pick one"
    matInput
    [formControl]="form.controls.productCtrl"
    [matAutocomplete]="autoProduct"
  />
  <mat-autocomplete
    #autoProduct="matAutocomplete"
    [displayWith]="displayProductFn"
  >
    @for (product of products; track product) {
    <mat-option [value]="product">{{ product.desc }}</mat-option>
    }
  </mat-autocomplete>
</mat-form-field>
</div>


import {
  Component,
  Injectable,
  Input,
  OnChanges,
  OnInit,
  SimpleChanges,
} from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import {
  Product,
  ProductForm,
  User,
} from 'src/example/stepper-editable-example';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';

@Component({
  selector: 'product',
  templateUrl: 'product.component.html',
  styleUrl: 'product.component.css',
  imports: [
    FormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatAutocompleteModule,
    ReactiveFormsModule,
  ],
})
export class ProductComponent implements OnInit {
  @Input() form: FormGroup<ProductForm>;
  private _selectedUserId: string = '';
  private isFirstChange = true;
  @Input()
  get selectedUserId(): string {
    return this._selectedUserId;
  }
  set selectedUserId(value: string) {
    if (this._selectedUserId !== value) {
      this._selectedUserId = value;
      if (!this.isFirstChange) {
        console.log('SETTER INSIDE IF...');
        this.onSelectedUserIdChange();
      }
      this.isFirstChange = false;
    }
  }
  products: Product[] = [];

  constructor(private productService: ProductService) {}

  ngOnInit() {
    if (this.selectedUserId) {
      this.getProducts();
    }
  }

  displayProductFn(product: Product): string {
    return product ? product.desc : '';
  }

  getProducts() {
    this.productService
      .getProductsService(this.selectedUserId ? this.selectedUserId : '')
      .subscribe((products) => {
        this.products = products;
        this.form.patchValue({
          productCtrl: products[1],
        });
      });
  }

  private onSelectedUserIdChange(): void {
    this.form.controls.productCtrl.setValue(null);
    this.getProducts();
  }
}

@Injectable({ providedIn: 'root' })
export class ProductService {
  private sub = new BehaviorSubject<Product[]>([]);

  private productMap: { [key: string]: Product[] } = {
    '1': [
      {
        id: '1',
        desc: 'One',
      },
      {
        id: '2',
        desc: 'Two',
      },
      {
        id: '3',
        desc: 'Three',
      },
    ],
    '2': [
      {
        id: '1',
        desc: 'Four',
      },
      {
        id: '2',
        desc: 'Five',
      },
      {
        id: '3',
        desc: 'Six',
      },
    ],
    '3': [
      {
        id: '1',
        desc: 'Seven',
      },
      {
        id: '2',
        desc: 'Eight',
      },
      {
        id: '3',
        desc: 'Nine',
      },
    ],
  };

  public observable$: Observable<Product[]> = this.sub.asObservable();
  constructor(private httpClient: HttpClient) {}

  getProductsService(userId: string): Observable<Product[]> {
    const products = this.productMap[userId] || [];
    return of(products);
    // return this.httpClient.get<Product[]>(http://api/${userId});
  }
}

Reproduction

StackBlitz link: mat-autocomplete with mat-stepper

Expected Behavior

The <mat-option> corresponding to the value set by patchValue should visually appear as selected in the autocomplete dropdown when the component is loaded.

Image

Actual Behavior

When setting the value of the product form control via patchValue inside the getProducts method, the form control updates its value correctly. However, the corresponding <mat-option> in the autocomplete dropdown does not visually show as selected on the initial load of the component.

Image

Environment

  • Angular: 18.2.9
  • CDK/Material: 18.2.10
  • Browser(s): Google Chrome
  • Operating System (e.g. Windows, macOS, Ubuntu):
@naithagoni naithagoni added the needs triage This issue needs to be triaged by the team label Dec 9, 2024
@naithagoni naithagoni changed the title bug(mat-autocomplete): Dropdown Option <mat-option> Not Visually Selected After Setting Value with patchValue in **mat-autocomplete** bug(mat-autocomplete): Dropdown Option <mat-option> Not Visually Selected After Setting Value with patchValue in mat-autocomplete Dec 9, 2024
@kai1992cool
Copy link

@crisbeto
Copy link
Member

Duplicate of #29422.

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Jan 10, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
needs triage This issue needs to be triaged by the team
Projects
None yet
Development

No branches or pull requests

3 participants