typescript 50 lines · 7 steps

A city autocomplete service in Angular

An RxJS pipeline turns a stream of keystrokes into debounced, deduplicated city suggestions from an HTTP endpoint.

Explained by highlit
1import { Injectable } from '@angular/core';
2import { HttpClient, HttpParams } from '@angular/common/http';
3import {
4 Observable,
5 Subject,
6 of,
7} from 'rxjs';
8import {
9 debounceTime,
10 distinctUntilChanged,
11 switchMap,
12 map,
13 catchError,
14 filter,
15} from 'rxjs/operators';
16 
17export interface CitySuggestion {
18 id: string;
19 name: string;
20 region: string;
21}
22 
23@Injectable({ providedIn: 'root' })
24export class CityAutocompleteService {
25 private readonly queries$ = new Subject<string>();
26 
27 readonly suggestions$: Observable<CitySuggestion[]> = this.queries$.pipe(
28 map((term) => term.trim()),
29 debounceTime(250),
30 distinctUntilChanged(),
31 filter((term) => term.length >= 2),
32 switchMap((term) => this.fetch(term)),
33 );
34 
35 constructor(private readonly http: HttpClient) {}
36 
37 search(term: string): void {
38 this.queries$.next(term);
39 }
40 
41 private fetch(term: string): Observable<CitySuggestion[]> {
42 const params = new HttpParams()
43 .set('q', term)
44 .set('limit', '8');
45 
46 return this.http
47 .get<CitySuggestion[]>('/api/cities', { params })
48 .pipe(catchError(() => of<CitySuggestion[]>([])));
49 }
50}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1A Subject bridges imperative event calls into a declarative reactive stream you can transform.
  2. 2Chaining debounceTime, distinctUntilChanged, and switchMap curbs redundant requests and cancels stale ones automatically.
  3. 3Catching errors inside the inner Observable keeps the outer stream alive so one failed request doesn't kill autocomplete.

Related explainers

typescript
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';

How a JWT strategy authenticates in NestJS

authentication jwt passport
Intermediate 7 steps
typescript
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { catchError, filter, take, switchMap } from 'rxjs/operators';

Refreshing auth tokens in an Angular interceptor

http-interceptor token-refresh rxjs
Advanced 8 steps
go
package auth
 
import (
	"net/http"

Setting and reading secure session cookies in Go

cookies session-management security
Intermediate 6 steps
python
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from django.core.cache import cache
from django.db.models import F
from django.http import JsonResponse

Ranked full-text search in Django

full-text-search debouncing caching
Advanced 9 steps
typescript
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Reflector } from '@nestjs/core';
import { JwtService } from '@nestjs/jwt';

How a GraphQL auth guard works in NestJS

authentication authorization jwt
Intermediate 7 steps
typescript
type ResizeCallback = (size: { width: number; height: number }) => void;
 
export function observeResize(callback: ResizeCallback, delay = 150) {
  let timeoutId: ReturnType<typeof setTimeout> | undefined;

Debouncing window resize in TypeScript

debounce closures event-listeners
Intermediate 6 steps

Share this explainer

Here's the card — post it anywhere.

A city autocomplete service in Angular — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code