Home > Software design >  How can I subscribe or merge new Observable in RXJS and Angular
How can I subscribe or merge new Observable in RXJS and Angular

Time:04-10

I have a service that always returns Observable<T>, and I cannot change the code of this service.

Supposed I have a button, whenever the button is clicked, I call the method in the service, and it returns a new Observable. How can I update the new data to UI?

Source code and playground on StackBlitz

app.component.ts

import { Component, Injectable, OnInit } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable()
export class Service {
  // Cannot change the code in this class
  public getRandom(): Observable<number> {
    return of(Math.random());
  }
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
  constructor(private service: Service) {}

  public random: number = 0;
  public random$: Observable<number> = new Observable<number>();

  ngOnInit(): void {
    this.random = 0;
  }

  buttonClick(): void {
    // how can I update the random with this.service.getRandom()?
    console.log('button clicked')
  }
}

app.component.html

<h1>{{random}}</h1>

<button (click)="buttonClick()">Get new Random number</button>

CodePudding user response:

One easy way is to convert the Observable to a Promise and use await:

async buttonClick(): Promise<void> {
    const value = await firstValueFrom(random$);
    console.log('button clicked '   value);
}

If the Observable emits more than once and you want all values, use .subscribe().

CodePudding user response:

I highly recommend using the reactive approach:

HTML

<ng-container *ngIf="(random$ | async) as theRandomNumber" >
  <h1>{{ theRandomNumber }}</h1>
</ng-container>

<button (click)="buttonClick()">Get new Random number</button>

ts

export class AppComponent implements OnInit {
  random$: Observable<number>!; // no need to initialize it

  constructor(private service: Service) {}
  
  ngOnInit(): void {}

  buttonClick(): void {
     this.random$ = this.service.getRandom();
  }
}

When you trigger the event click, your public class property random$ will store the observable from your service, then, within your template html, using the async pipe, you subscribe to random$, and it will react for every click event, with this, you keep your ts file cleaner and simple

Now, if for some reason, you need to have that random number within your ts file, you could pipe the observable and still keep this reactive approach:

import { tap } from 'rxjs';

export class AppComponent implements OnInit {
  random$: Observable<number>!; // no need to initialize it
  private random!: number;

  constructor(private service: Service) {}

  ngOnInit(): void {}

  buttonClick(): void {
     this.random$ = this.service.getRandom()
     .pipe(tap((theNumber) => this.random = theNumber));
  }
}
  • Related