Make RxJS and Angular Signal coexist in Pokemon Application
Tags: angular, tutorial, webdev
Published: 2023-06-01 15:34:37

##Introduction
I wrote a simple Pokemon application in Angular 15 and RxJS to display image URLs of a specific Pokemon. There are 2 methods to update current Pokemon Id in order to update image URLS. The first method is by clicking buttons to increment or decrement the id by a delta. The other method is to enter a value to a number input to overwrite the current Pokemon Id. However, the number input field continues to use `debounceTime`, `distinctUntilChanged` and `filter` RxJS operators to perform validation and limit the values getting emitted. Therefore, the challenge is to simplify reactive codes, and make RxJS and Angular signal coexist.

###Old Pokemon Component with RxJS codes
```typescript
// pokemon.component.ts

...omitted import statements...

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe, NgIf, FormsModule],
  template: `
    <h1>
      Display the first 100 pokemon images
    </h1>
    <div>
      <label>Pokemon Id:
        <span>{{ btnPokemonId$ | async }}</span>
      </label>
      <div class="container" *ngIf="images$ | async as images">
        <img [src]="images.frontUrl" />
        <img [src]="images.backUrl" />
      </div>
    </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>
      <pre>
        searchId: {{ searchId }}
      </pre>
    </div>
  `,
  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;

  btnPokemonId$!: Observable<number>;
  images$!: Observable<{ frontUrl: string, backUrl: string }>;

  searchId = 1;
  
  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,
        }))
      );

    this.btnPokemonId$ = merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$, inputId$)
      .pipe(
        scan((acc, { value, action }) => { 
          if (action === POKEMON_ACTION.OVERWRITE) {
            return value;
          } else if (action === POKEMON_ACTION.ADD) {
            const potentialValue = acc + value;
            if (potentialValue >= 1 && potentialValue <= 100) {
              return potentialValue;
            } else if (potentialValue < 1) {
              return 1;
            }

            return 100;
          }

          return acc;
        }, 1),
        startWith(1),
        shareReplay(1),
      );

      const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';
      this.images$ = this.btnPokemonId$.pipe(
        map((pokemonId: number) => ({
          frontUrl: `${pokemonBaseUrl}/shiny/${pokemonId}.png`,
          backUrl: `${pokemonBaseUrl}/back/shiny/${pokemonId}.png`
        }))
      );
  }

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

My tasks are to retain most of the logic `$inputId`, delete all occurrences of `ViewChild` and replace the rest of the RxJS codes with Angular signals. I will refactor RxJS codes to signals and rewrite `$inputId` Observable last.

First, I create a signal to store current Pokemon id

```typescript
// pokemon-component.ts

currentPokemonId = signal(1);
```

Then, I modify inline template to add `click` event to the button elements to update `currentPokemonId` 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)

<div class="container">
   <button class="btn" (click)="updatePokemonId(-2)">-2</button>
   <button class="btn" (click)="updatePokemonId(-1)">-1</button>
   <button class="btn" (click)="updatePokemonId(1)">+1</button>
   <button class="btn" (click)="updatePokemonId(2)">+2</button>
 </div>
```

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.currentPokemonId.update((pokemonId) => {
      const newId = pokemonId + delta;
      return Math.min(Math.max(this.min, newId), this.max);
    });
}
```

When button is clicked, `updatePokemonId` sets `currentPokemonId` to a value between 1 and 100.

Next, I further modify inline template to replace `images$` Observable with `imageUrls` computed signal and `btnPokemonId$` Observable with `currentPokemonId`

```typescript
Before (RxJS)

<div>
   <label>Pokemon Id:
      <span>{{ btnPokemonId$ | async }}</span>
   </label>
   <div class="container" *ngIf="images$ | async as images">
      <img [src]="images.frontUrl" />
      <img [src]="images.backUrl" />
   </div>
</div>
```

```typescript
After (Signal)

<div>
   <label>Pokemon Id:
      <span>{{ currentPokemonId() }}</span>
   </label>
   <div class="container">
      <img [src]="imageUrls().front" />
      <img [src]="imageUrls().back" />
   </div>
</div>
```

In signal version, I invoke `currentPokemonId()` to display the current Pokemon id. `imageUrls` is a computed signal that returns front and back URLs of pokemon.

```typescript
const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';

