Как реализовать время debounce в событии keyup в Angular 6

Я создаю приложение Angular, которое ищет студентов из API. Он работает нормально, но вызывает API каждый раз, когда изменяется входное значение. Я провел исследование, в котором мне нужно что-то, называемое debounce, но я не знаю, как реализовать это в своем приложении.

App.component.html

    <div class="container">
  <h1 class="mt-5 mb-5 text-center">Student</h1>
<div class="form-group">
  <input  class="form-control form-control-lg" type="text" [(ngModel)]=q (keyup)=search() placeholder="Search student by id or firstname or lastname">
</div>
 <hr>
 <table class="table table-striped mt-5">
    <thead class="thead-dark">
      <tr>
        <th scope="col" class="text-center" style="width: 10%;">ID</th>
        <th scope="col" class="text-center" style="width: 30%;">Name</th>
       <th scope="col" style="width: 30%;">E-mail</th>
        <th scope="col" style="width: 30%;">Phone</th> 
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let result of results">
        <th scope="row">{{result.stu_id}}</th>
        <td>{{result.stu_fname}} {{result.stu_lname}}</td>
         <td>{{result.stu_email}}</td>
        <td>{{result.stu_phonenumber}}</td> 
      </tr>
    </tbody>
  </table>
</div>

App.component.ts

import { Component} from '@angular/core';
import { Http,Response } from '@angular/http';
import { Subject, Observable } from 'rxjs';

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


  results;
  q = '';

  constructor(private http:Http) {


  }


  search() {
    this.http.get("https://www.example.com/search/?q="+this.q)
    .subscribe(
      (res:Response) => {
          const studentResult = res.json();
          console.log(studentResult);
          if(studentResult.success) {
            this.results = studentResult.data;
          } else {
            this.results = [];
          }
      }
    )
  }
}

Снимок экрана Screenshot

Я пробовал что-то вроде этого, но это ошибка Свойство debounceTime не существует для типа Subject <{}>

  mySubject = new Subject();
  constructor(private http:Http)  {
    this.mySubject
    .debounceTime(5000)
    .subscribe(val => {
      //do what you want
    });
  }

и это тоже не работает. Свойство 'fromEvent' не существует для типа 'typeof Observable'

    Observable.fromEvent<KeyboardEvent>(this.getNativeElement(this.term), 'keyup')

Итак, как правильно это реализовать?

Спасибо.

Ответ 1

В компоненте вы можете сделать что-то вроде этого. Создайте метод RxJS Subject, In search, который вызывается при событии keyup, do .next() в этом Subject, который вы создали. Тогда subscribe в ngOnInit() будет debounce в течение 1 секунды, как в приведенном ниже коде.

searchTextChanged = new Subject<string>();
constructor(private http:Http) {

}


ngOnInit(): void {
    this.subscription = this.searchTextChanged
        .debounceTime(1000)
        .distinctUntilChanged()
        .mergeMap(search => this.getValues())
        .subscribe(() => { });
}

getValues() {
    return this.http.get("https://www.example.com/search/?q="+this.q)
    .map(
      (res:Response) => {
          const studentResult = res.json();
          console.log(studentResult);
          if(studentResult.success) {
            this.results = studentResult.data;
          } else {
            this.results = [];
          }
      }
    )
}

search($event) {
    this.searchTextChanged.next($event.target.value);
}

rxjs v6 имеет несколько нарушений, включая упрощение точек импорта для операторов. Попробуйте установить rxjs-compat, который добавляет обратно те пути импорта, пока код не будет перенесен.

Импортируйте необходимые операторы из RxJS. Ниже приведены для RxJS 5.x

import { Subject } from "rxjs/Subject";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import { Observable } from "rxjs/Observable";
import "rxjs/add/operator/mergeMap";

Ответ 2

Для всех, кто сталкивался с этим в более новой версии angular (и rxjs).

Новые Rxjs имеют конвейерные операторы, и их можно использовать так (из принятого кода ответов)

