Search code examples
angulartypescripthttprxjsangular-pipe

Angular 4 - limit HTTP request to one every 200 milliseconds while filtering


I'm working on a project where I have a page that has an ngFor displaying an array of objects. I want to give the user the ability to filter those objects by typing in certain keywords in an input field.

The filtering of the data has to be done server side, so I implemented a call for this in my service.

public searchProperties(query: string): Observable<IProperty[]> {
    console.log('SERVICE: query: ' + query);
    let params = new HttpParams();

    params = params.append('q', query);

    return this.http.get<IProperty[]>(this.baseUrl+'/api/property', {params: params})
    .do( response => {
      console.log('Successfully retrieved the search response');
    }).catch((error: HttpErrorResponse) => {
      console.log('Something went wrong while retrieving the search response');
      return Observable.throw(error);
    })
  }

I call this method in a pipe like this:

  transform(items: IProperty[], searchText: string): any[] {
    if(!items) return [];
    if(!searchText) return items;

    console.log('PIPE: Amount of items in items array BEFORE: ' + items.length);

    this.propertyService.searchProperties(searchText).subscribe(
      result => {
        items = result;
      }, error => {
        console.log('Failed to retrieve the properties that were searched for');
        return items;
      },
      () => {
        console.log('PIPE: Amount of items in items array AFTER: ' + items.length);
        return items;
      }
    )
  }

I added the pipe to my ngFor in the html like this:

 <div class="col-md-4" *ngFor="let property of properties | property_filter : searchText ; let i=index">

There are two problems with my current code:

  1. Whenever I type some keyword in the input field, the http call does get called and returns a response to the pipe, but my page shows no objects, the ngFor just remains empty until I clear the input field again.

  2. While typing, the HTTP call should only be fired off every 200 milliseconds instead of on every single change event.

Thanks in advance!

UPDATE:

I removed the pipe and implemented the call in the component. The list of objects is now properly updated.

The only question that still remains is how can I limit the amount of responses to one every 200 milliseconds?


Solution

  • for firing event after your change after certain second you make use of debounceTime function of RxJs as below.

    <input type="text" [ngModel]="term" (ngModelChange)="change($event)"/>
    
    import { Subject } from 'rxjs/Subject';
    import { Component }   from '@angular/core';
    import 'rxjs/add/operator/debounceTime';
    
    export class AppComponent{
        term: string;
        modelChanged: Subject<string> = new Subject<string>();
    
        constructor() {
            this.modelChanged
           .debounceTime(300) // wait 300ms after the last event before emitting last event
           .distinctUntilChanged() // only emit if value is different from previous value
           .subscribe(model => this.model = model);
        }
    
        change(text: string) {
            this.modelChanged.next(text);
           //code for filtering goes here 
        }
    }
    
    
    ____
    

    Following is code of mine which I used for filtering Email addresses coming from server and it working fine

    Pipe.ts

    import { Pipe, PipeTransform } from '@angular/core';
    import { ReportService } from './ReportService';
    import { Observable } from 'rxjs/Observable';
    
    @Pipe({
      name: 'filter'
    })
    
    export class MyFilter implements PipeTransform {
      emails: Array<string>= new Array<string>();
      constructor(private reportservice :ReportService)
      {}
    
        transform(items: any[], term: string): any {
          debugger;
          if (!term) 
            return items;
           this.reportservice.GetAllEmails().subscribe(e=> this.emails = e);
           return this.emails.filter(item => item.indexOf(term) > -1);
        }
    }
    

    App.Component.Html

    <form id="filter">
      <label>Searching for email</label>
      <input type="text" name="termtext" [(ngModel)]="term" />
    </form>
    
    <ul *ngFor="let email of emails| filter:term">
        {{email}}
    </ul>
    

    App.component.ts

      term:string;
      emails: Array<string> = new Array<string>();
      constructor()
      {
        this.term="test";
      }
    

    First time it take time as its fetching data from serve but it working form next time fine.