How to convert HTTP Response from Observable to Angular signal with toSignal
Tags: angular, signals, tutorial
Published: 2023-06-10 13:51:37

##Introduction

I extended my Pokemon application to call an API to retrieve a Pokemon by id. The HTTP request returned an Observable that required ngIf and async pipe to resolve in order to render the results in inline template. In this blog post, I want to demonstrate how to convert HTTP response to Signal with `toSignal`. `toSignal` returns `T | undefined` but we can provide an initial value to the function to get rid of the undefined type.

###Old Pokemon Component with RxJS codes

```typescript
// pokemon.component.ts

...omitted import statements for brevity...

const retrievePokemonFn = () => {
  const httpClient = inject(HttpClient);
  return (id: number) => httpClient.get<Pokemon>(`https://pokeapi.co/api/v2/pokemon/${id}`)
    .pipe(
      map((pokemon) => ({
        id: pokemon.id,
        name: pokemon.name,
        height: pokemon.height,
        weight: pokemon.weight,
        back_shiny: pokemon.sprites.back_shiny,
        front_shiny: pokemon.sprites.front_shiny,
      }))
    );
}

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe, FormsModule, NgIf, NgTemplateOutlet],
  template: `
    <h1>
      Display the first 100 pokemon images
    </h1>
    <div>
      <ng-container *ngIf="pokemon$ | async as pokemon">
        <div class="pokemon-container">
          <ng-container *ngTemplateOutlet="details; context: { $implicit: 'Id: ', value: pokemon.id }"></ng-container>
          <ng-container *ngTemplateOutlet="details; context: { $implicit: 'Name: ', value: pokemon.name }"></ng-container>
          <ng-container *ngTemplateOutlet="details; context: { $implicit: 'Height: ', value: pokemon.height }"></ng-container>
          <ng-container *ngTemplateOutlet="details; context: { $implicit: 'Weight: ', value: pokemon.weight }"></ng-container>
        </div>
        <div class="container">
          <img [src]="pokemon.front_shiny" />
          <img [src]="pokemon.back_shiny" />
        </div>
      </ng-container>
    </div>
    <div class="container">
      <button class="btn" #btnMinusTwo>-2</button>
      <button class="btn" #btnMinusOne>-1</button>
      <button class="btn" #btnAddOne>+1</button>
      <button class="btn" #btnAddTwo>+2</button>
      <form #f="ngForm" novalidate>
        <input type="number" [(ngModel)]="searchId" [ngModelOptions]="{ updateOn: 'blur' }" 
          name="searchId" id="searchId" />
      </form>
    </div>
    <ng-template #details let-name let-value="value">
      <label><span style="font-weight: bold; color: #aaa">{{ name }}</span>
        <span>{{ value }}</span>
      </label>
    </ng-template>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent implements OnInit {
  @ViewChild('btnMinusTwo', { static: true, read: ElementRef })
  btnMinusTwo: ElementRef<HTMLButtonElement>;

  @ViewChild('btnMinusOne', { static: true, read: ElementRef })
  btnMinusOne: ElementRef<HTMLButtonElement>;

  @ViewChild('btnAddOne', { static: true, read: ElementRef })
  btnAddOne: ElementRef<HTMLButtonElement>;

  @ViewChild('btnAddTwo', { static: true, read: ElementRef })
  btnAddTwo: ElementRef<HTMLButtonElement>;

  @ViewChild('f', { static: true, read: NgForm })
  myForm: NgForm;

  pokemon$!: Observable<FlattenPokemon>;
  searchId = 1;
  retrievePokemon = retrievePokemonFn();
  
  ngOnInit() {
    const btnMinusTwo$ = this.createButtonClickObservable(this.btnMinusTwo, -2);
    const btnMinusOne$ = this.createButtonClickObservable(this.btnMinusOne, -1);
    const btnAddOne$ = this.createButtonClickObservable(this.btnAddOne, 1);
    const btnAddTwo$ = this.createButtonClickObservable(this.btnAddTwo, 2);

    const inputId$ = this.myForm.form.valueChanges
      .pipe(
        debounceTime(300),
        distinctUntilChanged((prev, curr) => prev.searchId === curr.searchId),
        filter((form) => form.searchId >= 1 && form.searchId <= 100),
        map((form) => form.searchId),
        map((value) => ({
          value,
          action: POKEMON_ACTION.OVERWRITE,
        }))
      );

    const btnPokemonId$ = merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$, inputId$)
      .pipe(
        scan((acc, { value, action }) => { 
          ... derive pokemon id....
        }, 1),
        startWith(1),
        shareReplay(1),
      );

      this.pokemon$ = btnPokemonId$.pipe(switchMap((id) => this.retrievePokemon(id)));
  }

  createButtonClickObservable(ref: ElementRef<HTMLButtonElement>, value: number) {
    return fromEvent(ref.nativeElement, 'click').pipe(
      map(() => ({ value, action: POKEMON_ACTION.ADD }))
    );
  }
}
```

