I have an angular form where I want to suggest the user what he should put on the input depending of what he is writing.
I obviously can use the directive onChange and make an api call to the service which makes the suggestions after every change. But I think that it is not a good implementation as I will do as many calls as letters the users writes (as well as the possible typos). So the idea is to make this onChange function that only makes the api call after X changes have been made to the field. How can I do that? How can I check if the field has changed enough to make another api call to get new suggestions?
First of all, you have to listen for changes on your form input field. If you are using Reactive Angular forms, then you can use the formControl's valueChanges
property to listen to changes. Else you have to use the @ViewChild
decorator to get the reference of the input element in TS file and then listen to changes using the RXJS fromEvent
function.
I have used RXJS fromEvent function for demo. Feel free to modify it as per your needs.
export class AppComponent implements OnInit {
@ViewChild('textbox', { static: true }) textbox: ElementRef<HTMLInputElement>;
ngOnInit() {
/**
* If you are using Reactive form, then use
* form.get('<form-control-name>').valueChanges
*/
const inputChanges$ = fromEvent(this.textbox.nativeElement, 'keyup');
inputChanges$
.pipe(
// remove the below map operator if you are using Reactive forms formControl
map(event => (event.target as HTMLInputElement).value),
debounceTime(500),
distinctUntilChanged(),
switchMap(val => {
// perform the http call for your service API like below
// return this.http.get(`service.com/get?search=${val}`)
return of(`sevice api all for: ${val}`);
})
)
.subscribe(console.log)
}
}
In the above example, all heavy-lifting is done by these 3 RXJS operators debounceTime
, distinctUntilChanged
and switchMap
.
This filter operator simply emits value only after a certain period of time and if there are multiple values emitted within this time span, only the last value is returned. For eg, if debounceTime is set to 500ms (as in our case), only the last value emitted for every 500ms passes through this filter operator. For more information, visit Rxjs official docs.
debounceTime is the better solution than counting number of letters changed to prevent API call for every single change. Also, do not increase the denounceTime too much as it might spoil the user experience.
As the name suggests, value passes through this filter operator only if the latest value received is different from the previous value. For more information, visit Rxjs official docs.
This is an higher-order mapping operator, in the sense, it returns an observable for the given input. This is especially useful in our case, since for each input element value change, we need to hit a backend service API to fetch some values for suggestion. The most interesting part about this operator is that, when it receives a new value while an async service call is ongoing for the previous value, it cancels the previous request and sends a new request for the latest value received.
I strongly suggest you to go through this article as many things are going on here when switchMap
opreator is used and I won't be able to explain everything here. It is important that you understand the concept behind this operator.
Remember do not manually subscribe to the observable inside the switchMap operator. This RXJS operator automatically subscribes to the new obseravble and unsubscribes the old observable.
You can view the soure code for this example in my sample Stackblitz app.
Edits: Updated links and references