【问题标题】:Angular Material Autocomplete observable fires additional time after selecting dropdown option选择下拉选项后,Angular Material Autocomplete observable 会触发额外的时间
【发布时间】:2019-11-12 07:20:41
【问题描述】:

每当我从 Material Autocomplete 中选择一个选项时,它都会对服务器进行额外的 HTTP GET 调用。选择下拉选项的预期结果应该只是填充输入。

下拉选项是从服务器动态检索的。

HTML

<form class="example-form">
    <mat-form-field class="example-full-width">
      <input type="text" placeholder="search item" aria-label="Item1" matInput [formControl]="searchTerm" [matAutocomplete]="auto1">
      <mat-autocomplete #auto1="matAutocomplete" [displayWith]="displayFn">
        <mat-option *ngFor="let item of searchResult" [value]="item.Id">
          {{ item.Name }}
        </mat-option>
      </mat-autocomplete>
    </mat-form-field>
  </form>

TypeScript

    searchTerm : FormControl = new FormControl();

    searchResult = [];
    constructor(private service: AppService){
      this.searchTerm.valueChanges
          .debounceTime(400) 
          .subscribe(data => {
              this.service.search_word(data).subscribe(response =>{
                  this.searchResult = response
              })

其他一切正常。唯一的问题是选择自动完成选项时对服务器的意外额外调用。

快速更新:尚未完全解决此问题。但是,我已将其范围缩小到可能是 displayWith 的问题。

【问题讨论】:

  • 为什么会出乎意料?它改变了价值不是吗?
  • 您已订阅valueChanges。选择该选项会更改文本字段的值,我错了吗?您可能需要添加.distinctUntilChanged()
  • 我得到:[ts] 属性 'distinctUntilChanged' 在类型 'FormControl' 上不存在。
  • 添加import 'rxjs/add/operators/distinctUntilChanged';。此外,它需要像valueChanges.distinctUntilChanged() 一样应用
  • 其实这行不通。我得到了和以前一样的功能。

标签: angular angular-material observable


【解决方案1】:

出现您的问题是因为下拉选择更改了searchTerm 表单控件的值;从而为该表单控件上的 valueChanges observable 发出一个事件。

有几种方法可以在表单控件上的 valueChanges observable 中构建一些逻辑,以忽略这个不需要的 Http GET 请求。

简单的解决方案

天真的解决方案是存储下拉选项选择值,如果selectionValue === searchTerm.value,则跳过可观察的逻辑。

var optionSelection; // set this value on selection of a dropdown option
this.searchTerm.valueChanges
  .debounceTime(400) 
  .subscribe(data => {
    if (optionSelection !== searchTerm.value) {
      this.service.search_word(data).subscribe(response =>{
        this.searchResult = response
      })
    }
  })

更好的解决方案

FormControl 类有一个setValue 函数,它有一个可选参数来指定是否向valueChanges observable 发出事件。

setValue(value: any, options: {
    onlySelf?: boolean;
    emitEvent?: boolean;
    emitModelToViewChange?: boolean;
    emitViewToModelChange?: boolean;
} = {}): void

通过更改下拉选项选择以使用 setValue 设置 searchTerm 表单控件值,您可以传递可选的 emitEvent: false 参数并阻止此额外的可观察事件发出。

searchTerm.setValue(dropdownValue, { emitEvent: false });

如果 emitEvent 为 false,此更改将导致 FormControl 上的 valueChanges 事件不被发出。这默认为 true(因为它属于 updateValueAndValidity)。

【讨论】:

  • 当我添加这个时,我得到:[ts] 找不到名称 'dropdownValue' ...我需要将 dropdownValue 更改为什么吗?
  • 是的,它应该是您的下拉选项选择的值。也许 searchResult[i].value
  • 对不起,我认为我没有完全理解(Angular 新手)。我会将 setValue 代码放在哪里?它会取代我的 ValueChanges 代码吗?
  • 谢谢。它帮助我意识到 formgroup.patchValue 也有这个 emitEvent 标志,这解决了我的问题。 (我没有使用材料,但有类似的问题)
【解决方案2】:

问题在于触发的事件数量有两个......我已经为它制作了一个错误,但它应该如何工作。希望这会有所帮助。

这是我的解决方案:

HTML:

<mat-option *ngFor="let item of searchResult" [value]="item.Id" (onSelectionChange)="onSelectionChange($event, item.Id)"> // or item

在您的组件中:

private onSelectionChange(_event: any, _id: any) {
  if (_event.isUserInput === true) { // there are two events one with true and one with false;
    this.service.search_word(_id).subscribe(response =>{
         this.searchResult = response
    })
}

【讨论】:

【解决方案3】:

您可以在输入文本中使用(keyup)="onKeyUp($event)",您可以像这样使用它

onKeyUp(event: any) {
   this.service.search_word(event.target.value).subscribe(response =>{
                  this.searchResult = response
              })
}

【讨论】:

    【解决方案4】:

    从 Angular Material 7.2.0 版开始,您有一个名为“optionSelected”的新 EventEmitter,用于 MatAutocomplete

    所以你的 HTML 应该是这样的:

    <mat-autocomplete (optionSelected)="optionSelected($event)" #auto1="matAutocomplete" [displayWith]="displayFn">
        <mat-option *ngFor="let item of searchResult" [value]="item.Id">
          {{ item.Name }}
        </mat-option>
      </mat-autocomplete>
    

    还有你的 .ts:

    private optionSelected(event) {
      console.log(event)
    }
    

    您可以在 optionSelected() 方法中处理它。

    试一试,因为我没有你的所有代码。如果它不起作用,请告诉我。

    【讨论】:

      【解决方案5】:

      这是一个比较棘手的方法,将选项值设置为object而不是string,然后当输入发生变化时,您可以确定是输入还是选择。

      <!-- Template -->
      <mat-option *ngFor="let item of searchResult" [value]="item">
      
      // Component
      this.searchTerm.valueChanges.subscribe(modelValue => {
        if (typeof modelValue === 'string') {
          this.searchUser.next(modelValue.toString());
        } else {
          this.searchTerm.setValue(modelValue.Id, {
            emitEvent: false
          });
        }
      });
      

      【讨论】:

        【解决方案6】:

        您可以使用keyup 代替valueChanges。下面是我如何解决同样的问题。请记住,这个例子是为了展示我使用mat-autocomplete的keyup用例。

        import { ViewChild, ElementRef } from '@angular/core';
        import { fromEvent } from 'rxjs';
        import { debounceTime, tap, switchMap, map, filter, distinctUntilChanged } from 'rxjs/operators';
        

        添加mat-autocomplete html,注意这还包括远程http调用期间的加载微调器,但可以删除。

         <mat-form-field>
           <input matInput #searchInput placeholder="" [matAutocomplete]="auto">             
           <mat-autocomplete #auto="matAutocomplete">
            <mat-option *ngIf="isLoading">
             <mat-spinner diameter="50"></mat-spinner>
            </mat-option>
            <ng-container *ngIf="!isLoading">
             <mat-option *ngFor="let data of filteredData | async" [value]="data.modelValue">
              <span>{{ data.modelValue }}</span>
             </mat-option>
            </ng-container>
           </mat-autocomplete>
          </mat-form-field>
        

        将以下内容添加到您的控制器中。

        isLoading: boolean;
        @ViewChild('searchInput') searchInput: ElementRef;
        filteredData: searchModel[] = [];
        

        设置keyup 事件监听器。

        fromEvent(this.searchInput.nativeElement, 'keyup').pipe(
          map((event: any) => {
            return event.target.value; // Map search input value
          }),
          filter(res => res.length > 3), // If character length greater then 3
          debounceTime(500),
          distinctUntilChanged(), // If previous search is different from current
          tap((val) => {
            this.isLoading = true;
            this.filteredData = [];
          }),
          switchMap(value =>
            this.service.search(value).pipe(
              finalize(() => this.isLoading = false)
            )
        
          )
        ).subscribe(data => {
          this.filteredData = data;
        });
        

        【讨论】:

          猜你喜欢
          • 2020-11-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-01-14
          • 2019-04-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多