当前行为: 当通过
patchValue
方法内的 getProducts
设置产品表单控件的值时,表单控件会正确更新其值。但是,自动完成下拉列表中相应的 <mat-option>
不会在视觉上显示为在组件初始加载时选择的。
预期行为: 加载组件时,与
<mat-option>
设置的值相对应的 patchValue
应在视觉上显示为在自动完成下拉列表中选择。
重现步骤:
getProducts
方法使用 productCtrl
以编程方式设置 patchValue
控件的值。代码片段:
<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
您正在使用 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>