Search code examples
angularrxjsobservablesubscriptionbehaviorsubject

RXJS Subscription Chaining in Services


I have two services and a component. The component listens to service A which listens to service B. I am currently subscribing to service B in service A, running a filtering function, and then the filtering function is firing the observable in service A which the component listens to. Best practice would be to not subscribe to service B from service A and instead pass that observable up to the component that will be the only listener, but I can't figure out how to handle it properly.

Current Setup:

When user changes the type, the subscription in Service B fires which Service A listens to, then takes that data and creates a new array and fires the productsChanged subject which is listened to by the Component

Service A {
  productsChanged = new BehaviorSubject<Product[]>([]);

  types$: Subscription;

  private json: Product[] = ProductsJSON;
  private currentProducts: Product[] = [];

  constructor(private typeService: TypeService) {
    this.types$ = this.typeService.typeChanged.subscribe((type: Type) => {
      this.filterProducts(type);
    });
  }


  filterProducts(type: Type): void {
    this.currentProducts = [];

    // Do Some Filtering to Set the Current Products Based on Given Type
  }
}

When the person changes the product type it fires 'TypeChanged' which is listened to by Subject A to filter out products not matching the type

Service B {
  typeChanged = new BehaviorSubject<Type>(Defaults.TYPE);
  private chosenType: Type;

  constructor() {
    this.typeChanged.pipe(take(1)).subscribe((type) => {
      this.chosenType = type;
    });
  }

  setType(type: Type): void {
    this.chosenType = type;

    this.typeChanged.next(this.chosenType);
  }
}

Component subscribes in the HTML and just puts out a list of products

Component {
  products$: Observable<any>;

  constructor(private productService: ProductService) {
    this.products$ = this.productService.productsChanged.pipe(
      map((product) => {
        return product;
      }),
    );
  }
}

Solution

  • You could do the following changes in your service A to achieve this.

    Your productsChanged should no longer be a BehaviorSubject, but instead it should be an unassigned property of type Observable, like this:

    productsChanged: Observable<Product[]>;
    

    Then in the constructor you do the following:

    this.productsChanged = this.typeService.typeChanged.pipe(map((type: Type) => {
      return this.filterProducts(type);
    }));
    

    Lastly your filterProducts method need to return the filtered products, so something like this:

      filterProducts(type: Type): void {
        this.currentProducts = [];
    
        // Do Some Filtering to Set the Current Products Based on Given Type
        
        return filteredProducts;
      }