`retrievePokemonFn()` returns a function that accepts an id to retrieve a Pokemon. When `btnPokemonId$` Observable emits an id, the stream invokes `this.retrievePokemon` and assigns the results to `this.pokemon$` Observable. Then, `this.pokemon$` is resolved in inline template to display image URLs and details. My goals are to refactor `ngOnInit` and convert `this.pokemon$` Observable to Angular signal. Then, inline template renders the signal value instead of the resolved Observable.

### Store Reactive results into signal
First, I create a signal to store current Pokemon id

```typescript
// pokemon-component.ts

pokemonId = signal(1);
```

Then, I modify inline template to add `click` event to the button elements to update `pokemonId` signal.

```typescript
Before (RxJS)

<div class="container">
   <button class="btn" #btnMinusTwo>-2</button>
   <button class="btn" #btnMinusOne>-1</button>
   <button class="btn" #btnAddOne>+1</button>
   <button class="btn" #btnAddTwo>+2</button>
</div>
```

```typescript
After (Signal)

<button class="btn" *ngFor="let delta of [-2, -1, 1, 2]" (click)="updatePokemonId(delta)">{{delta < 0 ? delta : '+' + delta }}</button>
```

In signal version, I remove template variables such that the component does not require `ViewChild` to query `HTMLButtonElement`

```typescript
readonly min = 1;
readonly max = 100;

updatePokemonId(delta: number) {
    this.pokemonId.update((value) => {
      const potentialId = value + delta;
      return Math.min(this.max, Math.max(this.min, potentialId));
    });
}
```

When button is clicked, `updatePokemonId` updates `pokemonId` to a value between 1 and 100.

In Imports array, I include `NgFor` to use ngFor directive

```typescript
imports: [..., NgFor],
```

Now, I declare `searchId` signal to react to changes to number input field

```typescript
// pokemon.component.ts

searchId = signal(1);
```

`searchId` emits search value, streams to subsequent RxJS operators and subscribes to update `pokemonId` signal.

```typescript
Before (RxJS)

ngOnInit() {
    const inputId$ = this.myForm.form.valueChanges
       .pipe(
         debounceTime(300),
         distinctUntilChanged((prev, curr) => prev.searchId === curr.searchId),
         filter((form) => form.searchId >= 1 && form.searchId <= 100),
         map((form) => form.searchId),
         map((value) => ({
           value,
           action: POKEMON_ACTION.OVERWRITE,
         }))
       );
}
```

```typescript
After (Signal)

<input type="number" [ngModel]="searchId()"
   (ngModelChange)="searchId.set($event)"
   name="searchId" id="searchId" />
```

`[(ngModel)]` is decomposed to `[ngModel]` and `(ngModelChange)` to get my solution to work. NgModel input is bounded to `searchId()` and `(ngModelChange)` updates the signal when input value changes.

```typescript
constructor() {
    toObservable(this.searchId)
      .pipe(
        debounceTime(300),
        distinctUntilChanged(),
        filter((value) => value >= this.min && value <= this.max),
        map((value) => Math.floor(value)),
        takeUntilDestroyed(),
      ).subscribe((value) => this.pokemonId.set(value));
}
```

Angular 16 introduces `takeUntilDestroyed` that completes Observable; therefore, I don’t have to implement `OnDestroy` interface to unsubscribe subscription manually.

###Convert Observable to Angular Signal with toSignal

```typescript
import { toSignal } from '@angular/core/rxjs-interop';

const initialValue: DisplayPokemon = {
  id: 0,
  name: '',
  height: -1,
  weight: -1,
  back_shiny: '',
  front_shiny: '',
};

pokemon = toSignal(
    toObservable(this.pokemonId).pipe(switchMap((id) => this.retrievePokemon(id))), { initialValue });
```

When the codes update `pokemonId`, `toObservable(this.pokemonId)` emits the id to `switchMap` operator to retrieve the specific Pokemon. The result of the stream is a Pokemon Observable that is passed to `toSignal` to convert to an Angular signal.

`pokemon` is a signal, I use it to compute rowData signal and pass that signal value to the context object of `ngTemplateOutlet`.

```typescript
rowData = computed(() => {
    const { id, name, height, weight } = this.pokemon();
return [
    { text: 'Id: ', value: id },
    { text: 'Name: ', value: name },
    { text: 'Height: ', value: height },
    { text: 'Weight: ', value: weight }, 
  ] 
});

<div class="pokemon-container">
    <ng-container 
*ngTemplateOutlet="details; context: { $implicit: rowData() }"></ng-container>
</div>
```
I modify ngTemplate to iterate the rowData array to display the label and actual value.

