If you need to make an HTTP request in response to user input, it's not efficient to send a request for every keystroke. It's better to wait until the user stops typing and then send a request. This technique is known as debouncing.
Consider the following template, which lets a user enter a search term to find a package by name. When the user enters a name in a search-box, the PackageSearchComponent
sends a search request for a package with that name to the package search API.
<input type="text" (keyup)="search(getValue($event))" id="name" placeholder="Search"/> <ul> <li *ngFor="let package of packages$ | async"> <strong>{{package.name}} v.{{package.version}}</strong> - <em>{{package.description}}</em> </li> </ul>
Here, the keyup
event binding sends every keystroke to the component's search()
method.
The type of
$event.target
is onlyEventTarget
in the template. In thegetValue()
method, the target is cast to anHTMLInputElement
to let type-safe have access to itsvalue
property.getValue(event: Event): string { return (event.target as HTMLInputElement).value; }
The following snippet implements debouncing for this input using RxJS operators.
withRefresh = false; packages$!: Observable<NpmPackageInfo[]>; private searchText$ = new Subject<string>(); search(packageName: string) { this.searchText$.next(packageName); } ngOnInit() { this.packages$ = this.searchText$.pipe( debounceTime(500), distinctUntilChanged(), switchMap(packageName => this.searchService.search(packageName, this.withRefresh)) ); } constructor(private searchService: PackageSearchService) { }
The searchText$
is the sequence of search-box values coming from the user. It's defined as an RxJS Subject
, which means it is a multicasting Observable
that can also emit values for itself by calling next(value)
, as happens in the search()
method.
Rather than forward every searchText
value directly to the injected PackageSearchService
, the code in ngOnInit()
pipes search values through three operators, so that a search value reaches the service only if it's a new value and the user stopped typing.
RxJS operators | Details |
---|---|
debounceTime(500) | Wait for the user to stop typing, which is 1/2 second in this case. |
distinctUntilChanged() | Wait until the search text changes. |
switchMap() | Send the search request to the service. |
The code sets packages$
to this re-composed Observable
of search results. The template subscribes to packages$
with the AsyncPipe and displays search results as they arrive.
See Using interceptors to request multiple values for more about the
withRefresh
option.
switchMap()
operatorThe switchMap()
operator takes a function argument that returns an Observable
. In the example, PackageSearchService.search
returns an Observable
, as other data service methods do. If a previous search request is still in-flight, such as when the network connection is poor, the operator cancels that request and sends a new one.
NOTE:
switchMap()
returns service responses in their original request order, even if the server returns them out of order.
If you think you'll reuse this debouncing logic, consider moving it to a utility function or into the
PackageSearchService
itself.
© 2010–2023 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://angular.io/guide/http-optimize-server-interaction