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 { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeFrExtra from '@angular/common/locales/extra/fr';
import localeDe from '@angular/common/locales/de';

Locale-aware bootstrapping in Angular

i18n localization dependency-injection
Intermediate 8 steps
typescript
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import * as Joi from 'joi';
 

Validating env config at boot in NestJS

configuration schema-validation environment-variables
Intermediate 8 steps
typescript
import { Inject, Injectable, Logger } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { InjectRepository } from '@nestjs/typeorm';

A cache-aside country lookup in NestJS

cache-aside dependency-injection batch-lookup
Intermediate 8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core';
 
interface Preferences {
  theme: 'light' | 'dark';

A signal-based preferences store in Angular

signals state-management persistence
Intermediate 7 steps
java
public static Map<String, String> parseCookieHeader(String header) {
    Map<String, String> cookies = new LinkedHashMap<>();
    if (header == null || header.isBlank()) {
        return cookies;

Parsing an HTTP Cookie header in Java

string-parsing http url-decoding
Intermediate 6 steps
typescript
import { useEffect, useState } from "react";
 
interface Section {
  id: string;

Building a scroll-spy hook in React

custom-hooks intersectionobserver dom-observation
Intermediate 8 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