RxJS Operators Every Angular Developer Should Know
Tags: rxjs, angular, javascript
Published: 2023-04-20 16:08:07

Today, one of our teammates was curious about which RxJS operators they'd need to create a search feature in Angular. I let them know that while RxJS has loads of operators, they don't need to know them all. I usually stick with Map, Filter, Tap, SwitchMap, ConcatMap, CombineLatest, StartWith, DistinctUntilChanged, DebounceTime, and CatchError.

So instead of overwhelming our teammate with every marble diagram, I thought showing them a practical example would be more helpful. I used the "dummyjson API" to create a search feature and included all the essential RxJS operators I mentioned earlier. This way, they could see how everything works together in a real-life scenario. 😊

## Building The Search

We must create a product search feature for the Product Search page, which fetches data from the "dummyjson API." Users can search for products by name, and the app will display the results.

We have two main actors `search.component.ts` and \`search.service.ts\`.

### The Search Component

* The Search component has an HTML input; we listen to the event (input) every time the user types a keystroke.
    
* The event is managed by the `searchByTerm` method invokes the search service with each keystroke.
    
* We utilize the async pipe to obtain data from the products$ observable and the data emitted by this.searchService.products$.
    

```typescript
import { Component, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SearchService } from './search.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'search',
  standalone: true,
  providers: [SearchService],
  imports: [CommonModule],
  template: `
    <div>
      <h1>Search</h1>
      <input type="text" #searchTerm (input)="searchByTerm(searchTerm.value)" />
      <div class="product" *ngIf="products$ | async as products">
        <div *ngFor="let product of products">
          <h3>{{ product.title }}</h3>
          <img [alt]="product.title" [src]="product.thumbnail" />
        </div>
      </div>
    </div>
  `,
})
export class SearchComponent {
  #searchService = inject(SearchService);
  products$: Observable<any> = this.#searchService.products$;

  searchByTerm(value: string) {
    this.#searchService.searchByText(value);
  }
}
```

> 👉🏽 We could enhance this process by using [fromEvent](https://rxjs.dev/api/index/function/fromEvent) and piping the data, but for now, we'll focus on showcasing some popular operators.

## The Search Service

This is the crux of the article: We use RxJS operators in combination with HttpClient to retrieve data and make API requests, and we pipe the data with the operators. However, the communication is made possible thanks to a special guest: the Observable Subject.

Let's take a moment to explain the Observable Subject, as some readers may have never used it or might be unfamiliar with it.

The `Subject` is a type of `Observable` that enables both emitting data and subscribing to it.

```typescript
 #searchTermSubject = new Subject<string>()
```

When the user types in the component, it triggers the `searchByText` function in the service. We then utilize the `searchTermSubject` to emit the data using the `.next()` method.

Next, we create the observable `products$` from `searchTermSubject` emissions and apply all the RxJS operators to it.

```typescript
products$ = this.searchTermSubject.asObservable().pipe(
//the rxjsoperators
)
```

We aim to incorporate the following features into the search:

* Prevent excessive API requests when the user types rapidly.
    
* Avoid making duplicate requests to the API.
    
* Skip searches with fewer than three letters.
    
* Fetch data from the API only after the previous conditions have been met.
    
* Extract a specific property from the API response.
    

The final code looks like this:

```typescript
import {inject, Injectable} from '@angular/core';
import {HttpClient} from "@angular/common/http";
import {catchError, debounceTime, distinctUntilChanged, filter, map, of, Subject, switchMap, tap} from "rxjs";

@Injectable()
export class SearchService {

  http = inject(HttpClient);
  #API ='https://dummyjson.com/products/search'

  #searchTermSubject = new Subject<string>()

  products$ = this.searchTermSubject.asObservable().pipe(
    debounceTime(500),
    distinctUntilChanged(),
    filter(termSearch => termSearch.trim().length > 5),
    tap((searchTerm) => console.log(`The search term: ${searchTerm}`)),
    switchMap((searchTerm) => this.fetchProducts(searchTerm)),
    map((response: any) => response.products)
  );

  searchByText(term: string) {
    this.searchTermSubject.next(term.trim());
  }

  private fetchProducts(searchTerm: string) {
    const apiUrl = `${this.#API}?q=${searchTerm}`;
    return this.http.get(apiUrl).pipe(
      catchError((error) => {
        console.error('Error getting Products', error)
        return of([])
      })
    );
  }
}
```

Ok, but what are all these operators doing? let's explain one by one.

## The RxJS Operators

1. **DebounceTime**: Reducing Rapid Events The `debounceTime` operator limits the rate at which events are processed. By using `debounceTime(500)`, we ensure that our service only processes user input every 500 milliseconds, preventing excessive API requests when the user types rapidly.
    
    ```typescript
    debounceTime(500),
    ```
    
2. **DistinctUntilChanged**: Eliminating Duplicates The `distinctUntilChanged` operator ensures that consecutive duplicate values are not emitted. This helps to avoid making duplicate requests to the API.
    
    ```typescript
    distinctUntilChanged(),
    ```
    
3. **Filter**: Refining the Search The `filter` operator refines our query by ensuring it is at least three characters long before proceeding to the API. This narrows down the search results.
    
    ```typescript
    filter(q => q.length > 2),
    ```
    
4. **SwitchMap**: Handling Observables, The `switchMap` operator allows us to switch between observables. In this example, we use `switchMap` to fetch data from the API only after the user input has passed through `debounceTime`, `distinctUntilChanged`, and `filter`.
    
    ```typescript
    switchMap(q => this.http.get(`${this.apiURL}?q=${q}`).pipe(/* ... */)),
    ```
    
5. **CatchError**: Handling Errors The `catchError` operator helps us handle errors gracefully if the API call encounters any issues.
    
    ```typescript
    catchError(error => /* handle error */),
    ```
    
6. **Map**: Transforming Data The `map` operator transforms the data emitted by an observable. In our example, we use it to extract the `product` property from the API response.
    
    ```typescript
    map((response: any) => response.products),
    ```
    
7. **Tap**: Side Effects The `tap` operator allows us to perform side effects, such as logging, without affecting the main data flow. In our case, we use it to log the fetched products.
    

```typescript
  tap((products) => console.log('Fetched products:', products))
```

It's finished; we've learned each operator's purpose and explanations for their use in our search service.

* [Stackblitz](https://stackblitz.com/edit/search-rxjs)
    
* [GitHub Code](https://github.com/danywalls/search-and-learn-rxjs-operators)
    

**Conclusion**

We've used all the operators we discussed earlier, and I hope they made sense to you. These are the ones I use quite often. There's another popular operator called combineLatest that we didn't include here, but don't worry. You can check it out in another article.

To summarize, combined operators are super handy when working with RxJS. Even though there are many operators, the ones we discussed should help you with your everyday tasks. I really hope these examples make your RxJS journey a bit easier!

> If like please share!😊