获取多个动态添加 Material 自动完成输入以在 valueChange 上调用相同的方法

Get Multiple Dynamically added Material Autocomplete input to call same method on valueChange

感谢阅读。

我有一个页面,用户可以在其中输入和 select 来自自动完成的地址。 自动完成的来源来自外部 API,它是使用 valueChanges 事件调用的。

结果行为是基于用户输入的预测地址查找。 这目前适用于这个单一目的。

<mat-form-field>
      <input matInput placeholder="Search Multi" aria-label="State" [matAutocomplete]="auto" [formControl]="searchMoviesCtrl" type="text">
      <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
        <mat-option *ngIf="isLoading" class="is-loading">Loading...</mat-option>
        <ng-container *ngIf="!isLoading">
          <mat-option *ngFor="let suggestion of filteredMovies" [value]="suggestion.text">
            <span><b>{{suggestion.text}}</b></span>
          </mat-option>
        </ng-container>
      </mat-autocomplete>
    </mat-form-field>

    <br>

  <ng-container *ngIf="errorMsg; else elseTemplate">
    {{errorMsg}}
  </ng-container>
  <ng-template #elseTemplate>
    <h5>Selected Value: {{searchMoviesCtrl.value}}</h5>
  </ng-template>
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';
import { debounceTime, tap, switchMap, finalize } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
  searchMoviesCtrl = new FormControl();
  filteredMovies: any;
  isLoading = false;
  errorMsg: string;

  constructor(private http: HttpClient) 
  { }

ngOnInit() {
    this.searchMoviesCtrl.valueChanges
      .pipe(
        debounceTime(500),
        tap(() => {
          this.errorMsg = "";
          this.filteredMovies = [];
          this.isLoading = true;
        }),
        switchMap(value => this.http.get("http://searchaddressapiurl?text=" + this.searchMoviesCtrl.value)
          .pipe(
            finalize(() => {
              this.isLoading = false
            }),
          )
        )
      )
      .subscribe(data => {
        console.log('Search text :' + this.searchMoviesCtrl.value);
        console.log(data);

        if (data['suggestions'] == undefined) {
          this.errorMsg = data['Error'];
          this.filteredMovies = [];
          console.log('coming here ERROR');
        } else {
          this.errorMsg = "";
          this.filteredMovies = data['suggestions'];
          console.log('coming here');
        }


        console.log(this.filteredMovies);
      });
  }

displayFn(suggestion : string) : string {
    console.log('Display - ' + suggestion);
    return suggestion ? suggestion : '';
  }
}

但是,我想允许用户添加额外的自动完成输入,这将 use/call 与 API 相同的值更改。

我该怎么做才能最好?

通过执行以下操作,我已经能够添加多个输入。 我只是不确定如何将这些输入连接到一个 valueChange 函数,该函数调用搜索 API 传递输入的文本...理想情况下是一个满足所有输入的函数

<form [formGroup]="autocompleteForm" novalidate >
        <div formArrayName="sites">
            <div *ngFor="let unit of autocompleteForm.controls.sites.controls; let i=index" class="form-group">
                <div [formGroupName]="i">
                    <div style="width: 100%;">

                    <mat-form-field>
                        <input matInput placeholder="Search Multi" aria-label="State" [matAutocomplete]="auto" formControlName="site" type="text" (input)="searchSite(this, i)">
                        <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
                        <mat-option *ngIf="isLoading" class="is-loading">Loading...</mat-option>
                        <ng-container *ngIf="!isLoading">
                            <mat-option *ngFor="let suggestion of filteredAddresses" [value]="suggestion.text">
                            <span><b>{{suggestion.text}}</b></span>
                            </mat-option>
                        </ng-container>
                        </mat-autocomplete>
                    </mat-form-field>

                    </div>

                </div>
            </div>
            <button (click)="addSite()">Add Site</button>
        </div>

    <ng-container *ngIf="errorMsg; else elseTemplate">
        {{errorMsg}}
    </ng-container>
    <ng-template #elseTemplate>
        <h5>Selected Value: {{site.value}}</h5>
    </ng-template>

