skip to Main Content

I’ve followed this guide on how to cancel HTTP requests with the switchMap Operator Guide

I have following Component in my Angular project

import { Component, OnInit } from '@angular/core';
import { BehaviorSubject, Observable, forkJoin, skip, switchMap } from 'rxjs';
import { Todo } from 'src/app/model/todo.model';
import { User } from 'src/app/model/user.model';
import { TodoService } from 'src/app/service/todo.service';
import { UserService } from 'src/app/service/user.service';

@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.scss']
})
export class TodoComponent implements OnInit{

  todos: Todo[] = [];
  users: User[] = [];

  fetchTodos$ = new BehaviorSubject<Observable<Todo[]>>(null);
  fetchUsers$ = new BehaviorSubject<Observable<User[]>>(null);

  public constructor(
    private todoService: TodoService,
    private userService: UserService
  ){}

  ngOnInit(): void {
    this.fetchTodos$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(todos => {
      this.todos = todos;
    });

    this.fetchUsers$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(users => {
      this.users = users;
    });

    this.loadData();
  }

  loadData(){
    const getAllTodosObservable = this.todoService.getAllTodos();
    this.fetchTodos$.next(getAllTodosObservable);

    const getAllUsersObservable = this.userService.getAllUsers();
    this.fetchUsers$.next(getAllUsersObservable);

    forkJoin([this.fetchTodos$, this.fetchUsers$])
    .subscribe(_ => {
      this.processTodos();
    });
  }

  //Only process todos when all todos and users have been loaded
  processTodos(){
    console.log("Processing todos...")
  }
}

The problem is that forkJoin subscribe will never be called because the BehaviourSubjects are not completed.

forkJoin([this.fetchTodos$, this.fetchUsers$])
    .subscribe(_ => {
      this.processTodos();
  });

How would I achieve calling processTodos() when both requests are done without losing the functionality of HTTP cancellation?

I tried calling complete() on the BehaviourSubjects when requests were done like so

ngOnInit(): void {
    this.fetchTodos$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(todos => {
      this.todos = todos;
      this.fetchTodos$.complete();
    });

    this.fetchUsers$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(users => {
      this.users = users;
      this.fetchUsers$.complete();
    });

    this.loadData();
  }

This will actually call the subscribe method of the forkJoin, but only once and not more.

I’ve also tried using combineLatest instead of forkJoin like this

loadData(){
    const getAllTodosObservable = this.todoService.getAllTodos();
    this.fetchTodos$.next(getAllTodosObservable);

    const getAllUsersObservable = this.userService.getAllUsers();
    this.fetchUsers$.next(getAllUsersObservable);

    combineLatest([this.fetchTodos$, this.fetchUsers$])
    .subscribe(_ => {
      this.processTodos();
    });
  }

The problem with that is, processTodos() will be called twice due to two values being emitted.

Last thing I tried was to use forkJoin() with the HTTP observables directly instead of the behaviour subjects like following

loadData(){
    const getAllTodosObservable = this.todoService.getAllTodos();
    this.fetchTodos$.next(getAllTodosObservable);

    const getAllUsersObservable = this.userService.getAllUsers();
    this.fetchUsers$.next(getAllUsersObservable);

    forkJoin([getAllTodosObservable, getAllUsersObservable])
    .subscribe(_ => {
      this.processTodos();
    });
  }

Here I have to problem that the HTTP requests are being sent twice because of two subscriptions.
Subscriptions in ngOnInit:

ngOnInit(): void {
    this.fetchTodos$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(todos => {
      this.todos = todos;
      this.fetchTodos$.complete();
    });

    this.fetchUsers$.pipe(
      skip(1),
      switchMap(innerObservable => innerObservable)
    )
    .subscribe(users => {
      this.users = users;
      this.fetchUsers$.complete();
    });

    this.loadData();
  }

2

Answers


  1. It seems you tried all combinations except for one.

    It should work if you use combineLatest with the direct data streams (instead of the BehaviorSubjects).

    Something like this:

      // Get all posts
      // Get all users
      postData$ = this.http.get<Post[]>(this.postUrl);
      userData$ = this.http.get<User[]>(this.userUrl);
    
      // Combine them.
      // Each of these will only emit once
      // The combineLatest won't emit until both streams have emitted.
      sub = combineLatest([
        this.postData$,
        this.userData$
      ]).subscribe(
        ([posts, users]) => this.processTodos(posts, users)
      )
    
      constructor(private http: HttpClient) {}
    
      processTodos(posts: Post[], users: User[]) {
        // whatever code here
        console.log("processing");
      }
    

    I have an example here: https://stackblitz.com/edit/angular-posts-user-combinelatest-deborahk

    Login or Signup to reply.
  2. You could use one Subject and use switchMap on its emits like below

    fetchTodosAndUsers$ = new Subject<void>();
    
    ngOnInit(): void {
      this.fetchTodosAndUsers$.pipe(
        startWith(() => {}),
        switchMap(() => forkJoin([
          this.todoService.getAllTodos(),
          this.userService.getAllUsers()
        ])),
        map(([todos, users]) => {
          this.todos = todos;
          this.users = users;
          this.processTodos();
        })
       ).subscribe(); 
     }
    
    loadData() {
      this.fetchTodosAndUsers$.next();
    }
    
    //Only process todos when all todos and users have been loaded
    processTodos() {
      console.log('Processing todos...');
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search