2 ways to create a Typeahead in Angular

A typeahead, also know as an Autocomplete, is useful at narrowing down search results based on user input.

Regardless of which method is used, an Observable is used to capture data in real time as a user types.??

1. Use an RxJS Subject.

File: typeahead-subject.component.ts

import { UserService } from "../services/user.service";

@Component({
  selector: 'app-typeahead-using-subject',
  standalone: true,
  imports: [ReactiveFormsModule, FormsModule, NgForOf, NgFor, AsyncPipe, NgIf],
  templateUrl: './typeahead-subject.component.html',
  styleUrl: './typeahead-subject.component.scss'
})

export class TypeaheadComponentUsingSubject {

  searchTerm$ = new Subject<string>();
  searchResults$: Observable<any[]>; 

  constructor(private userService: UserService) {

    this.searchResults$ = this.searchTerm$.pipe( 
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(term => this.searchFunction(term))
    );
  }

  onSelect(item: any) {
    console.log('selected item', item);
  }
}        

File: typeahead-subject.component.html

<input type="text" (input)="searchTerm$.next($event.target.value)">
<ul>
  <li *ngFor="let result of searchResults$ | async" (click)="onSelect(result)">
    {{ result.name }} 
  </li>
</ul>        

This block of code in the backing Typescript component is where the magic happens:

this.searchResults$ = this.searchTerm$.pipe(

? debounceTime(300),?

? distinctUntilChanged(),?

? switchMap(term => this.userService.getUsers(term))

);

searchTerm$ is the Subject (which is a type of Observable) that allows us to listen to the user input in real time. Since it is an observable, we can now chain RxJS methods to enhance the user experience.

debounceTime(300) - defers calling the next pipe chained function by 300 milliseconds. This reduces the amount of API/server called and makes sure the API call only happens when a user stops typing and not on every character entered.

distinctUntilChanged() - ensures that the API or switchMap function is only called when a new search term is entered and avoids duplicate searches.

switchMap() - performs the API call and returns it to the searchResults$ observable to show the list in the <li


2. Using a FormControl

File: typeahead-fc.component.ts

import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, map, catchError } from 'rxjs/operators';
import { UserService } from "../services/user.service";

@Component({
  selector: 'app-typeahead-fc',
  templateUrl: './typeahead-fc.component.html',
  styleUrls: ['./typeahead-fc.component.scss']
})
export class TypeaheadFcComponent implements OnInit {
  searchControl = new FormControl();
  filteredOptions$: Observable<any[]> = [];

constructor(private userService: UserService) {
}

  ngOnInit() {
    this.filteredOptions$ = this.searchControl.valueChanges.pipe(
      debounceTime(300), // Delay the request to prevent too many calls
      distinctUntilChanged(), // Only emit when the current value is different from the last
      switchMap(value => this.userService.getUsers(value)) // Switch to a new observable when the input changes and return the final value to the filteredOptions$ observable
    )
  }

  onOptionSelected(option: string) {
    console.log('Selected option:', option);
  }
}        

File: typeahead-fc.component.html

<input type="text" [formControl]="searchControl" placeholder="Search..." />

<ul>
  <li *ngFor="let option of filteredOptions" (click)="onOptionSelected(option)">
    {{ option }}
  </li>
</ul>        

When using a FormControl for the typeahead you can then .subscribe or .pipe on the .valueChanges Observable of the FormControl Angular core class. You don't not need a FormGroup to use a FormControl but most of the time you already have a form/FormGroup when using a FormControl.

Notice I am also using an observable along with the 'async' pipe to avoid having to create observable arrays myself and writing code to destroy the lingering observable subscription. The async pipe subscribes to the API call and destroys the Observable on its own. This helps prevent memory leaks and messy code. The only drawback is that now you will need to use RxJS to transform API returned data instead of straight Array functions.

I prefer the FormControl way of creating a typeahead because it it seems more straightforward and easier to understand for new developers than something like the RxJS Subject or fromEvent to get the input value.

Let me know your thoughts or feel free to ask questions.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了