首页 > 解决方案 > 如何阻止材料下拉自动完成选择在 Angular 8/9 中触发另一个搜索查询?

问题描述

摘要:我有一个按您输入的字段下拉搜索。当我从下拉列表中选择时,它很乐意将字段设置为我在搜索中收到的对象,但不高兴地注册了该更改并将整个对象发送出去进行搜索。

HTML:

<form [formGroup]="myForm" novalidate>
  <mat-form-field>
    <input matInput 
    placeholder="SKU / Item Number" 
    [matAutocomplete]="auto" 
    formControlName='itemName'>
  </mat-form-field> 
  <mat-autocomplete #auto="matAutocomplete" [displayWith]="parseDropDownSelection">
      <mat-option 
            *ngFor="let row of searchQueryResult" 
            [value]="row" 
            (onSelectionChange)="onItemSelection($event)">
        <span>{{ row.Name }}</span>
      </mat-option>
  </mat-autocomplete>   
</form>

设置:

import {FieldSearchServiceItem}     from './../services/field-search.service';

constructor(
    private dialog: MatDialog,
    private formBuilder: FormBuilder,
    private http: HttpClient,
    private appServiceItem: FieldSearchServiceItem,    
    )   {}

ngOnInit()

ngOnInit(){
    this.myForm = this.formBuilder.group
        ({
        itemName: '',
        });

this.myForm
  .get('itemName')
  .valueChanges
  .pipe(
    debounceTime(200),
    switchMap(value => this.appServiceItem.search({name: value}, 1))
    )
  .subscribe(serviceResult => this.searchQueryResult = serviceResult.qResult);
}

服务:

@Injectable()
export class FieldSearchServiceItem 
  {
  constructor(private http: HttpClient) {}
  search(filter: {name: string} = {name: ''}, page = 1): Observable<apiQueryResponseForItem> 
    {
    var queryResponse;
    return this.http.get<apiQueryResponseForItem>(`example.com/search/item/${filter.name}`)
    .pipe(
      tap((response: apiQueryResponseForItem) => 
        {
        response.qResult = response.qResult
          .map(unit => new ItemUnit(unit.Id, unit.Name))
        return response;
        })
      );
    }
  }

类定义:

export class ItemUnit
    {
    constructor
        (
        public Id:number,
        public Name:string,
        )   {}
    }

export interface apiQueryResponseForItem
    {
    qSuccess: boolean;
    qResult: ItemUnit[];        
    }

我已经看到其他答案,解决方案是在设置值时使用 emitEvent:false ,如下所示:

this.myForm.get('itemName').patchValue('theDataThatCameBackFromSeach', {emitEvent:false})

这是有道理的……但我觉得解决方案与这种可观察/可注射/材料方法不匹配……主要是因为我没有使用执行 .setValue() 或 .patchValue() 的调用,我猜在 Material 东西的某个地方有一个绑定来处理它。

服务器最终看到这样的调用:

http://example.com/search/item/a                   (when the letter a is typed)
http://example.com/search/item/[object%20Object]   (after clicking the dropdown, JS tries to search 'object' after clumsily falling back to string representation )

onItemSelection()目前没有参与,它正在工作,但除了转储到控制台之外什么也没做。.qResult从我的服务返回的内容searchQueryResult包含{Id:number,Name:string}. 我怎样才能让自动完成的字段设置操作仍然完成设置字段的工作,但在这样做时创建更改事件,同时仍然尊重onItemSelection()我可以完成其他处理?

标签: angularangular-material

解决方案


我前段时间遇到过同样的问题,这是我在系统中用于多个输入的最终解决方案。

步骤是:

  1. 意识到这里实际上有两个事件。已键入的内容和已选择的值
  2. 为这两个事件创建两个 observables 并分别处理它们。
class Component {
  ngOnInit() {

    /*       SEPARATE THE EVENTS      */

    // If it's an object, it's been selected.
    // I also don't allow selecting `null` but it's up to you.
    const itemSelected$ = this.myForm.get('itemName').valueChanges.pipe(
      filter(val => typeof val === 'object' && val !== null),
    );
    // If it's a string, it's been typed into the input
    const itemTyped$ = this.myForm.get('itemName').valueChanges.pipe(
      filter(val => typeof val === 'string'),
    );

    /*       HANDLE ITEM SELECTED     */

    itemSelected$.subscribe(item => {
      // If you want, you can also handle "fake" items here.
      // I use this trick to show a placeholder like "Create New Item" in the dropdown
      if (item.id === 0 && item.name === 'Create New Item') {
        this.createNewItem().subscribe(
          newItem => this.myForm.get('itemName').setValue(newItem),
        );
        return;
      }
      // I use this in a custom input component (with ControlValueAccessor)
      // So this is where I notify the parent
      this.onChange(item);
    });

    /*       HANDLE ITEM TYPED        */

    const searchQueryResult$ = itemTyped$.pipe(
      debounce(200),
      tap(value => {/* you could handle starting a loading spinner or smth here */}),
      switchMap(name => this.appServiceItem.search({name}, 1)),
    );

    // now you can either use searchQueryResult$ with async pipe:
    // this.filteredResults$ = searchQueryResult$;
    // or subscribe to it and update a field in your component class:
    searchQueryResult$.subscribe(result => this.searchQueryResult = result);
    // If you subscribe, don't forget to clean up subscriptions onDestroy
  }
}

我冒昧地添加了一些建议和技巧,但您明白了 - 创建两个独立的互斥可观察对象并分别处理它们。


推荐阅读