Make user search live as you type
Angular · Angular · intermediate · modification
Turns the manual search into a live typeahead. Instead of a button, the component now reacts to the reactive-form valueChanges, debounces the input, and maps each query to the /api/users request, rendering the matches. Subscription is torn down with takeUntilDestroyed so nothing leaks. Tested locally by typing several queries and confirming the list updates on every change.
Used in the admin panel header. Production users often search from slow mobile networks, so requests for different queries are routinely in flight at the same time.
Requirements
- Replace the search button with a live search that queries the API as the user types.
- Debounce input so a burst of keystrokes does not fire one request per character.
- The rendered list must always reflect the latest query the user typed: a slower earlier response must never overwrite the result of a newer one.
Files touched
- src/app/components/user-search.ts
--- src/app/components/user-search.ts
import { HttpClient } from '@angular/common/http';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { of } from 'rxjs';
+import { catchError, debounceTime, mergeMap } from 'rxjs/operators';
interface User {
template: `
<input [formControl]="query" placeholder="Search users" />
- <button (click)="search()">Search</button>
<ul>
@for (u of results(); track u.id) {
results = signal<User[]>([]);
- search(): void {
- this.http
- .get<User[]>(`/api/users?q=${this.query.value}`)
+ constructor() {
+ this.query.valueChanges
+ .pipe(
+ debounceTime(200),
+ mergeMap((q) => this.http.get<User[]>('/api/users', { params: { q } }).pipe(catchError(() => of<User[]>([])))),
+ takeUntilDestroyed(),
+ )
.subscribe((users) => this.results.set(users));
}