Как выполнить вызов API после ввода 3-х символов в поле? - PullRequest
10 голосов
/ 07 августа 2020

Я работаю с родительским и дочерним компонентами. Дочерний компонент имеет поле ввода и будет выдавать значение, введенное пользователем в родительский компонент, следующим образом:

<parent-component (sendInputValue)="getInputValue($event)"><parent-component>

Теперь в родительском компоненте у меня есть это:

getInputField(data){
 console.log(data); // this prints the data (Example: abc)
 //  then here I'm just executing the API call ONLY if data length is 3
  if(data.length === 3){
    this.myService.getDataFromService(data).subscribe(rs=>console.log(rs));
  }
}

Теперь предположим, что это происходит:

  1. Пользователь вводит: ab c // Вызов API выполняется, что хорошо
  2. Теперь пользователь вводит: abcd // Вызов API не выполняется , это хорошо
  3. Теперь пользователь удаляет букву «d», и новое значение данных будет «ab c». Я НЕ хочу снова выполнять вызов API, потому что мы уже выполняем вызов API для «ab c "
  4. Теперь, если пользователь удаляет букву« c », новым значением данных становится« ab ». На этом этапе вызова API не ожидается.
  5. Теперь, если пользователь добавляет букву «c», новое значение будет «ab c». На этом этапе ожидается вызов API. (это работает)

Итак, как всегда выполнять вызов API, если входные данные составляют 3 символа, и если пользователь вводит больше символов, ничего не должно происходить, и если он удаляет символы и возвращается к первым 3 символов ничего не должно происходить, потому что API уже произошло? Заранее большое спасибо!

Ответы [ 9 ]

6 голосов
/ 07 августа 2020

Думаю вам нужно independentUntilChanged И можно использовать фильтр в трубе

this.myService.getDataFromService(data)
  .pipe(
    filter(_ => data.length === 3), 
    distinctUntilChanged()
  ).subscribe(rs => console.log(rs));
2 голосов
/ 09 августа 2020

Ниже приведена небольшая настройка в вашем коде, которая будет соответствовать указанным вами требованиям.

1 голос
/ 15 августа 2020

Используйте метод RxJs FromEvent для прослушивания события ввода из поля input.

@ViewChild('#input', {static:true}) inputField: ElementRef<any>;

ngOnInit(){
   FromEvent(inputField, 'input')
     .pipe(
         map(event => event.target.value),
         map((value:string) => value.trim()),
         filter((value:string) => value.length === 3), 
         debounceTime(500),
         distinctUntilChanged()
     )
     .subscribe(keyword => {
         // API Call(keyword)
     })
}
0 голосов
/ 16 августа 2020

Я сделал общую c функцию, которая позволит вам использовать ее для любого количества символов, чтобы ограничить вызов API.

const cached = {};
/**
 * @desc Check if API call is allowed or not 
 * based on provided input and length condition
 * @param {String} input - input value
 * @param {Number} len   - length of the input over which API has to be called.
 * @returns {Boolean}
 */
function doCallAPI(input, len) {
  if(input.length === len) {
    if(!cached[input]) {
      // Call the API
      cached[input] = 1;
      return true;
    }
  }
  else if(input.length < len) for(let i in cached) cached[i] = 0;
  return false
}

Пояснение:

  1. Проверить если длина ввода равна условной длине (Здесь 3).
    • ЕСЛИ ДА, тогда проверьте, есть ли у кэшированного объекта ключ с входным значением и значение (например, 1)
      • ЕСЛИ НЕТ, тогда API не вызывается для этого входа стоимость. Теперь
      • SET cached [input value] = 1, чтобы вставить значение в кэшированный объект.
    • Вернуть true, вызов API состояния разрешен.
  2. Проверьте, меньше ли длина (скажем, 2) входных данных условной длины.
  3. Затем, l oop через кэшированный объект и установите все в 0, чтобы сообщить, что теперь вызов API разрешен для кэшированных значений условной длины (Здесь 3).
  4. Вернуть false, чтобы указать, что вызов API не разрешен.

Вот как используйте это,

