Discover the Power of Real-Time Search wit RxJS and Angular standalone components

Reading Time: 5 minutes

 61 total views


This is day 6 of Wes Bos’s JavaScript 30 challenge where I create real-time search input box to filter out cities and states in the USA. This is a challenge for me because I had to rewrite the original real-time search using Angular and adopting the styles of the framework. In the tutorial, I created the components using RxJS, custom operators, Angular standalone components and removed the NgModules. Moreover, Angular HTTP Client is responsible for retrieving the JSON data from GitHub gist.

In this blog post, I define a function that injects HttpClient, retrieves USA cities from external JSON file and caches the response. Next, I create an observable that emits search input to filter out USA cities and states. Finally, I use async pipe to resolve the observable in the inline template to render the matching results.

let's go

Create a new Angular project

ng generate application day6-ng-type-ahead

Bootstrap AppComponent

First, I convert AppComponent into standalone component such that I can bootstrap AppComponent and inject providers in main.ts.

// app.component.ts

import { Component } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TypeAheadComponent } from './type-ahead/type-ahead/type-ahead.component';

  selector: 'app-root',
  standalone: true,
  imports: [
  template: '<app-type-ahead></app-type-ahead>',
  styles: [`
    :host {
      display: block;
export class AppComponent {
  title = 'Day 6 NG Type Ahead';

  constructor(titleService: Title) {

In Component decorator, I put standalone: true to convert AppComponent into a standalone component.

Instead of importing TypeAheadComponent in AppModule, I import TypeAheadComponent (that is also a standalone component) in the imports array because the inline template references it. It is because main.ts uses bootstrapApplication to render AppComponent as the root component of the application. When compiler sees <app-type-ahead> in the inline template and AppComponent does not import TypeAheadComponent, the application fails to compile.

// main.ts

import { provideHttpClient } from '@angular/common/http';
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { environment } from './environments/environment';

if (environment.production) {

    providers: [provideHttpClient()]
  .catch(err => console.error(err));

provideHttpClient is a function that configures HttpClient to be available for injection.

Next, I delete AppModule because it is not used anymore.

Declare Type Ahead component

I declare standalone component, TypeAheadComponent, to create a component with search box. To verify the component is a standalone, standalone: true is specified in the Component decorator.

├── app.component.ts
└── type-ahead
    ├── custom-operators
    │   └── find-cities.operator.ts
    ├── interfaces
    │   └── city.interface.ts
    ├── pipes
    │   ├── highlight-suggestion.pipe.ts
    │   └── index.ts
    └── type-ahead
        ├── type-ahead.component.scss
        └── type-ahead.component.ts

find-cities.ts is a custom RxJS operator that receives search value and filters out USA cities and states by it.

// type-ahead.component.ts

import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { ChangeDetectionStrategy, Component, OnInit, ViewChild, inject } from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { Observable, shareReplay } from 'rxjs';
import { findCities } from '../custom-operators/find-cities.operator';
import { City } from '../interfaces/city.interface';
import { HighlightSuggestionPipe } from '../pipes/highlight-suggestion.pipe';

const getCities = () => {
  const httpService = inject(HttpClient);
  const endpoint = '';
  return httpService.get<City[]>(endpoint).pipe(shareReplay(1));

  selector: 'app-type-ahead',
  standalone: true,
  imports: [
  template: `
    <form class="search-form" #searchForm="ngForm">
      <input type="text" class="search" placeholder="City or State" [(ngModel)]="searchValue" name="searchValue">
      <ul class="suggestions" *ngIf="suggestions$ | async as suggestions">
        <ng-container *ngTemplateOutlet="suggestions?.length ? hasSuggestions : promptFilter; context: { suggestions, searchValue }"></ng-container>

    <ng-template #promptFilter>
      <li>Filter for a city</li>
      <li>or a state</li>

    <ng-template #hasSuggestions let-suggestions="suggestions" let-searchValue="searchValue">
      <li *ngFor="let suggestion of suggestions">
        <span [innerHtml]="suggestion | highlightSuggestion:searchValue"></span>
        <span class="population">{{ suggestion.population | number }}</span>
  styleUrls: ['./type-ahead.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
export class TypeAheadComponent implements OnInit {

  @ViewChild('searchForm', { static: true })
  searchForm!: NgForm;

  searchValue = ''
  suggestions$!: Observable<City[]>;
  cities$ = getCities();

  ngOnInit(): void {
    this.suggestions$ = this.searchForm.form.valueChanges.pipe(findCities(this.cities$));

TypeAheadComponent imports CommonModule, FormsModule and HighlightSuggestionPipe in the imports array. CommonModule is included to make ngIf and async pipe available in the inline template. After importing FormsModule, I can build a template form to accept search value. Finally, HighlightSuggestionPipe highlights the search value in the search results for aesthetic purpose.

cities$ is an observable that fetches USA cities from external JSON file. Angular 15 introduces inject that simplifies HTTP request logic in a function. Thus, I don’t need to inject HttpClient in the constructor and perform the same logic.

const getCities = () => {
  const httpService = inject(HttpClient);
  const endpoint = '';
  return httpService.get<City[]>(endpoint).pipe(shareReplay(1));

cities$ = getCities();

suggestions$ is an observable that holds the matching cities and states after search value changes. It is subsequently resolved in inline template to render in a list.

Create RxJS custom operator

It is a matter of taste but I prefer to refactor RxJS operators into custom operators when observable has many lines of code. For suggestion$, I refactor the chain of operators into findCities custom operator and reuse it in TypeAheadComponent.

// find-cities.operator.ts

const findMatches = (formValue: { searchValue: string }, cities: City[]) => {
    const wordToMatch = formValue.searchValue;

    if (wordToMatch === '') {
        return [];

    const regex = new RegExp(wordToMatch, 'gi');
    // here we need to figure out if the city or state matches what was searched
    return cities.filter(place => || place.state.match(regex));

export const findCities = (cities$: Observable<City[]>) => {
    return (source: Observable<{ searchValue: string }>) =>
            map(([formValue, cities]) => findMatches(formValue, cities)),
  • skip(1) – The first valueChange emits undefined for unknown reason; therefore, skip is used to discard it
  • debounceTime(300) – emit search value after user stops typing for 300 milliseconds
  • distinctUntilChanged() – do nothing when search value is unchanged
  • withLatestFrom(cities$) – get the cities returned from HTTP request
  • map(([formValue, cities]) => findMatches(formValue, cities)) – call findMatches to filter cities and states by search value
  • startWith([]) – initially, the search result is an empty array

Finally, I use findCities to compose suggestion$ observable.

Use RxJS and Angular to implement observable in type ahead component

// type-ahead.component.ts

this.suggestions$ = this.searchForm.form.valueChanges
  • this.searchForm.form.valueChanges – emit changes in template form
  • findCities(this.cities$) – apply custom operator to find matching cities and states

This is it, we have created a real-time search that filters out USA cities and states by search value.

Final Thoughts

In this post, I show how to use RxJS and Angular standalone components to create real-time search of the USA cities and states. The application has the following characteristics after using Angular 15’s new features:

  • The application does not have NgModules and constructor boilerplate codes.
  • In main.ts, the providers array provides the HttpClient by invoking providerHttpClient function
  • In TypeAheadComponent, I inject HttpClient in a function to make http request and obtain the results. In construction phase, I assign the function to cities$ observable
  • Using inject to inject HttpClient offers flexibility in code organization. I can define getCities function in the component or move it to a utility file. Pre-Angular 15, HttpClient is usually injected in a service and the service has a method to make HTTP request and return the results

This is the end of the blog post and I hope you like the content and continue to follow my learning experience in Angular and other technologies.


  1. Github Repo:
  2. Live demo:
  3. Wes Bos’s JavaScript 30 Challenge: