Reputation:
I create an Angular app that search students from API. It works fine but it calls API every time an input value is changed. I've done a research that I need something called debounce ,but I don't know how to implement this in my app.
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 = [];
}
}
)
}
}
I've tried something like this but it's error Property debounceTime does not exist on type Subject<{}>
mySubject = new Subject();
constructor(private http:Http) {
this.mySubject
.debounceTime(5000)
.subscribe(val => {
//do what you want
});
}
and this's also not work. Property 'fromEvent' does not exist on type 'typeof Observable'
Observable.fromEvent<KeyboardEvent>(this.getNativeElement(this.term), 'keyup')
So, what's the correct way to implement this ?
Thank you.
Upvotes: 32
Views: 53459
Reputation: 91
Just use like this, without RXJS.
It may call 'search()' function itself every keyups, but it does not call inside contents of the function(such as http connect) every time. Much simple solution.
export class MyComponent implements OnInit {
debounce:any;
constructor(){}
search(){
clearTimeout(this.debounce);
this.debounce = setTimeout(function(){
// Your Http Function..
},500); // Debounce time is set to 0.5s
}
}
Upvotes: 3
Reputation: 9964
Demo Link
Tutorial Source Link
Using Template Variable
<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);
});
});
...
...
Upvotes: 2
Reputation: 706
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);
});
}
*Note: make sure that your input element is not present in ngIf Block.
Upvotes: 1
Reputation: 344
For anyone coming across this in a newer version of angular (and rxjs).
The new Rxjs has pipeable operators and they can be used like this (from the accepted answers code)
ngOnInit() {
this.subscription = this.searchTextChanged.pipe(
debounceTime(1000),
distinctUntilChanged(),
mergeMap(search => this.getValues())
).subscribe((res) => {
console.log(res);
});
Upvotes: 12
Reputation: 1740
Also, you can use angular formControls to bind the input search field
<input class="form-control form-control-lg"
type="text" [formControl]="searchField"
placeholder="Search student by id or firstname or lastname">
and use valueChanges observable on our searchField to react to changes of out search field in your App.component.ts file.
searchField: FormControl;
ngOnInit() {
this.searchField.valueChanges
.debounceTime(5000)
.subscribe(term => {
// call your service endpoint.
});
}
optionally you can use distinctUntilChanged ( which only publishes to its output stream if the value being published is different from the previous one)
searchField: FormControl;
ngOnInit() {
this.searchField.valueChanges
.debounceTime(5000)
.distinctUntilChanged()
.subscribe(term => {
// call your service endpoint.
});
}
Upvotes: 7
Reputation: 5673
In the component you can do somthing like this. Create RxJS Subject
, In search
method which is called on keyup
event, do .next()
on this Subject
you have created. Then subscribe
in ngOnInit()
will debounce
for 1 second, as in below code.
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 has several breaking changes including simplifying import points for operators. Try installing rxjs-compat
, which adds back those import paths until the code has been migrated.
Import the necessary operators from RxJS
. Below ones are for 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";
Upvotes: 39
Reputation: 5257
If you are using angular 6 and rxjs 6, try this:
Notice the .pipe(debounceTime(1000))
before your 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 = [];
}
}
)
}
Upvotes: 4