getInputField(data){
 console.log(data); // this prints the data (Example: abc)
 //  then here I'm just executing the API call ONLY if data length is 3
  if(doCallAPI(data, 3)){
    this.myService.getDataFromService(data).subscribe(rs=>console.log(rs));
  }
}
0 голосов
/ 14 августа 2020
    private lastLetter = "";

    getInputField(data) {
     if(!this.lastLetter === data.toLowerCase()) && data.length === 3) {
        this.myService.getDataFromService(data).subscribe(rs=>{
        this.lastLetter = data.toLowerCase();
        console.log(rs)
        });
      }
    }

Полагаю, это сработает

0 голосов
/ 12 августа 2020

Во-первых, давайте сделаем ваши входные данные наблюдаемыми - чтобы было легче реализовать все остальное:

private inputData$ = new Subject<string>();

public getInputField(data: string){
  this.inputData$.next(data);
}

Теперь мы можем делать все, что захотим, с этим входным потоком. Например, адаптируйте подход, предложенный @Thorsten Rintelen выше

ngOnInit() {
  this.inputData$
    .pipe(
      filter(data => data.length === 3),
      distinctUntilChanged(), // this makes sure api call is sent only if input has changed
      switchMap(data => this.myService.getDataFromService(data)),
    )
    .subscribe(apiResult => console.log(apiResult));
}

ПРИМЕЧАНИЕ: Этот подход кэширует только последний ввод. Если вы хотите кэшировать и повторно использовать все ответы api, вы можете обернуть свой метод службы api на некотором уровне кеширования, не меняя ничего другого.

0 голосов
/ 10 августа 2020

дочерний компонент

import { Component, Input, Output, EventEmitter, OnDestroy } from "@angular/core";
import { Observable, Subject, interval } from "rxjs";
import { debounce, map, filter, tap } from "rxjs/operators";

@Component({
  selector: "hello",
  template: `
    <input type="text" id="textInput" (keyup)="keyupHandler($event)" />
  `
})
export class HelloComponent implements OnDestroy {
  requestCompleted: { [key: string]: boolean } = {};

  @Output()
  sendInputValue = new EventEmitter<string>();

  observableFilter = new Subject<string>();
  constructor() {
    this.observableFilter
      .pipe(
        debounce(() => interval(500)),
        filter(x => x && x.length === 3 && !this.requestCompleted[x]),
        tap(x => (this.requestCompleted[x] = true))
        //tap( x => console.log(x)),
      )
      .subscribe(x => {
        this.sendInputValue.emit(x);
      });
  }

  ngOnInit() {
  }

  ngOnDestroy(): void {
    delete this.requestCompleted;
  }

  keyupHandler(e) {
    this.observableFilter.next(e.target.value);
  }
}

в родительском компоненте html

<hello (sendInputValue)="getInputValue($event)"></hello>

Машинопись родительского компонента

getInputValue(data){
   console.log(data); // this prints the data (Example: abc)
}

Щелкните здесь Полный рабочий пример с родительский дочерний компонент

0 голосов
/ 09 августа 2020

Теперь пользователь удаляет букву «d», и новое значение данных будет «ab c». Я НЕ хочу выполнять вызов API снова, потому что мы уже выполняем вызов API для «ab c»

Вы можете попробовать реализовать его, используя distinct (Возвращает Observable, который испускает все элементы, испускаемые исходным Observable, которые отличаются по сравнению с предыдущими элементами.)

distinctUntilChanged испускает значения, которые были сгенерированы перед предыдущим сгенерированным значением, а отличное - нет.

  valueChanges.pipe(
    filter(x => x.length === 3),
    distinct()
  )

Здесь работает CodeSandbox Demo

Надеюсь, это поможет!

0 голосов
/ 07 августа 2020

Вот простой способ stackblitz :

setInputField(data){
  if(data.length >= 3){
    let wasCalled  = this.calledInputs.includes(data) ? true : false;
    if(wasCalled){
      return;
    }else{
      this.calledInputs.push(data)
      console.log("call Service For", data)
    }
  }
}

Попробуйте добавить вызываемые значения в массив, затем проверьте этот массив перед выполнением вызовов. Я сделал пример, который вызывает API, когда длина больше 3, потому что я считаю этот способ более разумным, чем делать то же самое, что и вы.

...