imageUrls = computed(() => ({
    front: `${pokemonBaseUrl}/shiny/${this.currentPokemonId()}.png`,
    back: `${pokemonBaseUrl}/back/shiny/${this.currentPokemonId()}.png`
}));
```

After applying these changes, the inline template does not rely on async pipe and ngIf. Therefore, I can remove `NgIf` and `AsyncPipe` from the imports array.

###Make RxJS and Angular Signal coexist in Component Class

Now, I have to tackle `$inputId` Observable such that it can invoke RxJS operators and update `currentPokemonId` signal correctly. My solution is to call subscribe and update `currentPokemonId` in it. Calling subscribe creates a subscription unfortunately; therefore, I import `takeUntilDestroyed` to complete the Observable in the constructor.

```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]="searchIdSub.getValue()"
        (ngModelChange)="searchIdSub.next($event)"
        name="searchId" id="searchId" />
```

`[(ngModel)]` is decomposed to `[ngModel]` and `(ngModelChange)` to get my solution to work. `searchIdSub` is a BehaviorSubject that initializes to 1. `NgModel` input is bounded to `searchIdSub.getValue()` and `(ngModelChange)` updates the BehaviorSubject when input value changes.

```typescript
searchIdSub = new BehaviorSubject(1);

constructor() {
    this.searchIdSub
      .pipe(
        debounceTime(300),
        distinctUntilChanged(),
        filter((value) => value >= this.min && value <= this.max),
        takeUntilDestroyed(),
      ).subscribe((value) => this.currentPokemonId.set(value));
}
```

In the constructor, `this.searchIdSub` emits values to RxJS operators and invokes subscribe to update `currentPokemonId` signal. Angular 16 introduces `takeUntilDestroyed` that completes Observable; therefore, I don’t have to implement OnDestroy interface to unsubscribe subscription manually.


###New Pokemon Component using Angular Signals

```typescript
// pokemon.component.ts

...omitted import statements due to brevity...

const pokemonBaseUrl = 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon';

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [FormsModule],
  template: `
    <h2>
      Display the first 100 pokemon images
    </h2>
    <div>
      <label>Pokemon Id:
        <span>{{ currentPokemonId() }}</span>
      </label>
      <div class="container">
        <img [src]="imageUrls().front" />
        <img [src]="imageUrls().back" />
      </div>
    </div>
    <div class="container">
      <button class="btn" (click)="updatePokemonId(-2)">-2</button>
      <button class="btn" (click)="updatePokemonId(-1)">-1</button>
      <button class="btn" (click)="updatePokemonId(1)">+1</button>
      <button class="btn" (click)="updatePokemonId(2)">+2</button>
      <input type="number" 
        [ngModel]="searchIdSub.getValue()"
        (ngModelChange)="searchIdSub.next($event)"
        name="searchId" id="searchId" />
    </div>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
  readonly min = 1;
  readonly max = 100;
  
  searchIdSub = new BehaviorSubject(1);
  currentPokemonId = signal(1);

  imageUrls = computed(() => {
    const pokemonId = this.currentPokemonId();
    return {
      front: `${pokemonBaseUrl}/shiny/${pokemonId}.png`,
      back: `${pokemonBaseUrl}/back/shiny/${pokemonId}.png` 
    }
  });

  constructor() {
    this.searchIdSub
      .pipe(
        debounceTime(300),
        distinctUntilChanged(),
        filter((value) => value >= this.min && value <= this.max),
        takeUntilDestroyed(),
      ).subscribe((value) => this.currentPokemonId.set(value));
  }

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

The new version moves RxJS codes of number input field to the constructor, deletes `OnInit` interface and `ngOnInit` method. The application simplifies reactive codes; signals replaces `btnPokemonId$`, `images$` and their logic. The final component codes are more concise than the prior version.

This is it and I have rewritten the Pokemon application to have both RxJS codes and Angular signals to coexist.

{% embed https://stackblitz.com/edit/angular-xhj2xb?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-2
- Stackblitz: https://stackblitz.com/edit/angular-xhj2xb?file=src%2Fpokemon%2Fpokemon%2Fpokemon.component.ts
- PokeAPI: https://pokeapi.co/