如何使用 ng-autocomplete 地址输入正确地对 Angular 中的 positionStack 进行 rest api 调用,并对其进行去抖动

How to properly make a rest api call to positionStack in Angular with an ng-autocomplete address input, and debounce it

我第一次尝试在 Angular 9 中实现自动完成地址表单。 我使用 positionstack rest api to retrieve addresses, and angular-ng-autocomplete 在文本输入下显示结果。 我设法调用 api 并获得结果,甚至显示它,但我认为我做的不正确,因为我出现了几个警告和错误。

最后,我想消除其余 api 调用的抖动。到目前为止,它是在每次击键时执行的,但我无法让它工作。

这是我从 api 获得的响应对象:

"data": [
        {
            "latitude": 45.136054,
            "longitude": 5.711749,
            "type": "street",
            "name": "Avenue Général de Gaulle",
            "number": null,
            "postal_code": null,
            "street": "Avenue Général de Gaulle",
            "confidence": 0.8,
            "region": "Isère",
            "region_code": "IS",
            "county": null,
            "locality": null,
            "administrative_area": "Le Pont-De-Claix",
            "neighbourhood": null,
            "country": "France",
            "country_code": "FRA",
            "continent": "Europe",
            "label": "Avenue Général de Gaulle, Le Pont-De-Claix, France"
        },...
]

这是我的 html 模板

  <ng-autocomplete
    [data]="addresses"
    [searchKeyword]="keyword"
    [itemTemplate]="itemTemplate"
    [notFoundTemplate]="notFoundTemplate"
    (inputChanged)="onChange($event)"
    placeHolder="Recherchez votre adresse">
  </ng-autocomplete>

  <ng-template #itemTemplate let-item>
    <a [innerHTML]="item?.label"></a>
  </ng-template>

  <ng-template #notFoundTemplate let-notFound>
    <div [innerHTML]="notFound"></div>
  </ng-template>

和我的打字稿 classes

export class AutocompleteAddressComponent {

  keyword: string;
  public addresses;

  constructor(private mapService: MapService) { }

  onChange(event: any) {
    this.addresses = [];
    if (event.length > 3 ) {
      this.mapService.getCoordinates(event).subscribe(
        response => {
          this.addresses = response.data; <- WARNING: Property 'data' does not exist on type 'Object
        }
      );
    }
  }
}

地图服务方法是一个带有查询参数的简单 get。

  public getCoordinates(query: string) {
    console.log('Sending request');
    query = query.trim();
    const options = query ?
      {
        params: new HttpParams()
          .set('access_key', environment.positionstack_apikey)
          .set('query', query)
          .set('limit', '10')
          .set('output', 'json')
      } : {};

    return this.httpClient.get(
      this.baseUrl + '/forward',
      options
    );
  }

当我试图影响对我的地址对象(稍后我将转换为模型)的调用结果时,我在 IntelliJ 中收到警告。我在 Web 浏览器控制台中收到错误消息:

core.js:6185 ERROR TypeError: Cannot read property 'replace' of undefined
at HighlightPipe.transform (angular-ng-autocomplete.js:1529)
    at pureFunction3Internal (core.js:36519)
    at Module.ɵɵpipeBind3 (core.js:36698)
    at AutocompleteComponent_li_10_div_2_Template (angular-ng-autocomplete.js:92)

显示结果列表id,但不是很稳定,所以我可能做错了。

最后,我尝试使用 subject 和 observable 向其余调用添加去抖动方法,但它不再触发调用。

我修改的class:

export class AutocompleteAddressComponent implements OnInit{
  result$: Observable<any>;
  subject = new Subject<string>();

  public addresses;
  keyword: string;

  constructor(private mapService: MapService) { }

  ngOnInit(): void {
    this.result$ = this.subject.pipe(
      debounceTime(500),
      map(searchText => this.mapService.getCoordinates(searchText).subscribe(
        result => console.log(result)
      ))
    );
  }

  onChange(keyword: any) {
    this.addresses = [];
    if (keyword.length > 3 ) {
      this.subject.next(keyword);
    }
  }
}

如果问题不够清楚,我会做一个 stackblitz ! 感谢您的帮助!

好吧,多亏了 this tutorial

事实上,一切都由开箱即用的 ng-autocomplete 正确处理。 我的主要问题是我没有使用我要搜索的字段名称来评估 'keyword' 变量。

所以这里是完整的解决方案,剩下的 api 调用地图服务,去抖动时间和最小查询长度也由 ng-autocomplete 处理,在 select使用我需要的属性创建模型对象。

Html 模板:

<div class="ng-autocomplete">
  <ng-autocomplete 
    [data]="addresses"
    [searchKeyword]="keyword"
    (selected)='selectEvent($event)'
    (inputChanged)='getServerResponse($event)'
    (inputCleared)="searchCleared()"
    [debounceTime]="300"
    [isLoading]="isLoadingResult"
    [minQueryLength]="3"
    [itemTemplate]="itemTemplate"
    [notFoundTemplate]="notFoundTemplate"
    placeHolder="Rechercher une adresse...">
  </ng-autocomplete>

  <ng-template #itemTemplate let-item>
    <a [innerHTML]="item.label"></a>
  </ng-template>

  <ng-template #notFoundTemplate let-notFound>
    Aucun résultat !
  </ng-template>
</div>

component.ts 文件:

import {Component} from '@angular/core';
import {MapService} from '../service/map.service';
import {AddressModel} from '../model/AddressModel';


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

  address: AddressModel;
  addresses: any;
  keyword = 'label';
  isLoadingResult: boolean;

  constructor(private mapService: MapService) {
  }

  getServerResponse(event) {
    this.isLoadingResult = true;
    this.mapService.getCoordinates(event).subscribe(
      (result: any) => {
        this.addresses = result.data;
        this.isLoadingResult = false;
      }
    );
  }

  selectEvent(event: any) {
    this.address = new AddressModel(
      event.latitude,
      event.longitude,
      event.label
    );
  }

  searchCleared() {
    console.log('searchCleared');
    this.addresses = [];
  }
}

地图服务:

import { Injectable } from '@angular/core';
import {HttpClient, HttpParams} from '@angular/common/http';
import {environment} from '../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class MapService {

  private baseUrl: string = environment.map_url;

  constructor(private httpClient: HttpClient) { }

  public getCoordinates(query: string) {
    query = query.trim();
    const options = query ?
      {
        params: new HttpParams()
          .set('access_key', environment.positionstack_apikey)
          .set('query', query)
          .set('limit', '10')
          .set('output', 'json')
      } : {};

    return this.httpClient.get(
      this.baseUrl + '/forward',
      options
    );
  }
}