Analyze ways to retrieve data with signals and HttpClient in Angular
Tags: angular, signal, tutorial, frontend
Published: 2024-02-15 04:23:25

##Introduction

In this blog post, I would like to discuss how signal and HttpClient can retrieve data in Angular.  In my observation, I found 5 data retrieval patterns and each one has its pros and cons. When the post analyzes data retrieval in Angular, I hope the readers can choose their preferred choice and apply it to their Angular projects.

The demo passes a signal to Pokemon API to retrieve Pikachu and uses different patterns to display the results. The data retrieval patterns are:

1. Signal + HttpClient + AsyncPipe
2. Signal + make HttpClient call in effect
3. Signal + HttpClient + toObservable + toSignal + SwitchMap
4. Signal + computedAsync
5. Signal + computedAsync + enable requiredSync to emit sync data

###Install ngxtension dependencies
```bash
npm i --save-exact ngxtension @use-gesture/vanilla
```
Install `ngxtension` to import computedAsync into the demo.

###Bootstrap HttpClient in the application

```typescript
// main.ts

import { provideHttpClient } from '@angular/common/http';

bootstrapApplication(App, {
  providers: [provideHttpClient()]
});
```

###Utility function to retrieve Pikachu

```typescript
// pokemon.interface.ts

export interface Pokemon {
  id: number;
  name: string;
  sprites: {
    front_shiny: string
  };
}

export interface DisplayPokemon {
  id: number;
  name: string;
  img: string;
}

// get-pokemon.ts

export const getPokemonFn = (): (id: number) => Observable<DisplayPokemon> => {
  const httpClient = inject(HttpClient);

  return (id: number) => {
    return httpClient.get<Pokemon>(`https://pokeapi.co/api/v2/pokemon/${id}`)
      .pipe(
        map((p) => ({
          id: p.id,
          name: p.name,
          img: p.sprites.front_shiny
        }))
      );
  }
}
```

`getPokemonFn` is a high-order function that returns a new function to retrieve Pokemon. The anonymous function accepts a Pokemon ID and makes an HTTP request to retrieve a Pokemon from the Pokemon API.

###Signal + HttpClient + AsyncPipe

```typescript
// main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AsyncPipe, PokemonComponent],
  template: `
    <h3>Signal + AsyncPipe + HttpClient</h3>
    @if (pokemon$ | async; as pokemon) {
      <app-pokemon [pokemon]="pokemon" />
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  pokemonId = signal(25);
  getPokemon = getPokemonFn();

  pokemon$ = this.getPokemon(this.pokemonId());
}
```

`App` has a signal with a fixed integer of 25. The component invokes `this.getPokemon` and assigns the Observable to `pokemon$`.  In the inline template, the async pipe resolves `pokemon$` to pokemon variable. Then,  the variable is passed to the required signal input of `PokemonComponent` to display data.

My two cents:   I like RxJS and I have no problem using this pattern to retrieve and display data in the inline template.  If Angular developers want to avoid AsyncPipe and Observable, they can try the other patterns instead.  

###Signal + make HTTP call in effect

```typescript
// main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AsyncPipe, PokemonComponent],
  template: `
    <h3>Signal + make HttpClient call in effect</h3>
    @if (pokemon2(); as pokemon2) {
      <app-pokemon [pokemon]="pokemon2" />
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  pokemonId = signal(25);
  getPokemon = getPokemonFn();

  pokemon2 = signal<DisplayPokemon | null>(null);

  constructor() {
    effect((cleanUp) => {
      const subscription = this.getPokemon(this.pokemonId())
        .subscribe((p) => this.pokemon2.set(p))
    
      cleanUp(() => subscription.unsubscribe());
    });
  }
}
```

In the `App` component, I create `pokemon2` WritableSignal and initialize it with null. When `pokemonId` signal is updated, `effect` runs, makes an HTTP request to retrieve the pokemon, and sets the object to pokemon2 in subscribe().  Then, the inline template passes pokemon2 to PokemonComponent to display the data.

My two cents:   This pattern uses the signal to store the final result and avoids AsyncPipe in the inline template.  However, the component declares a signal explicitly and it could be far from the constructor when the component has many field initializations. Readers scroll up and down the file to find out where `pokemon2` is and what value is assigned to it. Moreover, `effect` creates a new Observable and a new subscription in each query search.  The subscription requires to unsubscribe in the cleanup function to avoid memory leaks.   The cleanup part is subtle and developers can easily forget about it.  

Let's explore other patterns that do not create subscription  

###Signal + HttpClient + toObservable + toSignal + SwitchMap

```typescript
// main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AsyncPipe, PokemonComponent],
  template: `
    <h3>Signal + HttpClient + toObservable + toSignal + SwitchMap</h3>
    @if (pokemon3(); as pokemon3) {
      <app-pokemon [pokemon]="pokemon3" />
    }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  pokemonId = signal(25);
  getPokemon = getPokemonFn();

  pokemon3 = toSignal(
    toObservable(this.pokemonId).pipe(switchMap((id) => this.getPokemon(id)))  
  );
}
```

