Bernd Fuhrmann
Bernd Fuhrmann

Reputation: 83

How to dynamically merge a reactivelist of observables into one observable in ReactiveUI, C#

I'm trying to use ReactiveUI to formulate some observables. While some things already work very well, I still wasn't able to find appropriate ReactiveUI methods for other things. I'm still new to ReactiveUI.

public class ViewModel : ReactiveObject {
    public ReactiveList<A> aList {get;}
}

public class A : ReactiveObject {
    public ReactiveList<B> bList {get;}
}

public class B : ReactiveObject {
    //some properties
}

I have a ReactiveList aList. It's items are of ReactiveObject class A, which contains another ReactiveList bList. bList items as property of ReactiveObject class B. Starting in aList, how can I react to ANY change inside aList, bList and all it's properties?

I was trying something like this:

Observable.Merge(viewModel.aList.Select(x => Observable.Merge(x.bList.Select(y => y.Changed))))

However, this will only observe changes in B that are already there, when this code is executed. Is there something to automatically observe changes in aList and bList and subscribe to the new items aswell?

Or maybe can Observable.Merge watch a ReactiveList and automatically subscribe to any new items and unsubscribe from deleted items?

I know, I can do it manually, but that would probably not be using ReactiveUI the way it is intended. Any ideas?

Upvotes: 2

Views: 1327

Answers (2)

bradgonesurfing
bradgonesurfing

Reputation: 32162

Instead of using ReactiveList you can try our library ReactiveCompositeCollections which allows you to flatten observable collections using LINQ.

public class ViewModel : ReactiveObject {
    public ICompositeSourceList<A> aList {get;}
}

public class A : ReactiveObject {
    public ICompositeSourceList<B> bList {get;}
}

public class B : ReactiveObject {
    //some properties
}

then

ViewModel vm = ... ;

IComposteList<B> all = 
    from aItem in vm.aList
    from bItem in aItem.bList
    select bItem;

// Subscribe to the stream of flattened items
all.Items.Subscribe((ImmutableList<B> bItems)=>DoSometing(bItems));

// or create an INPC object with a property Items
using(var s = allLines.Subscribe()){
   RenderLines(s.Items);
}

The nice thing is that this is not LINQ over IEnumerable it is LINQ over ICompositeCollection where ICompositeCollection is a reactive collection. Any changes in the source lists are projected reactively into the result lists.

The internal structures all use immutable lists so performance may be an issue for large collections.

The ICompositeCollection can be converted to an ObservableCollection usable by WPF via

ObservableCollection<B> observableBs =
   all.CreateObservableCollection(EqualityComparer<B>.Default)

More details at https://github.com/Weingartner/ReactiveCompositeCollections#using-icompositecollection-with-wpf

Upvotes: 0

Jon G St&#248;dle
Jon G St&#248;dle

Reputation: 3904

The Changed property on ReactiveList will let you know when the list has changed. Use this to know when to update the subscription to changes in the contained ReactiveLists.

To get an observable that ticks every time ReactiveList<A> and ReactiveList<B> changes, you can do this:

aList.Changed
    .Select(change => Observable.Merge(viewModel.aList.Select(x => Observable.Merge(x.bList.Select(y => y.Changed)))) // Create observable to notify when the sub collections change
        .StartWith(change) // Send the notification that aList has changed immediately
        )
    .Switch() // Only subscribe to the latest change notifications from the sub collections in the most recent aList
    .Subscribe(changes => { /* Do something */});

Upvotes: 3

Related Questions