'How to manage lots of BehaviourSubjects in angular component

Currently I'm working on some module in which we try to use approach with async pipes instead of markForCheck and so on. Most of data is coming over HTTP, and approach in whole project is to use ChangeDetectionStrategy.OnPush.

Usualy all ours BehaviourSubjects are in our 'smart' component and values for presentational components are set by inputs. This was fine for smaller modules. We usualy have 2-6 of such presentation components. For bigger ones, that have more api calls, we get cluttered with more and more code from behaviour subjects. Like:

  data1$ = new BehaviorSubject<Data1[]>([]);
  data2$ = new BehaviorSubject<Data2[]>([]);
  data3$ = new BehaviorSubject<Data3[]>([]);
  loading1$ = new BehaviorSubject<boolean>(true);
  loading2$ = new BehaviorSubject<boolean>(true);
  loading3$ = new BehaviorSubject<boolean>(true);
// and so on

Then we populate it with data in some method like

this.service.getData1()
.pipe(finalize() => this.loading1$.next(false))
.subscribe(result => this.data1$.next(result);

In bigger modules we have many of these method and many of declaration of BehaviorSubjects. So the question is, is there a way to clear up our component? Because right now maybe we get rid of manual calls for change detection but we get some cluttered code from behaviour subject.

We consider adding a new service for behaviour subject. It seems like often behaviour subjects goes in other service. So we would end up with service like

export class BsService {
  private data1$ = new BehaviorSubject<Data1[]>([]);
  private loading1$ = new BehaviorSubject<boolean>(true);
//... other declarations

getData1Observable$(): Observable<Data1[]> { // this is for async pipe
return this.data1$.asObservable();
}
setData1(data:Data[]){this.data1$.next(data)}
getData1(): Data1[] {return this.data1$.value} //we sometimes need direct access to value
getLoading$(){...}
setLoading(loadin:boolean){...}
pending$() {return combineLatest([...])};
//...and so on

So this seems to clear only declaration of BehaviorSubjects, and maybe some methods we use with combineLatest. Rest of the methods of setting data will stay the same in our 'smart' component. So we should add BsService to constructor of our component

constructor(...,service: DataService, behaviorSubjectService: BsService)

(DataService is a layer between component and API service)

Is this correct way of doing this? Or maybe there is better way?



Solution 1:[1]

Is each set of data (data1, data2, etc) coming from an HTTP call? Can you get rid of the BehaviorSubject and just directly work with the data returned from the HTTP call?

For example:

  products$ = this.http.get<Product[]>(this.productsUrl)
    .pipe(
      tap(data => console.log('Products: ', JSON.stringify(data))),
      catchError(this.handleError)
    );

Above is a products$ Observable that provides the list of products. You don't necessarily need to emit this into a BehaviorSubject to work with it.

And if you need to combine data from multiple HTTP get's for display in a template, you can combine these Observables with combineLatest, again without a BehaviorSubject.

  productsWithCategory$ = combineLatest([
    this.products$,
    this.productCategoryService.productCategories$
  ]).pipe(
    map(([products, categories]) =>
      products.map(product => ({
        ...product,
        category: categories.find(c => product.categoryId === c.id)?.name,
        searchKey: [product.productName]
      } as Product))
    ),
    shareReplay(1)
  );

The above code combines a set of products and product categories to display the products with their product category name (instead of the id stored in the product data).

Would something like that work for you?

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 DeborahK