`toObservable()` exposes `pokemonId` signal to an Observable and emits the pokemon id to the `switchMap` RxJS operator. The `switchMap` operator cancels any unfinished HTTP request and makes a new one to retrieve the Pokemon.  The `pokemon` Observable is subsequently passed to toSignal to get a signal back. Then, the inline template calls the signal function of `pokemon3` and passes the result to `PokemonComponent` to display the data.

My two cents: This pattern is good because HttpClient always finishes and unsubscribes. Moreover, switchMap cancels the previous request before making a new one. However, `toSignal(toObservable(...))` adds boilerplate codes to the component and the component becomes unmaintainable when this pattern is repeatedly seen. When a root service creates an Observable that does not unsubscribe, then `toSignal` can lead to memory leaks. Engineers should use this pattern with care in root services. 

###Signal + computedAsync

```typescript
// main.ts

import { computedAsync } from 'ngxtension/computed-async';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AsyncPipe, PokemonComponent],
  template: `
      <h3>Signal + computedAsync</h3>
      @if (pokemon4(); as pokemon4) {
         <app-pokemon [pokemon]="pokemon4" />
      }
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  pokemonId = signal(25);
  getPokemon = getPokemonFn();

  pokemon4 = computedAsync(() => this.getPokemon(this.pokemonId()));
}
```

Use ngxtension's `computedAsync` to retrieve the Pokemon via HttpClient. 

My two cents: The utility function supports Promise and Observable, and returns a `Signal<T | undefined>`. The function uses `Subject` to emit value and performs cleanup in the callback of `DestroyRef`. The default behavior is switchAll which cancels the previous request. It has all the benefits of the earlier patterns and not the drawbacks. Angular engineers do not have to worry about AsyncPipe, subscriptions that require clean-up, and `toSignal(toObservable(...))` boilerplate codes.

###Signal + computedAsync + enable requiredSync to emit sync data

```typescript
// main.ts

import { computedAsync } from 'ngxtension/computed-async';

const DEFAULT_POKEMON: DisplayPokemon = {
  id: 0,
  name: '',
  img: 'https://placehold.co/400',
};

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [AsyncPipe, PokemonComponent],
  template: `
      <h3>Signal + computedAsync + requireSync = true</h3>
      <app-pokemon [pokemon]="pokemon5()" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class App {
  pokemonId = signal(25);
  getPokemon = getPokemonFn();

  pokemon5 = computedAsync(() => this.getPokemon(this.pokemonId())
    .pipe(startWith(DEFAULT_POKEMON)),
    {
      requireSync: true,
    }
  );
}
```

This pattern also uses ngxtension's `computedAsync` to retrieve the Pokemon via HttpClient. The difference is requireSync option is enabled to emit synchronous data. The signal always has a value and it is impossible to have undefined.  

My two cents: By providing the initial value in the `startWith` RxJS operator, the return type is `Signal<T>` instead of `Signal<T | undefined>`. I don't need to use @if to test undefined before displaying the signal value in the `App` component.

These are all the patterns that I observe when retrieving data via signal and HttpClient. I prefer to use `computedAsync` utility function despite it comes from a third-party library.

My reasoning is as follows:

- No toSignal and toObservable boilerplate codes
- No reference to AsyncPipe and Observable
- No extra declaration of signal. Does not make HTTP requests in effect; therefore,  no cleanup of subscription in the cleanup function

The following Stackblitz repo shows the final results:
{%embed https://stackblitz.com/edit/stackblitz-starters-mab3qg?file=src%2Fmain.ts %}

This is the end of the blog post that analyzes data retrieval patterns in Angular. I hope you like the content and continue to follow my learning experience in Angular, NestJS and other technologies.

##Resources:
- Github Repo: https://github.com/railsstudent/ng-signal-data-retrieval
- Github Page: https://railsstudent.github.io/ng-signal-data-retrieval
- Stackblitz Demo: https://stackblitz.com/edit/stackblitz-starters-mab3qg?file=src%2Fmain.ts