在 mat-autocomplete 中使用 `patchValue` 设置值后,下拉选项 `<mat-option>` 未在视觉上被选中

问题描述 投票:0回答:1

当前行为: 当通过

patchValue
方法内的
getProducts
设置产品表单控件的值时,表单控件会正确更新其值。但是,自动完成下拉列表中相应的
<mat-option>
不会在视觉上显示为在组件初始加载时选择的。 enter image description here

预期行为: 加载组件时,与

<mat-option>
设置的值相对应的
patchValue
应在视觉上显示为在自动完成下拉列表中选择。 enter image description here

重现步骤:

  1. 打开应用程序并导航至步进器组件。
  2. 在步骤 1 中选择用户。
  3. 继续执行步骤 2,其中
    getProducts
    方法使用
    productCtrl
    以编程方式设置
    patchValue
    控件的值。
  4. 打开产品选择的自动完成下拉列表。

代码片段:

<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});
  }
}

环境:

  • 角度:
    18.2.9
  • 角材质:
    18.2.10

StackBlitz 链接:https://stackblitz.com/edit/zzialkdj-d8rtr2sa

angular angular-material mat-autocomplete mat-stepper
1个回答
0
投票

您正在使用 autocomplete 而不是 select

使用自动完成功能,当选择一个值时,列表应仅显示所选值。这意味着您实现的逻辑不正确,因为您缺少必要的处理,如官方文档所示:

filteredOptions: Observable<Product[]>;
private _filter(...

如果您想要所描述的行为,您应该考虑使用选择而不是自动完成,因为它更适合您的要求。

<mat-select [formControl]="form.controls.productCtrl">
  @for (product of products; track product) {
    <mat-option [value]="product">{{ product.desc }}</mat-option>
  }
</mat-select>
© www.soinside.com 2019 - 2024. All rights reserved.