How to subscribe to observable multiple times?

Viewed 680

I have a table that needs to be refreshed each time a user delete a row, or edit data with a designate function called reloadUsersData(); I've created a BehaviorSubject that gets the table's data from a service - once the table is loaded - but i'm not sure that my implementation of reloadUsersData is good since i repeat the same code and resubscribe to the same observable ....

    export class TableBasicExample implements OnDestroy, OnInit{
           dataSource$ = new BehaviorSubject<any>([]);
           private subs: Subscription[] = [];

           constructor(private apiService: ApiService) {}
                                
    
     ngOnInit() {

    this.subs.push(
      this.apiService.getUsers().subscribe((res: any) => {
  
        this.dataSource$.next(res);

      }));
     }

     reloadUsersData(){
      // how can i subscribe to the same api more effectively ?
      this.subs.push(
      this.apiService.getUsers().subscribe((res: any) => {
  
        this.dataSource$.next(res);

      }));

     }

     ngOnDestroy(): void {
       this.subs.forEach((us) => us.unsubscribe());
     }

  }
1 Answers

Fortunately, this can be simplified! I'm assuming that .getUsers() emits one array of users and completes. With that in mind, you have the right idea with creating a subject to handle reload events.

One strategy you can use in RxJS is to create an observable that includes a subject in its .pipe(). This is because a Subject inherits all capabilities of an Observable.

Here's the code.

export class TableBasicExample {
  private fetchEvent = new BehaviorSubject<'fetch'>('fetch');
  public users$: Observable<User[]>;

  constructor(private apiService: ApiService) {
    this.users$ = this.fetchEvent.pipe(
      switchMapTo(this.apiService.getUsers())
    );
  }

  reloadUsersData() {
    this.fetchEvent.next('fetch');
  }
}

We create a BehaviorSubject (which is an extension of Subject) to handle our fetch event. We have it emit the string 'fetch', but it could honestly be any value.

Next, we declare the main Observable we use for our table rendering. It will subscribe to fetchEvent and will switchMap to an inner observable (.getUsers()) and emit its value.

Last, we have our public method that will emit a new value in our private BehaviorSubject.

In your component's template file, you can subscribe to this observable using the async pipe.

<table *ngIf="users$ | async as users">
  <tr *ngFor="user of users">
    <!-- table row data -->
  </tr>
</table>
<button (click)="reloadUsersData()">Reload</button>

The async pipe handles the subscribe/unsubscribe events for you, so you no longer need onInit and onDestroy in your component's TS file.

And that's it! When reloadUsersData() is invoked, it causes fetchEvent to emit a new value. Because users$ has an active subscription, it will receive that new value, and again subscribe to the inner switchMap observable.

Note: The switchMapTo(obs$) operator is similar to switchMap((value)=>$obs) operator. It's just a little less code because we don't actually need the value from our BehaviorSubject.

Related