Make RxJS and Angular Signal coexist in Pokemon Application

Reading Time: 5 minutes

Loading

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.

let's go

Old Pokemon Component with RxJS codes

// 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

// pokemon-component.ts

currentPokemonId = signal(1);

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

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>
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

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

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>
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.

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.

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,
         }))
       );
}
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.

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

// 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.

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:

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

Replace RxJS with Angular Signals in Pokemon Application

Reading Time: 4 minutes

Loading

Introduction

I wrote the simple Pokemon application in Angular 15 and RxJS to display image URLs of a specific Pokemon. In this use case, I would like to replace RxJS with Angular signals to simplify reactive codes. When code refactoring completes, ngOnInit method does not have any RxJs code and can delete. Moreover, ViewChild is redundant and no more NgIf and AsyncPipe imports.

Steps will be as follows:

  • Create a signal to store current Pokemon id
  • Create a computed signal that builds the image URLs of the Pokemon
  • Update inline template to use signal and computed signal instead
  • Delete NgIf and AsyncPipe imports
let's go

Old Pokemon Component with RxJS codes

// pokemon.component.ts

...omitted import statements...

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe, NgIf],
  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>
    </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>;

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

  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);

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

          return 100;
        }, 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));
  }
}

I will rewrite the Pokemon component to replace RxJS with Angular signals, make ngOnInit useless and delete it.

First, I create a signal to store current Pokemon id

// pokemon-component.ts

currentPokemonId = signal(1);

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

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>
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

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

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>
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.

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 and they can be removed from imports array.

New Pokemon Component using Angular Signals

// pokemon.component.ts

import { Component, ChangeDetectionStrategy, signal, computed } from '@angular/core';

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

