Angular 13中REST API填充Autocomplete选项异常:点击无显示输入后出现
Angular Material Autocomplete 点击输入框不显示选项的修复方案
问题描述
使用Angular Material的Autocomplete组件加载REST API中的选项时,点击输入框选项列表为空,只有输入内容后才会显示选项。通过getData()函数从API获取数据填充options数组,推测问题源于options是普通数组,而filteredOptions作为Observable的创建时机早于数据填充,导致初始点击时无数据。预期点击输入框即可看到完整选项列表。
原代码
HTML:
<form class="example-form"> <mat-form-field class="example-full-width" appearance="fill"> <mat-label>Number</mat-label> <input type="text" placeholder="Pick one" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto"> <mat-autocomplete autoActiveFirstOption #auto="matAutocomplete"> <mat-option *ngFor="let option of filteredOptions | async" [value]="option"> {{option}} </mat-option> </mat-autocomplete> </mat-form-field> </form>
TypeScript:
import { Component, OnInit} from '@angular/core'; import { FormControl} from '@angular/forms'; import { Observable } from 'rxjs'; import { map, startWith} from 'rxjs/operators'; import { AuthenticationService, Products_Header_VW } from '...'; @Component({ selector: 'app-test-second', templateUrl: './test-second.component.html', styleUrls: ['./test-second.component.scss'] }) export class TestSecondComponent implements OnInit { myControl = new FormControl(''); options: string[] = []; filteredOptions!: Observable<any>; constructor(private auth: AuthenticationService) { this.getData().subscribe(x => { this.options = x; }); } ngOnInit() { this.filteredOptions = this.myControl.valueChanges.pipe( startWith(''), map(value => this._filter(value || '')), ); } private _filter(value: string): string[] { const filterValue = value; return this.options.filter(option => option.includes(filterValue)); } //**** 获取所有产品数据并转换为字符串数组 **** private getData(): Observable<string[]> { return this.auth.getallproductheadersvw() .pipe( map( x => x.data.map((y:Products_Header_VW) => y.Product_Title_Persian) ) ) } }
问题原因
filteredOptions在ngOnInit中初始化,仅监听myControl.valueChanges流,初始startWith('')触发过滤时,options还是空数组(API请求异步未返回)。- 当API数据返回更新
options数组时,filteredOptions不会重新触发计算,因为它不监听options的变化。
修复方案
方案一:用BehaviorSubject管理选项数据(推荐)
通过BehaviorSubject包装选项数据,让filteredOptions同时监听表单值变化和选项数据的更新,确保数据同步。
修改后的TypeScript代码:
import { Component, OnInit} from '@angular/core'; import { FormControl} from '@angular/forms'; import { Observable, BehaviorSubject, combineLatest} from 'rxjs'; import { map, startWith} from 'rxjs/operators'; import { AuthenticationService, Products_Header_VW } from '...'; @Component({ selector: 'app-test-second', templateUrl: './test-second.component.html', styleUrls: ['./test-second.component.scss'] }) export class TestSecondComponent implements OnInit { myControl = new FormControl(''); // 用BehaviorSubject存储选项数据,初始为空数组 private optionsSubject = new BehaviorSubject<string[]>([]); // 对外暴露Observable,避免直接修改内部数据 options$ = this.optionsSubject.asObservable(); filteredOptions!: Observable<string[]>; constructor(private auth: AuthenticationService) {} ngOnInit() { // 合并表单值变化和选项数据的流,任一变化都重新过滤 this.filteredOptions = combineLatest([ this.myControl.valueChanges.pipe(startWith('')), this.options$ ]).pipe( map(([value, options]) => this._filter(value || '', options)) ); // 请求API数据并更新BehaviorSubject this.getData().subscribe(x => { this.optionsSubject.next(x); }); } // 调整过滤函数,接收当前的选项列表参数 private _filter(value: string, options: string[]): string[] { const filterValue = value; return options.filter(option => option.includes(filterValue)); } private getData(): Observable<string[]> { return this.auth.getallproductheadersvw() .pipe( map(x => x.data.map((y:Products_Header_VW) => y.Product_Title_Persian)) ); } }
方案二:数据返回后初始化filteredOptions
如果不需要后续更新选项数据,可在API数据返回后再创建filteredOptions,确保初始过滤时已有完整数据:
修改后的TypeScript代码:
import { Component, OnInit} from '@angular/core'; import { FormControl} from '@angular/forms'; import { Observable } from 'rxjs'; import { map, startWith} from 'rxjs/operators'; import { AuthenticationService, Products_Header_VW } from '...'; @Component({ selector: 'app-test-second', templateUrl: './test-second.component.html', styleUrls: ['./test-second.component.scss'] }) export class TestSecondComponent implements OnInit { myControl = new FormControl(''); options: string[] = []; filteredOptions!: Observable<any>; constructor(private auth: AuthenticationService) {} ngOnInit() { this.getData().subscribe(x => { this.options = x; // 数据返回后再初始化filteredOptions this.filteredOptions = this.myControl.valueChanges.pipe( startWith(''), map(value => this._filter(value || '')), ); }); } private _filter(value: string): string[] { const filterValue = value; return this.options.filter(option => option.includes(filterValue)); } private getData(): Observable<string[]> { return this.auth.getallproductheadersvw() .pipe( map(x => x.data.map((y:Products_Header_VW) => y.Product_Title_Persian)) ); } }
效果说明
两种方案都能实现:点击输入框时,filteredOptions会基于完整的API返回数据执行过滤(空值匹配所有选项),从而显示全部选项列表。方案一更适合需要动态更新选项数据的场景,方案二更简洁适用于一次性加载数据的场景。
内容的提问来源于stack exchange,提问作者Blue Moon
相关产品推荐
相关产品推荐