</form>
ngOnInit(){
    this.autocompleteForm = this.formBuilder.group({
      sites: this.formBuilder.array([
         // load first row at start
         this.getSite()
      ])
    });

  displayFn(suggestion : string) : string {
    console.log('Display - ' + suggestion);
    return suggestion ? suggestion : '';
  }

  public searchSite(obj, index)
  {
    console.log(obj);
    console.log(index + ' - ' + obj);
  }

  private getSite() {
    return this.formBuilder.group({
      site: ['', [Validators.required]
    });
  }

  addSite() {
    const control = <FormArray>this.autocompleteForm.controls['sites'];
    control.push(this.getSite());
  }

更新

我更新了在输入更改时调用的 searchSite 方法... 它允许输入调用 searchSite 方法。

它完成了取回必要结果集的工作,但它似乎进行了许多不必要的 API 调用,我认为这是因为(输入)onchange 调用和 .valueChanges 事件连接。

目前仍在进行中,但只是提出了一些进展。 仍然欢迎提出想法。

public searchSite(obj : MultiAutoCompleteComponent, index)
  {
    console.log(obj);
    console.log(index + ' - ' + obj);
    console.log(obj.autocompleteForm.controls.sites.value[index].site);
    console.log('Input value : ' + obj.autocompleteForm.controls);

    var searchText = obj.autocompleteForm.controls.sites.value[index].site;

    if(searchText.length < 2 || searchText == '' || searchText == null || searchText == undefined || searchText === -1)
    {
      console.log('Minimum not provided');
      return;
    }

    obj.autocompleteForm.controls.sites.valueChanges
      .pipe(
        debounceTime(500),
        tap(() => {
          this.errorMsg = "";
          this.filteredAddresses = [];
          this.isLoading = true;
        }),
        switchMap(value => this.http.get("http://searchaddressapi?text=" + searchText)
          .pipe(
            finalize(() => {
              this.isLoading = false
            }),
          )
        )
      )
      .subscribe(data => {
        console.log('Search text :' + this.site.value);
        console.log(data);

        if (data['suggestions'] == undefined) {
          this.errorMsg = data['Error'];
          this.filteredAddresses = [];
          console.log('Search site coming here ERROR');
        } else {
          this.errorMsg = "";
          this.filteredAddresses = data['suggestions'];
          console.log('Search site coming here');
        }


        console.log(this.filteredAddresses);
      });

  }

更新 2

所以我做了一些更改,这导致自动完成按我的用例的预期工作。 (添加调用相同 API 的动态多自动完成)

只有一件事我仍然想解决...在每个字符输入上对 API 的调用。

我试图通过使用 debounceTime 来解决这个问题,但这似乎没有效果,它延迟了调用但它们仍然被调用(每个字符都是)而不是忽略在 debounceTime 期间发送的那些调用。至少那是我认为would/should发生的事情?

我对 deboundTime 行为的理解不正确吗?

import { Observable, Subject } from 'rxjs';

searchTextChanged = new Subject<string>();

public searchSite(obj : MultiAutoCompleteComponent, index)
  {
    console.log(obj);
    console.log(index + ' - ' + obj);
    console.log(obj.autocompleteForm.controls.sites.value[index].site);
    console.log('Input value : ' + obj.autocompleteForm.controls);

    var searchText = obj.autocompleteForm.controls.sites.value[index].site;

    const items = this.autocompleteForm.get('sites') as FormArray;
    console.log('No of sites added: ' + items.length);


    if(searchText.length < 5 || searchText == '' || searchText == null || searchText == undefined || searchText === -1)
    {
      console.log('Minimum not provided, no serarch conducted');
      return;
    }
    else
    { 
        this.searchTextChanged.next(searchText);

        this.searchTextChanged
          .pipe(
            debounceTime(1000),
            tap(() => {
              this.errorMsg = "";
              this.filteredAddresses = [];
              this.isLoading = true;
            }),
            switchMap(value => this.http.get("http://searchaddressapi?text=" + searchText)
              .pipe(
                finalize(() => {
                  this.isLoading = false
                }),
              )
            )
          )
          .subscribe(data => {
            console.log('Search text :' + searchText);
            console.log(data);

            if (data['suggestions'] == undefined) {
              this.errorMsg = data['Error'];
              this.filteredAddresses = [];
              console.log('Search site coming here ERROR');
            } else {
              this.errorMsg = "";
              this.filteredAddresses = data['suggestions'];
              console.log('Search site coming here');
            }


            console.log(this.filteredAddresses);
          });
    }

  }

我做错了什么。 debounceTime 延迟实现?

更新 3 - 已解决

我根据需要让它工作!

动态添加额外的自动完成,使用相同的 API 数据结果集。

debounceTime 减少了用户输入 searchText 时 API 调用的次数。

我相信你可以清理它,正如一位评论者所建议的那样,将 API 调用放在服务中,但无论如何它都在这里。

//multi-auto-complete.component.css
.example-form {
    min-width: 150px;
    max-width: 500px;
    width: 100%;
  }

  .example-full-width {
    width: 100%;
  }

//multi-auto-complete.component.html
<div>

<form [formGroup]="autocompleteForm" novalidate >
            <div formArrayName="sites">
                <div *ngFor="let unit of autocompleteForm.controls.sites.controls; let i=index" class="form-group">
                    <div [formGroupName]="i">
                        <div style="width: 100%;">

                        <mat-form-field>
                            <input matInput placeholder="Search Multi" aria-label="State" [matAutocomplete]="auto" formControlName="site" type="text" (input)="searchSite(this, i)">
                            <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn">
                            <mat-option *ngIf="isLoading" class="is-loading">Loading...</mat-option>
                            <ng-container *ngIf="!isLoading">
                                <mat-option *ngFor="let suggestion of filteredAddresses" [value]="suggestion.text">
                                <span><b>{{suggestion.text}}</b></span>
                                </mat-option>
                            </ng-container>
                            </mat-autocomplete>
                        </mat-form-field>

                        </div>

                    </div>
                </div>
                <button (click)="addSite()">Add Site</button>
            </div>

        <ng-container *ngIf="errorMsg; else elseTemplate">
            {{errorMsg}}
        </ng-container>
        <ng-template #elseTemplate>
            <h5>Selected Value: {{site.value}}</h5>
        </ng-template>

    </form>

</div>
//multi-auto-complete.component.ts
import { Component, OnInit, ɵConsole } from '@angular/core';
import { FormControl } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { Router } from '@angular/router';

import { debounceTime, tap, switchMap, finalize } from 'rxjs/operators';
import { FormGroup, FormBuilder, FormArray, Validators } from '@angular/forms';
import { Observable, Subject } from 'rxjs';


export interface Suggestion {
  text: string;
  magicKey: string;
  isCollection: boolean;
}

@Component({
  selector: 'app-multi-auto-complete',
  templateUrl: './multi-auto-complete.component.html',
  styleUrls: ['./multi-auto-complete.component.css']
})
export class MultiAutoCompleteComponent implements OnInit {
  site = new FormControl();
  filteredAddresses: any;
  isLoading = false;
  errorMsg: string;

  autocompleteForm: FormGroup;

  results$: Observable<any>;
  searchTextChanged = new Subject<string>();

  constructor(private http: HttpClient, private router: Router, private formBuilder: FormBuilder) 
  { }

  ngOnInit(){
    this.autocompleteForm = this.formBuilder.group({
      sites: this.formBuilder.array([
         // load first row at start
         this.getSite()
      ])
    });

    this.results$ = this.searchTextChanged.pipe(
      debounceTime(500),
      switchMap(searchText => this.http.get("http://searchaddressapi?text=" + searchText)))

      console.log(this.results$.subscribe(data => { 
        this.filteredAddresses = data['suggestions'];
        console.log(data)
      }));

  }

  displayFn(suggestion : string) : string {
    console.log('Displaying selection - ' + suggestion);
    this.filteredAddresses = [];
    return suggestion ? suggestion : '';
  }

  public searchSite(obj : MultiAutoCompleteComponent, index)
  {
    console.log(obj);
    console.log(index + ' - ' + obj);
    console.log(obj.autocompleteForm.controls.sites.value[index].site);
    console.log('Input value : ' + obj.autocompleteForm.controls);

    var searchText = obj.autocompleteForm.controls.sites.value[index].site;

    //const items = this.autocompleteForm.get('sites') as FormArray;
    //console.log('No of sites added: ' + items.length);


    if(searchText.length < 5 || searchText == '' || searchText == null || searchText == undefined || searchText === -1)
    {
      console.log('Minimum characters not provided, no search conducted');
      return;
    }
    else
    { 
        this.searchTextChanged.next(searchText);
    }

  }

  /**
   * Create form site
   */
  private getSite() {
    return this.formBuilder.group({
      site: ['', [Validators.required]]
    });
  }

  /**
   * Add new site row into form
   */
  addSite() {
    const control = <FormArray>this.autocompleteForm.controls['sites'];
    control.push(this.getSite());

    this.filteredAddresses = [];
  }

}

我根据需要让它工作了!

动态添加额外的自动完成,这些自动完成使用相同的 API 作为数据结果集。

debounceTime 减少了 API 用户输入 searchText 时的调用次数。

我相信你可以清理它,正如一位评论者所建议的那样,将 API 调用放在服务中,但无论如何它都在这里。

请参阅我的问题 Post 中的 UPDATE 3 获取代码!