@Component({
  selector: 'app-pokemon',
  standalone: true,
  template: `
    <h2>
      Use Angular Signal to 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>
    </div>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
  readonly min = 1;
  readonly max = 100;
  currentPokemonId = signal(1);

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

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

The new version has zero dependency of RxJS codes, does not implement NgOnInit interface and ngOnInit method. It deletes many lines of codes to become easier to read and understand.

This is it and I have rewritten the Pokemon application to replace RxJS with Angular signals.

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon-signal/tree/main/projects/pokemon-signal-demo-1
  2. Stackblitz: https://stackblitz.com/edit/angular-yd67rk?file=src%2Fpokemon%2Fpokemon%2Fpokemon.component.ts
  3. Youtube: https://youtu.be/88X3jaAobDQ
  4. PokeAPI: https://pokeapi.co/

How to lazy-load routes and import standalone components in Angular

Reading Time: 4 minutes

Loading

Introduction

In Angular, routing is a key feature of application to navigate users to different pages to render components. A typical enterprise Angular application would lazy load routes and standalone components to maintain small main bundle to reduce the initial load time. In advanced case, such application even loads root-level and children routes to render parent and children components.

In this blog post, I would like to cover three cases of lazy loading single route and standalone component:

  • Lazy load default route
  • Lazy load routes that exist in the route array
  • Lazy load non-existing route
let's go

Define application route array

// app.route.ts

import { Route } from '@angular/router';

// lazy-load standalone component
export const APP_ROUTES: Route[] = [{
  path: 'pokemon',
  loadComponent: () => import('./pokemon/pokemon/pokemon.component')
    .then(mod => mod.PokemonComponent)
}, 
{
  path: '',
  pathMatch: 'full',
  loadComponent: () => import('./home/home.component').then(mod => mod.HomeComponent)
},
{
  path: '**',
  loadComponent: () => import('./page-not-found/page-not-found.component')
    .then(mod => mod.PageNotFoundComponent)
}];

In app.route.ts file, I defined a route array and the application should lazy load the routes when they are clicked. When the path of the route is /pokemon, Angular imports PokemonComponent. When users reach the default URL, they should see HomeComponent standalone component. For other non-existent routes, Angular imports PageNotFoundComponent that redirects to home after 10 seconds.

Set up routes in the application

// main.ts

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [RouterLink, RouterOutlet],  // import RouteLink and RouterOutlet to use in inline template
  template: `
    <ul>
      <li><a routerLink="/" routerLinkActive="active">Home</a></li>
      <li><a routerLink="/pokemon" routerLinkActive="active">Show Pokemon</a></li>
      <li><a routerLink="/bad" routerLinkActive="active">Bad route</a></li>
    </ul>
    <router-outlet></router-outlet>
  `,
  styles: [`...omitted due to brevity...`]
})
export class App {}

bootstrapApplication(App, {
  providers: [
    provideHttpClient(),
    // provider to inject routes, preload all modules and trace route change events
    provideRouter(APP_ROUTES, withPreloading(PreloadAllModules), withDebugTracing())
  ]
});

In main.ts, I provided APP_ROUTES to provideRouter provider. Moreover, the provider has two features; withPreloading that preloads all modules and withDebugTracing that traces routes in debug mode.

In the inline template, each hyperlink has routeLink directive that specifies the path and <router-outlet> is the placeholder of the standalone component. Finally, the page renders three routes for clicking

Lazy load default route

When browser first loads the application or user clicks ‘Home’, routeLink equals to ‘/’ and it meets path: '' condition. Therefore, HomeComponent is imported

{
  path: '',
  pathMatch: 'full',
  loadComponent: () => import('./home/home.component').then(mod => mod.HomeComponent)
}
// home.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-home',
  standalone: true,
    template: `
    <div>
      <h2>Click Pokemon Link</h2>
    </div>
  `,
  styles: [`
    :host {
      display: block;
    }
  `]
})
export class HomeComponent {}

The simple component displays “Click Pokemon Link” header and serves its only purpose.

Lazy load routes that exist

When user clicks “Show Pokemon”, routeLink changes to “/pokemon” and it matches the route path of “pokemon”.

{
  path: 'pokemon',
  loadComponent: () => import('./pokemon/pokemon/pokemon.component')
    .then(mod => mod.PokemonComponent)
}

The route eventually imports Pokemon component that also renders the rest of the child components.

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe, NgIf, PokemonControlsComponent, PokemonPersonalComponent, PokemonTabComponent],
  template: `
    ...render HTML and child components...  
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonComponent {
  retrievePokemon = retrievePokemonFn();
  pokemon$ = getPokemonId().pipe(switchMap((id) => this.retrievePokemon(id)));
}

We hit a bad route, how do we fix it?

In the last scenario, user clicks “Bad route” but “/bad” does not exist in the routes array. Will the application break? Fortunately, I can define path: "**" route that catches all unmatched paths.

{
  path: '**',
  loadComponent: () => import('./page-not-found/page-not-found.component')
    .then(mod => mod.PageNotFoundComponent)
}

This route renders PageNotFoundComponent and informs users that the page does not exist.

// page-not-found.component.ts

@Component({
  selector: 'app-page-not-found',
  standalone: true,
  imports: [AsyncPipe],
  template: `
    <div>
      <h2>Page not found</h2>
      <p>Return home after {{ countDown$ | async }} seconds</p>
    </div>
  `,
  styles: [`...omitted due to brevity...`]
})
export class PageNotFoundComponent implements OnInit {
  countDown$ = timer(0, 1000)
    .pipe(
      map((value) => 10 - value),
      takeWhile((value) => value >= 0),
      shareReplay(1),
    );

  redirectHome$ = this.countDown$.pipe(
      tap((value) => {
        if (value <= 0) {
          inject(Router).navigate(['']);
        }
      }),
      takeUntilDestroyed()
    );

  ngOnInit(): void {
    this.redirectHome$.subscribe();
  }
}

This component displays “Page not found” message and redirects home after 10 seconds.

The following Stackblitz repo shows the final results:

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-13
  2. Stackblitz: https://stackblitz.com/edit/angular-hl6z5z?file=src/main.ts
  3. Youtube: https://www.youtube.com/watch?v=3nh4ct7NExs
  4. PokeAPI: https://pokeapi.co/

Render ngTemplates dynamically using ViewContainerRef in Angular

Reading Time: 5 minutes

Loading

Introduction

In Angular, we use ngTemplateOutlet to display ngTemplate when we know which ones and the exact number of them during development time. The other option is to render ngTemplates using ViewContainerRef class. ViewContainerRef class has createEmbeddedView method that instantiates embedded view and inserts it to a container. When there are many templates that render conditionally, ViewContainerRef solution is cleaner than multiple ngIf/ngSwitch expressions that easily clutter inline template.

In this blog post, I created a new component, PokemonTabComponent, that is consisted of radio buttons, two ngTemplates and a ng-container element. When clicking a radio button, the component renders stats ngTemplate, abilities ngTemplate or both dynamically. Rendering ngTemplates using ViewContainerRef is more sophisticated than ngTemplateOutlet and we will see its usage for the rest of the post.

let's go

The skeleton code of Pokemon Tab component

// pokemon-tab.component.ts

@Component({
  selector: 'app-pokemon-tab',
  standalone: true,
  imports: [NgFor],
  template: `
    <div style="padding: 0.5rem;" class="container">
      <div>
        <div>
          <input type="radio" id="all" name="selection" value="all" checked>
          <label for="all">All</label>
        </div>
        <div>
          <input type="radio" id="stats" name="selection" value="stats">
          <label for="stats">Stats</label>
        </div>
        <div>
          <input type="radio" id="abilities" name="selection" value="abilities">
          <label for="abilities">Abilities</label>
        </div>
      </div>
      <ng-container #vcr></ng-container>
    </div>

    <ng-template #stats let-pokemon>
      <div>
        <p>Stats</p>
        <div *ngFor="let stat of pokemon.stats" class="flex-container">
          <label>
            <span style="font-weight: bold; color: #aaa">Name: </span>
            <span>{{ stat.name }}</span>
          </label>
          <label>
            <span style="font-weight: bold; color: #aaa">Base Stat: </span>
            <span>{{ stat.base_stat }}</span>
          </label>
          <label>
            <span style="font-weight: bold; color: #aaa">Effort: </span>
            <span>{{ stat.effort }}</span>
          </label>
        </div>
      </div>
    </ng-template>

    <ng-template #abilities let-pokemon>
      <div>
        <p>Abilities</p>
        <div *ngFor="let ability of pokemon.abilities" class="flex-container">
          <label>
            <span style="font-weight: bold; color: #aaa">Name: </span>
            <span>{{ ability.name }}</span>
          </label>
          <label>
            <span style="font-weight: bold; color: #aaa">Is hidden? </span>
            <span>{{ ability.is_hidden ? 'Yes' : 'No' }}</span>
          </label>
          <label>&nbsp;</label>
        </div>
      </div>
    </ng-template>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonTabComponent {
  @Input()
  pokemon: FlattenPokemon;
}

In PokemonTabComponent standalone component, nothing happened when clicking the radio buttons. However, the behavior would change when I called ViewContainerRef class to create embedded views from ngTemplates and insert the templates to the container named vcr. The end result is to display templates “stats” and/or “abilities” conditionally without using structure directives such as ngIf and ngSwitch.

Access embedded ngTemplates in PokemonTabComponent

In order to create embedded views, I need to pass TemplateRef to createEmbeddedView method of ViewContainerRef class. I can specify the template variable of ngTemplate in ViewChild to obtain the TemplateRef.

// pokemon-tab.component.ts
// obtain reference to ng-container element
@ViewChild('vcr', { static: true, read: ViewContainerRef })
vcr!: ViewContainerRef;

// obtain reference ngTemplate named stats
@ViewChild('stats', { static: true, read: TemplateRef })
statsRef!: TemplateRef<any>;

// obtain reference ngTemplate named abilities
@ViewChild('abilities', { static: true, read: TemplateRef })
abilitiesRef!: TemplateRef<any>;

In the above codes, statsRef and abilitiesRef are the TemplateRef<any> instances of stats and abilities ngTemplates respectively.

Add click event handler to radio buttons

When I click any radio button, I wish to look up the TemplateRef<any> instances, create embedded views and append them to vcr. In inline template, I add click event handler to the radio buttons that execute renderDynamicTemplates method to render templates.

// pokemon-tab.component.ts

template:`  
...
<div>
   <input type="radio" id="all" name="selection" value="all"
      checked (click)="selection = 'ALL'; renderDyanmicTemplates();">
   <label for="all">All</label
</div>
<div>
   <input type="radio" id="stats" name="selection" value="stats"
     (click)="selection = 'STATISTICS'; renderDyanmicTemplates();">
   <label for="stats">Stats</label>
</div>
<div>
  <input type="radio" id="abilities" name="selection" value="abilities"
     (click)="selection = 'ABILITIES'; renderDyanmicTemplates();">
   <label for="abilities">Abilities</label>
</div>
...
`

selection: 'ALL' | 'STATISTICS' | 'ABILITIES' = 'ALL';
embeddedViewRefs: EmbeddedViewRef<any>[] = [];

cdr = inject(ChangeDetectorRef);

private getTemplateRefs() {
  if (this.selection === 'ALL') {
      return [this.statsRef, this.abilitiesRef];
  } else if (this.selection === 'STATISTICS') {
      return [this.statsRef];
  }

  return [this.abilitiesRef];
}

renderDyanmicTemplates(currentPokemon?: FlattenPokemon) {
    const templateRefs = this.getTemplateRefs();
    const pokemon = currentPokemon ? currentPokemon : this.pokemon;

    this.vcr.clear();
    for (const templateRef of templateRefs) {
      const embeddedViewRef = this.vcr.createEmbeddedView(templateRef, { $implicit: pokemon });
      this.embeddedViewRefs.push(embeddedViewRef);
      // after appending each embeddedViewRef to conta iner, I trigger change detection cycle
      this.cdr.detectChanges();
    }
}

this.selection keeps track of the currently selected radio button and the value determines the template/templates that get(s) rendered in the container.

const templateRefs = this.getTemplateRefs(); examines the value of this.selection, constructs and returns TemplateRef<any>[]. When the selection is ‘ALL’, getTemplateRefs returns both template references. When the selection is ‘STATISTICS’, getTemplateRefs returns the template reference of stats template in an array. Otherwise, the method returns the template reference of abilities template in an array.

this.vcr.clear(); clears all components from the container and inserts new components dynamically

const embeddedViewRef = this.vcr.createEmbeddedView(templateRef, { $implicit: pokemon }); instantiates and appends the new embedded view to the container, and returns a EmbeddedViewRef.

{ $implicit: pokemon } is the template context and the template has a local variable named pokemon that references a Pokemon object.

this.embeddedViewRefs.push(embeddedViewRef); stores all the EmbeddedViewRef instances and later I destroy them in ngOnDestroy to avoid memory leak.

this.cdr.detectChanges(); triggers change detection to update the component and its child components.

This summarizes how to render ngTemplates using createEmbeddedView method of ViewContainerRef Class.

Destroy embedded views in OnDestroy lifecycle hook

Implement OnDestroy interface by providing a concrete implementation of ngOnDestroy.

export class PokemonTabComponent implements OnDestroy {
   ...other logic...

   ngOnDestroy() {
    // destroy embeddedViewRefs to avoid memory leak
    for (const viewRef of this.embeddedViewRefs) {
      if (viewRef) {
        viewRef.destroy();
      }
    }
  }
}

The method iterates embeddedViewRefs array and frees the memory of each EmbeddedViewRef to avoid memory leak.

Render embedded views in ngOnInit

When the application is initially loaded, the page is blank because it has not called renderDynamicTemplates yet. It is easy to solve by implementing OnInit interface and calling the method in the body of ngOnInit.


export class PokemonTabComponent implements OnDestroy, OnInit {
   ...
   ngOnInit(): void {
     this.renderDynamicTemplates();
   }
}

When Angular runs ngOnInit , the initial value of this.selection is ‘ALL’ and renderDynamicTemplates displays both templates at first.

Now, the initial load renders both templates but I have another problem. Button clicks and form input change do not update the Pokemon input of the embedded views. It can be solved by implementing OnChanges interface and calling renderDynamicTemplates again in ngOnChanges.

Re-render embedded views in ngOnChanges

export class PokemonTabComponent implements OnDestroy, OnInit, OnChanges {
   ...

   ngOnChanges(changes: SimpleChanges): void {
      this.renderDynamicTemplates(changes['pokemon'].currentValue);
   }
}

changes['pokemon'].currentValue is the new Pokemon input. this.renderDynamicTemplates(changes['pokemon'].currentValue) passes the new Pokemon to template context and the new embedded views display the new value in the container.

The following Stackblitz repo shows the final results:

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-12
  2. Stackblitz: https://stackblitz.com/edit/angular-rorch6?file=src/pokemon/pokemon-tab/pokemon-tab.component.ts
  3. Youtube: https://www.youtube.com/watch?v=1HToLFXGcNI
  4. PokeAPI: https://pokeapi.co/

Render dynamic components in Angular using viewContainerRef

Reading Time: 4 minutes

Loading

Introduction

In Angular, ngComponentOutlet is the simple way to render components dynamically. The other option is to render dynamic components using ViewContainerRef class. ViewContainerRef class has createComponent method that instantiates component and inserts it to a container. It is a powerful technique to add components at runtime; they are not loaded in main bundle to keep the bundle size small. When there are many components that render conditionally, ViewContainerRef solution is cleaner than multiple ng-if expressions that easily bloat the inline template.

In this blog post, I created a new component, PokemonTabComponent, that is consisted of radio buttons and a ng-container element. When clicking a radio button, the component renders PokemonStatsComponent, PokemonAbilitiesComponent or both dynamically. Rendering dynamic components using ViewContainerRef is more sophisticated than ngComponentOutlet and we will see its usage for the rest of the post.

let's go

The skeleton code of Pokemon Tab component

// pokemon-tab.component.ts

@Component({
  selector: 'app-pokemon-tab',
  standalone: true,
  imports: [PokemonStatsComponent, PokemonAbilitiesComponent],
  template: `
    <div style="padding: 0.5rem;" class="container">
      <div>
        <div>
          <input type="radio" id="all" name="selection" value="all" checked">
          <label for="all">All</label>
        </div>
        <div>
          <input type="radio" id="stats" name="selection" value="stats">
          <label for="stats">Stats</label>
        </div>
        <div>
          <input type="radio" id="abilities" name="selection" value="abilities">
          <label for="abilities">Abilities</label>
        </div>
      </div>
      <ng-container #vcr></ng-container>
    </div>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonTabComponent {
  @Input()
  pokemon: FlattenPokemon;
}

In PokemonTabComponent standalone component, nothing happened when clicking the radio buttons. However, the behavior would change when I started to add new codes to render dynamic components using ViewContainerRef. The end result is to display the components in the ng-container named vcr.

Map radio button selection to component types

// pokemon-tab.enum.ts

export enum POKEMON_TAB {
    ALL = 'all',
    STATISTICS = 'statistics',
    ABILITIES = 'abilities'
}

First, I defined enum to represent different radio button selections.

// pokemon-tab.component.ts

componentTypeMap = {
    [POKEMON_TAB.ALL]: [PokemonStatsComponent, PokemonAbilitiesComponent],
    [POKEMON_TAB.STATISTICS]: [PokemonStatsComponent],
    [POKEMON_TAB.ABILITIES]: [PokemonAbilitiesComponent],
  };

Then, I defined an object map to map the enum members to the component type lists.

@ViewChild('vcr', { static: true, read: ViewContainerRef })
vcr!: ViewContainerRef;

Next, I used the ViewChild() decorator to obtain the reference to ng-container element and invoke ViewContainerRef class to append dynamic components to the container.

Add click event handler to radio buttons

When I click any radio button, I wish to look up the component types in componentTypeMap, iterate the list to create new component references and append them to vcr. In inline template, I add click event handler to the radio buttons that execute renderDynamicComponents method to render dynamic components.

// pokemon-tab.component.ts

template:`  
...<div>
   <input type="radio" id="all" name="selection" value="all"
   checked (click)="selection = 'ALL'; renderDynamicComponents();">
   <label for="all">All</label>
</div>
<div>
    <input type="radio" id="stats" name="selection" value="stats"
       (click)="selection = 'STATISTICS'; renderDynamicComponents();">
    <label for="stats">Stats</label>
</div>
<div>
    <input type="radio" id="abilities" name="selection" value="abilities"
       (click)="selection = 'ABILITIES'; renderDynamicComponents();">
    <label for="abilities">Abilities</label>
</div>...
`

selection: 'ALL' | 'STATISTICS' | 'ABILITIES' = 'ALL';
componentRefs: ComponentRef<PokemonStatsComponent | PokemonAbilitiesComponent>[] = [];
cdr = inject(ChangeDetectorRef);

async renderDynamicComponents(currentPokemon?: FlattenPokemon) {
    const enumValue = POKEMON_TAB[this.selection as keyof typeof POKEMON_TAB];
    const componentTypes = this.componentTypeMap[enumValue];

    // clear dynamic components shown in the container previously    
    this.vcr.clear();
    for (const componentType of componentTypes) {
      const newComponentRef = this.vcr.createComponent(componentType);
      newComponentRef.instance.pokemon = currentPokemon ? currentPokemon : this.pokemon;
      // store component refs created
      this.componentRefs.push(newComponentRef);
      // run change detection in the component and child components
      this.cdr.detectChanges();
    }
}

this.selection keeps track of the currently selected radio button and the value determines the component/components that get(s) rendered in the container.

this.vcr.clear(); removes all components from the container and inserts new components dynamically

const newComponentRef = this.vcr.createComponent(componentType); instantiates and appends the new component to the container, and returns a ComponentRef.

newComponentRef.instance.pokemon = currentPokemon ? currentPokemon : this.pokemon;

PokemonStatsComponent and PokemonAbilitiesComponent expect a pokemon input; therefore, I assign a Pokemon object to newComponentRef.instance.pokemon where newComponentRef.instance is a component instance.

this.componentRefs.push(newComponentRef); stores all the ComponentRef instances and later I destroy them in ngOnDestroy to avoid memory leak.

this.cdr.detectChanges(); triggers change detection to update the component and its child components.

Destroy dynamic components in OnDestroy lifecycle hook

Implement OnDestroy interface by providing a concrete implementation of ngOnDestroy.

export class PokemonTabComponent implements OnDestroy {
   ...other logic...

   ngOnDestroy(): void {
    // release component refs to avoid memory leak
    for (const componentRef of this.componentRefs) {
      if (componentRef) {
        componentRef.destroy();
      }
    }
  }
}

The method iterates componentRefs array and frees the memory of each ComponentRef to avoid memory leak.

Render dynamic components in ngOnInit

When the application is initially loaded, the page is blank because it has not called renderDynamicComponents yet. It is easy to solve by implementing OnInit interface and calling the method in the body of ngOnInit.


export class PokemonTabComponent implements OnDestroy, OnInit {
   ...
   ngOnInit(): void {
     this.renderDynamicComponents();
   }
}

When Angular runs ngOnInit , the initial value of this.selection is ‘ALL’ and renderDynamicComponents displays both components at first.

Now, the initial load renders both components but I have another problem. Button clicks and form input change do not update the Pokemon input of the dynamic components. It can be solved by implementing OnChanges interface and calling renderDynamicComponents again in ngOnChanges.

Re-render dynamic components in ngOnChanges

export class PokemonTabComponent implements OnDestroy, OnInit, OnChanges {
   ...

   ngOnChanges(changes: SimpleChanges): void {
      this.renderDynamicComponents(changes['pokemon'].currentValue);
   }
}

changes['pokemon'].currentValue is the new Pokemon input. this.renderDynamicComponents(changes['pokemon'].currentValue) passes the new Pokemon to assign to the new components and to display them in the container dynamically.

The following Stackblitz repo shows the final results:

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-11
  2. Stackblitz: https://stackblitz.com/edit/angular-dstj5e?file=src/pokemon/pokemon-tab/pokemon-tab.component.ts
  3. Youtube: https://www.youtube.com/watch?v=1Torp0Xgv0U
  4. PokeAPI: https://pokeapi.co/

Render dynamic components the simple way in Angular – ngComponentOutlet

Reading Time: 4 minutes

Loading

Introduction

In Angular, there are a few ways to render templates and components dynamically. There is ngTemplateOutlet that can render different instances of ng-template conditionally. When we use components, we can apply ngComponentOutlet to render the dynamic components the simple way or ViewContainerRef the complex way.

In this blog post, I created a new component, PokemonTabComponent, that is consisted of hyperlinks and a ng-container element. When clicking a link, the component renders PokemonStatsComponent, PokemonAbilitiesComponent or both dynamically. NgComponentOutlet helps render the dynamic components the simple way and we will see its usage for the rest of the post.

let's go

The skeleton code of Pokemon Tab component

// pokemon-tab.component.ts

@Component({
  selector: 'app-pokemon-tab',
  standalone: true,
  imports: [
    PokemonStatsComponent, PokemonAbilitiesComponent
  ],
  template: `
    <div style="padding: 0.5rem;">
      <ul>
        <li><a href="#" #selection data-type="ALL">All</a></li>
        <li><a href="#" #selection data-type="STATISTICS">Stats</a></li>
        <li><a href="#" #selection data-type="ABILITIES">Abilities</a></li>
      </ul>
    </div>
    <app-pokemon-stats [pokemon]="pokemon"></app-pokemon-stats>
    <app-pokemon-abilities [pokemon]="pokemon"></app-pokemon-abilities>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonTabComponent {
  @Input()
  pokemon: FlattenPokemon;
}

In PokemonTabComponent standalone component, nothing happened when clicking the links. However, the behaviour would change when I added new codes and and ngComponentOutlet in the inline template to render the dynamic components.

Compose RxJS code to map mouse click to components

// pokemon-tab.enum.ts

export enum POKEMON_TAB {
    ALL = 'all',
    STATISTICS = 'statistics',
    ABILITIES = 'abilities'
}

First, I defined enum to represent different mouse clicks.

// pokemon-tab.component.ts

componentMap = {
    [POKEMON_TAB.STATISTICS]: [PokemonStatsComponent],
    [POKEMON_TAB.ABILITIES]: [PokemonAbilitiesComponent],
    [POKEMON_TAB.ALL]: [PokemonStatsComponent, PokemonAbilitiesComponent],
}

Then, I defined an object map to map the enum members to the component lists.

@ViewChildren('selection', { read: ElementRef })
selections: QueryList<ElementRef<HTMLLinkElement>>;

Next, I used the ViewChildren() decorator to query the hyperlinks and the building blocks are in place to construct RxJS code in ngAfterViewInit.

// pokemon-tab.component.ts

export class PokemonTabComponent implements AfterViewInit, OnChanges {
  ...
} 
// pokemon-tab.component.ts

components$!: Observable<DynamicComponentArray>;

ngAfterViewInit(): void {
    const clicked$ = this.selections.map(({ nativeElement }) => fromEvent(nativeElement, 'click')
        .pipe(
          map(() => POKEMON_TAB[(nativeElement.dataset['type'] || 'ALL') as keyof typeof POKEMON_TAB]),
          map((value) => this.componentMap[value]),
        )
    );

    // merge observables to emit enum value and look up Component types
    this.components$ = merge(...clicked$)
      .pipe(startWith(this.componentMap[POKEMON_TAB.ALL]));
}
const clicked$ = this.selections.map(({ nativeElement }) => fromEvent(nativeElement, 'click')
   .pipe(
       map(() => POKEMON_TAB[(nativeElement.dataset['type'] || 'ALL') as keyof typeof POKEMON_TAB]),
       map((value) => this.componentMap[value]),
    )
 );
  • map(() => POKEMON_TAB[(nativeElement.dataset[‘type’] || ‘ALL’) as keyof typeof POKEMON_TAB]) – convert the value of type data attribute to POKEMON_TAB enum member
  • map((value) => this.componentMap[value]) – use POKEMON_TAB enum member to look up component list to render dynamically
  • Assign the component list to clicked$ Observable
this.components$ = merge(...clicked$)
    .pipe(startWith(this.componentMap[POKEMON_TAB.ALL]));
  • merge(…clicked$) – merge the Observables to emit the component list
  • startWith(this.componentMap[POKEMON_TAB.ALL]) – both PokemonStatsComponent and PokemonAbilitiesComponent are rendered initially

Apply ngComponentOutlet to PokemonTabComponent

ngComponentOutlet directive has 3 syntaxes and I will use the syntax that expects component and injector. It is because I require to inject Pokemon object to PokemonStatsComponent and PokemonAbilitiesComponent respectively

<ng-container *ngComponentOutlet="componentTypeExpression;
                                  injector: injectorExpression;
                                  content: contentNodesExpression;">
</ng-container>

In the inline template, I replaced <app-pokemon-stats> and <app-pokemon-abilities> with <ng-container> as the host of the dynamic components.

<ng-container *ngFor="let component of components$ | async">
     <ng-container *ngComponentOutlet="component; injector: myInjector"></ng-container>
</ng-container>

In the imports array, import NgFor, AsyncPipe and NgComponentOutlet.

imports: [PokemonStatsComponent, PokemonAbilitiesComponent, NgFor, AsyncPipe, NgComponentOutlet],

In the inline template, myInjector has not declared and I will complete the implementation in ngAfterViewInit and ngOnChange.

Let’s define a new injection token for the Pokemon object

// pokemon.constant.ts

import { InjectionToken } from "@angular/core";
import { FlattenPokemon } from "../interfaces/pokemon.interface";

export const POKEMON_TOKEN = new InjectionToken<FlattenPokemon>('pokemon_token');

createPokemonInjectorFn is a high-order function that returns a function to create an injector to inject an arbitrary Pokemon object.

// pokemon.injector.ts

export const createPokemonInjectorFn = () => {
  const injector = inject(Injector);

  return (pokemon: FlattenPokemon) =>
    Injector.create({
      providers: [{ provide: POKEMON_TOKEN, useValue:pokemon }],
      parent: injector
    });
}

In PokemonTabComponent, I imported POKEMON_TOKEN and createPokemonInjectorFn to assign new injector to myInjector in ngAfterViewInit and ngOnChanges.

// pokemon-tab.component.ts

injector = inject(Injector);
myInjector!: Injector;
createPokemonInjector = createPokemonInjectorFn();
markForCheck = inject(ChangeDetectorRef).markForCheck;

ngAfterViewInit(): void {
    this.myInjector = this.createPokemonInjector(this.pokemon);
    this.markForCheck();
    ...
}

ngOnChanges(changes: SimpleChanges): void {
    this.myInjector = this.createPokemonInjector(changes['pokemon'].currentValue);
}

At this point, the application would not work because PokemonStatsComponent and PokemonAbilitiesComponent had not updated to inject the Pokemon object. This would be the final step to have a working example.

Inject Pokemon to PokemonStatsComponent and PokemonAbilitiesComponent

// pokemon-stats.component.ts

export class PokemonStatsComponent {
  pokemon = inject(POKEMON_TOKEN);
}
// pokemon-abilities.component.ts

export class PokemonAbilitiesComponent {
  pokemon = inject(POKEMON_TOKEN);
}

The following Stackblitz repo shows the final results:

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-9
  2. Stackblitz: https://stackblitz.com/edit/angular-gxreww?file=src/pokemon/pokemon-tab/pokemon-tab.component.ts
  3. Youtube: https://www.youtube.com/watch?v=jXFsU81C9jI
  4. PokeAPI: https://pokeapi.co/

Compose RxJS custom operators to hide complex logic

Reading Time: 4 minutes

Loading

Introduction

This post describes how to compose RxJS custom operators from existing operators to hide complex logic. Not only RxJS custom operator encapsulates logic but it also promotes reusable operator in RxJS stream. In the Stackblitz example, I refactored a custom operator to emit a number between minimum value and maximum value. Then, the number is passed to Pokemon API to retrieve a Pokemon.

let's go

Pokemon Controls component without custom operators

// pokemon-controls.component.ts

@Component({
  selector: 'app-pokemon-controls',
  standalone: true,
  imports: [FormsModule, NgFor],
  template: `...omitted due to brevity...`,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonControlsComponent implements OnDestroy, AfterViewInit {
  @ViewChildren(PokemonButtonDirective)
  btns: QueryList<PokemonButtonDirective>;

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

  searchId = 1;
  pokemonService = inject(PokemonService);
  subscription!: Subscription;

  ngAfterViewInit(): void {
    const btns$ = this.btns.map((btn) => btn.click$);
    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.subscription = merge(...btns$, 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),
      )
      .subscribe((pokemonId) => this.pokemonService.updatePokemonId(pokemonId));
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

In PokemonControlsComponent standalone component, inputId$ and this.subscription are results of existing RxJS operators. inputId$ emits the inputted Pokemon id and I can create an operator that emits an integer between the lower bound and upper bound inclusively.

On the other hand, merge operator merges the Observables, uses existing operators to derive the Pokemon Id, manage and cache the Pokemon state. Similarly, all these steps can encapsulate in a custom operator to emit the cached Pokemon id.

Compose RxJS custom operators to emit input field

// emit-pokemon-id.operator.ts

import { Observable, debounceTime, distinctUntilChanged, filter, map } from "rxjs"
import { POKEMON_ACTION } from "../enums/pokemon.enum"

export const emitPokemonId = (minPokemonId = 1, maxPokemonId = 100) =>
  (source: Observable<any>) => source.pipe(
    debounceTime(300),
    distinctUntilChanged((prev, curr) => prev.searchId === curr.searchId),
    filter((form) => form.searchId >= minPokemonId && form.searchId <= maxPokemonId),
    map((form) => Math.floor(form.searchId)),
    map((value) => ({
      value,
      action: POKEMON_ACTION.OVERWRITE,
    }))
  );

emitPokemonId is a custom RxJS operator that emits the value of the input field. source is an Observable that emits { searchId: number } Object and the searchId will be validated to ensure it is between the lower bound and the upper bound inclusively.

  • debounceTime(300) – wait 300 milliseconds to ensure no ensuing input
  • distinctUntilChanged((prev, curr) => prev.searchId === curr.searchId) – compare the current search value is different than the previous one
  • filter((form) => form.searchId >= minPokemonId && form.searchId <= maxPokemonId) – validate the search value is between the minimum and maximum values
  • map((form) => Math.floor(form.searchId)) – Truncate the search value to emit an integer
  • map((value) => ({ value, action: POKEMON_ACTION.OVERWRITE })) – emit the search value and the next action

I have completed the first custom operator and will use it to replace the RxJS logic in PokemonControlsComponent.

// pokemon-controls.component.ts

import { emitPokemonId } from '../custom-operators/emit-pokemon-id.operator';

ngAfterViewInit(): void {
    const btns$ = this.btns.map((btn) => btn.click$); 
    const inputId$ = this.myForm.form.valueChanges.pipe(emitPokemonId());
}

Compose RxJS custom operators to cache Pokemon id

// derive-pokemon-id.operator.ts

import { Observable, scan, startWith, shareReplay } from "rxjs";
import { POKEMON_ACTION } from "../enums/pokemon.enum";

export const derivePokemonId = (minPokemonId = 1, maxPokemonId = 100) => 
  (source: Observable<{ value: number, action: POKEMON_ACTION }>) => 
    source.pipe(
      scan((acc, { value, action }) => { 
        if (action === POKEMON_ACTION.OVERWRITE) {
          return value;
        } else if (action === POKEMON_ACTION.ADD) {
          const potentialValue = acc + value;
          if (potentialValue >= minPokemonId && potentialValue <= maxPokemonId) {
            return potentialValue;
          } else if (potentialValue < minPokemonId) {
            return minPokemonId;
          }

          return maxPokemonId;
        }

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

derivePokemonId is a custom RxJS operator that derives the current Pokemon id and caches the result. Subsequently, the Pokemon id becomes a path parameter of the Pokemon endpoint to retrieve the current Pokemon. source is an Observable that emits { value: number, action: POKEMON_ACTION } Object to scan to manage the state of Pokemon Id.

  • scan(…) – derive the next Pokemon id. When the next value is out of range, the value is adjusted to either the minimum value or the maximum value.
  • startWith(minPokemonId) – the initial value of Pokemon Id is the minimum pokemon id
  • shareReplay(1) – cache the latest Pokemon Id

I have completed the second custom operator and will use it to replace the RxJS logic in PokemonControlsComponent.

// pokemon-controls.component.ts

import { derivePokemonId } from '../custom-operators/derive-pokemon-id.operator';

ngAfterViewInit(): void {
    const btns$ = this.btns.map((btn) => btn.click$); 
    const inputId$ = this.myForm.form.valueChanges.pipe(emitPokemonId());

    this.subscription = merge(...btns$, inputId$)
      .pipe(derivePokemonId())
      .subscribe((pokemonId) => this.pokemonService.updatePokemonId(pokemonId));
}

After refactoring, the Observables in ngAfterViewInit are easier to read, understand and maintain. The descriptive name of custom operators also describes the objective that existing operators are combined to accomplish.

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-7
  2. Stackblitz: https://stackblitz.com/edit/angular-nkwptv?file=src/pokemon/pokemon-controls/pokemon-controls.component.ts
  3. Derive pokemon id custom operators: https://stackblitz.com/edit/angular-nkwptv?file=src/pokemon/custom-operators/derive-pokemon-id.operator.ts
  4. Emit pokemon id custom operators: https://stackblitz.com/edit/angular-nkwptv?file=src/pokemon/custom-operators/emit-pokemon-id.operator.ts
  5. PokeAPI: https://pokeapi.co/

Too many ViewChild? Try ViewChildren to query DOM elements

Reading Time: 3 minutes

Loading

Introduction

This post describes how to refactor component to use ViewChildren to query DOM elements. It is a common case when inline template has several elements of the same type and the component uses ViewChild to query them. When this pattern occurs, we can consider to render the elements using ngFor and applying ViewChildren to obtain the elements in QueryList.

let's go

Pokemon Controls component applying ViewChild

// pokemon-controls.component.ts

@Component({
  selector: 'app-pokemon-controls',
  standalone: true,
  imports: [FormsModule],
  template: `
    <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>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonControlsComponent implements OnInit, OnDestroy {
  @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>;
 
  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);

    this.subscription = merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$)
      .pipe(... RxJS logic...)
      .subscribe(...subscribe logic...);
  }

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

In PokemonControlsComponent standalone component, the inline template has four buttons with different template variable names. In the component, I applied ViewChild decorators to access the button elements and create button click Observables.

<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>

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);

Then, I use merge operator to merge these Observables to emit the current Pokemon id and call Pokemon API to retrieve the current Pokemon

Since these buttons have the same type, I can refactor the template to render them using NgFor and assigning the same template variable name. Moreover, I can simplify all occurrences of ViewChild with a single ViewChildren decorator.

Rather than copy and paste <button> four times, I will render the buttons using ngFor. Before using ngFor, NgFor has to be imported because the component is standalone.

<div class="container">
      <button *ngFor="let delta of [-2, -1, 1, 2]" class="btn" #btn [attr.data-delta]="delta">
        {{ delta < 0 ? delta : '+' + delta }}
      </button>
</div>

The inline template renders all the buttons and assigns #btn template variable name to them. [attr.data-delta]="delta" stores the delta in data attribute named delta and the data attribute will be used when creating button click Observables. In the component, I update the code to apply ViewChildren to query the button elements.

@ViewChildren('btn', { read: ElementRef })
btns: QueryList<ElementRef<HTMLButtonElement>>;

this.btns is undefined in ngOnInit; therefore, I move the RxJS logic to ngAfterViewInit.

ngAfterViewInit(): void {
    const btns$ = this.btns.map(({ nativeElement }) => this.createButtonClickObservable(nativeElement));

    this.subscription = merge(...btns$)
      .pipe(...RxJS logic...)
      .subscribe(...subscribe logic...);
}

this.btns is QueryList and merge expects a spread array. Therefore, I iterate the QueryList to construct an array of Observables and pass the array to merge.

createButtonClickObservable(nativeElement: HTMLButtonElement) {
    const value = +(nativeElement.dataset['delta'] || 0);
    return fromEvent(nativeElement, 'click').pipe(
      map(() => ({ value, action: POKEMON_ACTION.ADD }))
    );
}

In createButtonClickObservable, I look up the delta in the data attribute and continue to create the new Observable.

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/blob/main/projects/pokemon-demo-6
  2. Stackblitz: https://stackblitz.com/edit/angular-kvsoxr?file=src%2Fpokemon%2Fpokemon-controls%2Fpokemon-controls.component.ts
  3. Youtube: https://www.youtube.com/watch?v=N74CSvHkuaQ
  4. PokeAPI: https://pokeapi.co/

Interested in latest HTTP response? Use RxJS SwitchMap operator

Reading Time: 3 minutes

Loading

Introduction

This post wants to illustrate how to use switchMap operator to cancel previous HTTP requests and retrieve the latest Pokemon by an id. It is an important technique to preserve scarce network resource on a device such as mobile phone that has limited data plan. It also improves performance because the application does not have to wait for previous requests to return and display until the last request comes back with the final result.

let's go

Bootstrap AppComponent

// main.ts

import 'zone.js/dist/zone';
import { provideHttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { PokemonComponent } from './pokemon/pokemon/pokemon.component';

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [PokemonComponent],
  template: `
    <app-pokemon></app-pokemon>
  `,
})
export class AppComponent {
  name = 'Angular';
}

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

In main.ts, I bootstrapped AppComponent as the root element of the application. It is possible because AppComponent is a standalone component and Component decorator defines standalone: true option. In the imports array, I imported PokemonComponent (that also a standalone component) and it is responsible to display information of a Pokemon. The second argument of bootstrapApplication is an array of providers and provideHttpClient makes HttpClient available for injection in components.

Retrieve Pokemon by HTTP

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

retrievePokemonFn is a high order function that returns a function that calls Pokemon endpoint to retrieve the data by id.

Use switchMap to make latest HTTP Request

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);

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

createButtonClickObservable creates an Observable that emits a number when button click occurs. When button text is +1 or +2, the Observables emit 1 or 2 respectively. When button text is -1 or -2, the Observables emit -1 or -2 respectively.

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

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

btnPokemonId$ is an Observable that emits the Pokemon id

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

When btnPokemonId$ emits a Pokemon id, I use switchMap to cancel previous HTTP requests and make a new request to retrieve the Pokemon. The result is then assigned to this.pokemon$ that is an Observable of Pokemon.

Render Pokemon in inline template

<ng-container *ngIf="pokemon$ | async as pokemon">
    <div class="pokemon-container">
       <label>Id:
          <span>{{ pokemon.id }}</span>
       </label>
       <label>Name:
          <span>{{ pokemon.name }}</span>
       </label>
       <label>Height:
          <span>{{ pokemon.height }}</span>
       </label>
       <label>Weight:
          <span>{{ pokemon.weight }}</span>
       </label>
    </div>
    <div class="container">
       <img [src]="pokemon.front_shiny" />
       <img [src]="pokemon.back_shiny" />
    </div>
 </ng-container>

Inline template uses async pipe to resolve pokemon$ Observable and displays the details of the Pokemon.

This is it and I have added HTTP capability to call Pokemon API. Moreover, I leverage switchMap to end earlier requests and trigger a new one whenever new pokemon id is emitted.

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:

  1. Github Repo: https://github.com/railsstudent/ng-pokemon/tree/main/projects/pokemon-demo-3
  2. Stackblitz: https://stackblitz.com/edit/angular-6qkuog?file=src/pokemon/pokemon/pokemon.component.ts
  3. Youtube: https://youtu.be/Y-AUQEYj7qg
  4. PokeAPI: https://pokeapi.co/

Reactive user interface in Angular with RxJS

Reading Time: 4 minutes

Loading

Introduction

This post wants to illustrate how powerful RxJS is when building a reactive user interface in Angular. The application is consisted of a group of buttons that can increment and decrement Pokemon id. When button click occurs, the observable emits the new id and renders the images of the new Pokemon.

let's go

Bootstrap AppComponent

// main.ts

import 'zone.js/dist/zone';
import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { PokemonComponent } from './pokemon/pokemon/pokemon.component';

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [PokemonComponent],
  template: `
    <app-pokemon></app-pokemon>
  `,
})
export class AppComponent {}