ngOnInit() {
 this.subscription = this.searchTextChanged.pipe(
   debounceTime(1000),
   distinctUntilChanged(),
   mergeMap(search => this.getValues())
  ).subscribe((res) => {
    console.log(res);
  });

Ответ 3

Если вы используете angular 6 и rxjs 6, попробуйте следующее:
Обратите внимание на .pipe(debounceTime(1000)) перед тем, как subscribe

import { debounceTime } from 'rxjs/operators';


search() {
    this.http.get("https://www.example.com/search/?q="+this.q)
    .pipe(debounceTime(1000))
    .subscribe(
      (res:Response) => {
          const studentResult = res.json();
          console.log(studentResult);
          if(studentResult.success) {
            this.results = studentResult.data;
          } else {
            this.results = [];
          }
      }
    )
  }

Ответ 4

Кроме того, вы можете использовать угловые formControls для привязки поля ввода ввода

<input  class="form-control form-control-lg" 
type="text" [formControl]="searchField"
placeholder="Search student by id or firstname or lastname">

и используйте valueChanges, наблюдаемые в нашем searchField, чтобы реагировать на изменения внешнего поля поиска в вашем файле App.component.ts.

searchField: FormControl; 

ngOnInit() {
    this.searchField.valueChanges
      .debounceTime(5000) 
      .subscribe(term => {
    // call your service endpoint.
      });
}

При желании вы можете использовать DifferentUntilChanged (который публикуется в свой выходной поток, только если публикуемое значение отличается от предыдущего)

searchField: FormControl; 

ngOnInit() {
    this.searchField.valueChanges
      .debounceTime(5000) 
     .distinctUntilChanged()
     .subscribe(term => {
            // call your service endpoint.
      });
}

Ответ 5

user.component.html

   <input type="text" #userNameRef class="form-control"  name="userName" >  <!-- template-driven -->
 <form [formGroup]="regiForm"> 
      email: <input formControlName="emailId"> <!-- formControl -->
    </form>

user.component.ts

       import { fromEvent } from 'rxjs';
       import { switchMap,debounceTime, map } from 'rxjs/operators';


        @Component({
          selector: 'app-user',
          templateUrl: './user.component.html',
          styleUrls: ['./user.component.css']
        })
        export class UserComponent implements OnInit {

          constructor(private userService : UserService) { }


             @ViewChild('userNameRef') userNameRef : ElementRef;

             emailId = new FormControl(); 
   regiForm: FormGroup = this.formBuilder.group({
      emailId: this.bookId
   });   

             ngOnInit() {

                    fromEvent(this.userNameRef.nativeElement,"keyup").pipe(
                    debounceTime(3000),
                    map((userName : any) =>userName.target.value )
                  ).subscribe(res =>{
                    console.log("User Name is :"+ res);

                  } );
    //--------------For HTTP Call------------------

                  fromEvent(this.userNameRef.nativeElement,"keyup").pipe(
                    debounceTime(3000),
                    switchMap((userName : any) =>{
                   return this.userService.search(userName.target.value)
                 })
                  ).subscribe(res =>{
                    console.log("User Name is :"+ res);

                  } );



----------
                // For formControl 

                  this.emailId.valueChanges.pipe(
                  debounceTime(3000),
                  switchMap(emailid => {
                         console.log(emailid);
                        return this.userService.search(emailid);
                        })
                         ).subscribe(res => {
                               console.log(res);
                                    });


            }

* Примечание: убедитесь, что ваш входной элемент отсутствует в блоке ngIf.

Ответ 6

Демо- ссылка

Ссылка на учебник

enter image description here

Использование переменной шаблона

<input type="text" #movieSearchInput class="form-control" placeholder="Type any movie name" />

    ...
    ...    
        fromEvent(this.movieSearchInput.nativeElement, 'keyup').pipe(
        // get value
        map((event: any) => {
            return event.target.value;
        })
        // if character length greater then 2
        ,filter(res => res.length > 2)
        // Time in milliseconds between key events
        ,debounceTime(1000)        
        // If previous query is diffent from current   
        ,distinctUntilChanged()
        // subscription for response
        ).subscribe((text: string) => {
            this.isSearching = true;
            this.searchGetCall(text).subscribe((res)=>{
            console.log('res',res);
            this.isSearching = false;
            this.apiResponse = res;
            },(err)=>{
            this.isSearching = false;
            console.log('error',err);
            });
        });
    ...
    ...