【问题标题】:How to execute API call after entering 3 characters in field?在字段中输入 3 个字符后如何执行 API 调用?
【发布时间】:2020-11-27 09:26:23
【问题描述】:

我正在使用父组件和子组件。子组件具有输入字段,并将用户输入的值发送给父组件,如下所示:

<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. 用户输入:abc // API 调用得到执行,这很好
  2. 现在,用户输入:abcd // 没有执行 API 调用,这很好
  3. 现在用户删除了字母“d”,数据的新值将是“abc”我不想再次执行 API 调用,因为我们已经为“abc”执行了 API 调用
  4. 现在如果用户删除字母“c”,数据的新值现在是“ab”。此时,不需要 API 调用
  5. 现在,如果用户添加字母“c”,新值将是“abc”。此时,API 调用是预期的。 (这是有效的)

那么,如果输入数据是 3 个字符,并且如果用户输入更多字符什么都不应该发生,那么如何始终执行 API 调用,如果他删除字符并返回前 3 个字符,则应该什么都不发生,因为 API 已经发生了?提前非常感谢!

【问题讨论】:

  • 听起来很复杂。也许另一种方法是使用debounce,您不会立即进行 API 调用。相反,您在输入字符之间有 1 秒的延迟,并且 API 调用将在输入最后一个字符后 1 秒进行。在这种情况下,您不必跟踪是否进行了 API 调用,以及检查击键是否为退格等。
  • @codemax 简单来说,我的问题是如果我已经输入“abc”并进行了 API 调用,那么如果用户输入“abcd”并删除“d”,则不要再进行 API 调用,这有意义吗?
  • 当您从子组件发出值时,您能否指定子组件的逻辑。 ?
  • 在你的例子中你有&lt;parent-component (sendInputValue),但我认为它应该是&lt;child-component (sendInputValue)
  • @progx 我已经添加了一个答案,你能检查一下并给出你的反馈吗?

标签: javascript angular typescript rxjs


【解决方案1】:

我认为你需要distinctUntilChanged 并且可以在管道中使用过滤器

this.myService.getDataFromService(data)
  .pipe(
    filter(_ => data.length === 3), 
    distinctUntilChanged()
  ).subscribe(rs => console.log(rs));

【讨论】:

  • 您的回答无效。每次我在字段中输入一个值时,它都会立即执行 API 调用,即使只有 1 个字符。我只想在输入 3 个字符时执行 API 调用
【解决方案2】:

这里有一个简单的方法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)
    }
  }
}

尝试将调用的值添加到数组中,然后在调用之前检查该数组。 我做了一个例子,当长度大于 3 时调用 API,因为我认为这种方式比像你所做的那样做更合理。

【讨论】:

  • 你可以只使用[].includes,三元对真或假的结果是愚蠢的,你可以只使用一个集合
  • 谢谢你,fork the stackblitz 并照你说的做:)
  • 编译器在我使用[].includes 时抱怨说为什么我没有使用它。我现在试了一下,尽管编译器仍在抱怨,但它仍然可以工作。“你可以只使用 Set”是什么意思?
  • 我不是字面上的意思。我的意思是使用数组包含方法而不是查找索引
  • @MFuatNUROĞLU 您的回答无效。我只想在输入 3 个字符时执行 API 调用,好像如果我继续输入更多的字符,如 4、5、6 等,它会继续调用,我不希望这样。现在,如果我将其更改为 === 3,它只会以 3 个字符进行调用,但我会进行两次相同的调用。示例我输入 abc(api 调用),现在我输入 abcd(无 API 调用),现在我删除 d 它将进行 API 调用,我不希望这样,因为它之前已经调用了 abc。希望有意义
【解决方案3】:

现在用户删除了字母“d”,数据的新值将是“abc” 我不想再次执行 API 调用,因为我们已经为“abc”执行了 API 调用

您可能想尝试使用distinct 来实现它(返回一个 Observable,该 Observable 发出由源 Observable 发出的所有项,这些项与以前的项相比是不同的。)

distinctUntilChanged 发出的值具有在前一个发出的值之前发出,而 distinct 不会。

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

这是工作CodeSandbox Demo

希望对您有所帮助!

【讨论】:

    【解决方案4】:

    以下是您代码中的小调整,它将满足您提出的要求。

    您绝对可以使用 debounce、distinctUntilChanged、switchMap 运算符来改进此过程。

    previousData = '' // create a property which will track of previous data from parent component.
    
    getInputField(data){
      if(data && data.length === 3 && data.length > previousData.length){
        this.myService.getDataFromService(data).subscribe(rs=>console.log(rs));
      }
      this.previousData = data || ''; // update previous data to current data after backend call.
    }
    

    【讨论】:

    • @PrincesNinja 非常感谢兄弟!!
    • @progx 很乐意提供帮助。
    【解决方案5】:

    子组件

    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)
    }
    

    Click here Full working example with parent child component

    【讨论】:

      【解决方案6】:

      首先,让我们让您的输入数据可观察 - 以便更容易实现其他所有内容:

      private inputData$ = new Subject<string>();
      
      public getInputField(data: string){
        this.inputData$.next(data);
      }
      

      现在我们可以对这个输入流做任何我们想做的事情。例如。采用上述@Thorsten Rintlen 建议的方法

      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 服务方法包装到某个缓存层中,而无需更改任何其他内容。

      【讨论】:

        【解决方案7】:
            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)
                });
              }
            }
        

        我认为这会起作用

        【讨论】:

        • @progx 你试过这个吗?
        【解决方案8】:

        使用RxJsFromEvent方法监听来自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)
             })
        }
        

        【讨论】:

          【解决方案9】:

          我制作了一个通用函数,可让您将其用于任意数量的字符以限制您的 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. 然后,循环遍历缓存对象并将所有内容设置为 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));
            }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-07-01
            • 1970-01-01
            • 1970-01-01
            • 2019-03-15
            • 1970-01-01
            • 1970-01-01
            • 2015-03-31
            相关资源
            最近更新 更多