Search code examples
angularrxjsobservablereactivex

Observable Array of Observables - How to implement in RxJS?


I'm just learning RxJS for the first time, so I apologize if this question is based on false assumptions.

Say, for example, that I have a FooService in my Angular app that returns an Array<Foo>. Elements may be added and removed from the list arbitrarily. Therefore, I wrap my Array of Foos in an RxJS observable; the FooService now returns a BehavioralSubject<Array<Foo>>. Now I can use Angular's AsyncPipe to automatically rerender the necessary components every time the BehavioralSubject is updated. So far, this is trivial RxJS 101.

But in my app, not only can the Array of Foos receive updates, but each individual Foo can receive updates without affecting the Array. How do I architect this? Do I wrap each individual Foo in an Observable, making my FooService return a BehavioralSubject<Array<BehavioralSubject<Foo>>>? This seems messy. Do I continue to return a BehavioralSubject<Array<Foo>>, and rerender the entire Array component every time a Foo updates? This tightly binds Foo updates to Array updates, preventing me from reusing my Foo component in other parts of the app.

What's the best way to do this in RxJS? How do I (actually) implement a (conceptual) Observable Array of Observables?


Solution

  • But in my app, not only can the Array of Foos receive updates, but each individual Foo can receive updates without affecting the Array.

    This doesn't make sense to me. If you have an array that holds foos, and one of the foos gets changed, the array inherently changes.

    If you are using the ngFor directive, change propagation in your template will be optimised so you won't have to worry about heavy DOM lifting in most use-cases: https://angular.io/api/common/NgForOf#change-propagation

    In case you need more control, check this out: https://angular.io/guide/template-syntax#ngfor-with-trackby

    Now, in your service, you can do this:

    private foos = new BehaviorSubject<Foo[]>([]);
    

    Then create a function that returns an Observable to be used in your template:

    get() { 
        return this.foos.asObservable(); 
    }
    

    Then you add logic to change foos the way you like, for example, add a foo:

    add(foo: Foo) {
        let arr = this.foos.getValue();
        arr.push(foo);
        this.foos.next(arr);
    }
    

    Change a foo:

    change(index: number, foo: Foo) {
        let arr = this.foos.getValue();
        arr[index] = foo;
        this.foos.next(arr);
    }
    

    Then, in your component, use fooService.get() | async