```typescript
<ng-template #details let-rowData>
   <label *ngFor="let data of rowData">
      <span style="font-weight: bold; color: #aaa">{{ data.text }}</span>
      <span>{{ data.value }}</span>
  </label>
</ng-template>
```

I remove `NgIf` and `AsyncPipe` from the imports array because the inline template does not need them anymore. The final array is consisted of FormsModule, NgTemplateOutlet and NgFor.

```typescript
imports: [FormsModule, NgTemplateOutlet, NgFor],
```

###New Pokemon Component using toSignal

```typescript
// retrieve-pokemon.ts
// ...omitted import statements due to brevity...

export const retrievePokemonFn = () => {
  const httpClient = inject(HttpClient);
  return (id: number) => httpClient.get<Pokemon>(`https://pokeapi.co/api/v2/pokemon/${id}`)
    .pipe(
      map((pokemon) => pokemonTransformer(pokemon))
    );
}

const pokemonTransformer = (pokemon: Pokemon): DisplayPokemon => ({
  id: pokemon.id,
  name: pokemon.name,
  height: pokemon.height,
  weight: pokemon.weight,
  back_shiny: pokemon.sprites.back_shiny,
  front_shiny: pokemon.sprites.front_shiny,
});

// pokemon.component.ts
...omitted import statements for brevity...

const initialValue: DisplayPokemon = {
  id: 0,
  name: '',
  height: -1,
  weight: -1,
  back_shiny: '',
  front_shiny: '',
};

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [FormsModule, NgTemplateOutlet, NgFor],
  template: `
    <h2>
      Display the first 100 pokemon images
    </h2>
    <div>
      <ng-container>
        <div class="pokemon-container">
          <ng-container *ngTemplateOutlet="details; context: { $implicit: rowData() }"></ng-container>
        </div>
        <div class="container">
          <img [src]="pokemon().front_shiny" />
          <img [src]="pokemon().back_shiny" />
        </div>
      </ng-container>
    </div>
    <div class="container">
      <button class="btn" *ngFor="let delta of [-2, -1, 1, 2]" (click)="updatePokemonId(delta)">
        {{delta < 0 ? delta : '+' + delta }}
      </button>
      <input type="number" [ngModel]="searchId()" (ngModelChange)="searchId.set($event)"
          name="searchId" id="searchId" />
    </div>
    <ng-template #details let-rowData>
      <label *ngFor="let data of rowData">
        <span style="font-weight: bold; color: #aaa">{{ data.text }}</span>
        <span>{{ data.value }}</span>
      </label>
    </ng-template>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
  readonly min = 1;
  readonly max = 100;

  searchId = signal(1);
  retrievePokemon = retrievePokemonFn();
  pokemonId = signal(1);

  pokemon = toSignal(
    toObservable(this.pokemonId).pipe(switchMap((id) => this.retrievePokemon(id))), { initialValue });

  rowData = computed(() => {
    const { id, name, height, weight } = this.pokemon();
    return [
      { text: 'Id: ', value: id },
      { text: 'Name: ', value: name },
      { text: 'Height: ', value: height },
      { text: 'Weight: ', value: weight }, 
    ] 
  });

  updatePokemonId(delta: number) {
    this.pokemonId.update((value) => {
      const potentialId = value + delta;
      return Math.min(this.max, Math.max(this.min, potentialId));
    });
  }
  
  constructor() {
    toObservable(this.searchId)
      .pipe(
        debounceTime(300),
        distinctUntilChanged(),
        filter((value) => value >= this.min && value <= this.max),
        map((value) => Math.floor(value)),
        takeUntilDestroyed(),
      ).subscribe((value) => this.pokemonId.set(value));
  }
}
```

The new version uses `toSignal` function to convert Pokemon Observable to Pokemon signal with an initial value. After the conversion, I can use `computed` to derive rowData signal and pass the signal value to the inline template to render. Thus, the inline template and logic is less verbose than the previous RxJS version.

This is it and I have enhanced the Pokemon application to make HTTP request and convert the HTTP response to signal. Then, signal functions are called within the inline template to render the results.

{% embed https://stackblitz.com/edit/angular-sqaafu?file=src%2Fpokemon%2Fpokemon%2Fpokemon.component.ts %}

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.

###Resources:

- Github Repo: https://github.com/railsstudent/ng-pokemon-signal/tree/main/projects/pokemon-signal-demo-3
- Stackblitz: https://stackblitz.com/edit/angular-sqaafu?file=src%2Fpokemon%2Fpokemon%2Fpokemon.component.ts
- PokeAPI: https://pokeapi.co/