bootstrapApplication(AppComponent).catch((err) => console.log(err));

In main.ts, I bootstrapped AppComponent as the root element of the application. It is possible because AppComponent is a standalone component and Component decorator defines standalone: true option. In the imports array, I imported PokemonComponent (that also a standalone component) and it is responsible to implement the reactive user interface with RxJS.

// pokemon-component.ts

import { AsyncPipe, NgIf } from '@angular/common';
import { Component, ElementRef, OnInit, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import { fromEvent, map, merge, Observable, scan, shareReplay, startWith } from 'rxjs';

@Component({
  selector: 'app-pokemon',
  standalone: true,
  imports: [AsyncPipe],
  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>
    </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>;

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

  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);

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

          return 100;
        }, 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));
  }
}

PokemonComponent imports AsyncPipe and NgIf in the imports array because the inline template makes use of async and ngIf.

Implement reactive user interface with rxJS

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);

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

createButtonClickObservable creates an Observable that emits a number when button click occurs. When button text is +1 or +2, the Observables emit 1 or 2 respectively. When button text is -1 or -2, the Observables emit -1 or -2 respectively.

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

          return 100;
     }, 1),
     startWith(1),
     shareReplay(1),
   );
merge(btnMinusTwo$, btnMinusOne$, btnAddOne$, btnAddTwo$)
  • Merge button observables to emit the delta that is either positive or negative
scan((acc, value) => { 
   const potentialValue = acc + value;
   if (potentialValue >= 1 && potentialValue <= 100) {
       return potentialValue;
   } else if (potentialValue < 1) {
       return 1;
   }

   return 100;
}, 1)
  • User scan operator to update the Pokemon id. When Pokemon id is less than 1, reset the id to 1. When Pokemon id is greater than 100, reset the id to 100.
  • The application displays the first 100 Pokemons only
startWith(1)
  • Set the initial Pokemon id to 1 to display the first Pokemon
shareReplay(1)
  • Cache the Pokemon id
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`
    }))
);

When this.btnPokemonId$ emits a new Pokemon id, it is mapped to front and back image URLs and assigned to this.images$ Observable.

<div class="container" *ngIf="images$ | async as images">
   <img [src]="images.frontUrl" />
   <img [src]="images.backUrl" />
</div>

Inline template uses async pipe to resolve images$ Observable and the image tags update the source to display the new Pokemon.

This is it and I have built a reactive user interface with RxJS and Angular

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:

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