11

Is it possible to create a ReactiveUI derived collection that has more elements in it than the original?

I've seen that there is a way of filtering a collection, and selecting single properties, but what I'm looking for is the equivalent of the SelectMany operation for enumerables.

To illustrate, imagine trying to get a derived collection representing every passenger stuck in a traffic jam.

class Car 
{
    ReactiveCollection<Passenger> Passengers;
}

var TrafficJam=new ReactiveCollection<Car>();
EveryPassengerInTheTrafficJam=Cars.CreateDerivedCollection(c=>c.Passengers);

The above doesn't work, I think the error was IEnumerable<ReactiveCollection<Passenger>> can't be cast to ReactiveCollection<Passenger> - or something up with the types, in any case.

I can't figure out the right approach for this flattening - admittedly I may be barking up completely the wrong tree here, so please let me know if there's a better way of achieving the same thing!

Daniel Neal
  • 4,165
  • 2
  • 20
  • 34

2 Answers2

13

At the moment, CreateDerivedCollection doesn't support SelectMany as a transformation, it gets too Tricky to handle removes. If you don't have many items, you can just regenerate the collection every time:

cars.Changed
    .Select(_ => cars.SelectMany(x => x.Passengers).ToList())
    .ToProperty(this, x => x.Passengers);

Edit: Alright, here we go:

var whenCarsOrPassengersInThoseCarsChange = Observable.Merge(
    cars.Changed
        .SelectMany(_ =>
            cars.Select(x => x.Passengers.Changed).Merge())
        .Select(_ => Unit.Default),
    cars.Changed.Select(_ => Unit.Default));

whenCarsOrPassengersInThoseCarsChange.StartWith(Unit.Default)
    .Select(_ => cars.SelectMany(x => x.Passengers).ToList())
    .ToProperty(this, x => x.Passengers);

So, the idea is that we've got two main situations when we want to reevaluate the passengers list:

  1. When one of the passengers change in the cars
  2. When one of the cars change

However, the tricky part is, we only want to watch passengers for cars in the collection (i.e. if a car is removed, we no longer care about its passengers).

Properly tracking suicidal passengers

So, the idea in that weird SelectMany is, "Every time the car list changes, build a new list of Observables that represent when the passenger collection changes, and merge them all together".

However, if we only had that statement, we would have to wait for a car to be added and its passengers change before we got a new passenger list, so we also have to update when the list of cars change too.

What's this "Unit" business?

In this case, I actually don't care about the values that these Observables put out, just when they happen. "Unit" is the Rx version of void, it only has a single value, "Unit.Default". You use it when you only care when something happens, not what the value of it is.

Ana Betts
  • 73,868
  • 16
  • 141
  • 209
  • One side effect of this question is now I gotta go learn how your framework works. :) – JerKimball Mar 07 '13 at 01:11
  • So, the reason that the straightforward version doesn't work, is that the `cars.SelectMany` returns an `IEnumerable` (a `SelectManyEnumerable` or something), not a type like `ObservableCollection`. There is no way for `CreateDerivedCollection` to figure out how to watch this collection, so it will populate initially but not update as `cars` has items added / removed – Ana Betts Mar 07 '13 at 02:07
  • @PaulBetts Ideally, I'd like to recalculate when a passenger collection in a car changes (someone gets out in desperation, Everybody Hurts style), or when the car collection changes (a new car arrives in the traffic jam), but **not** on other properties of the car (if WindscreenWipersEnabled becomes true, I don't want to recompute EveryPassengerInTheTrafficJam!). Is this possible? – Daniel Neal Mar 07 '13 at 09:02
  • @JerKimball definitely! I'm a complete ReactiveUI (and reactive programming) newbie, but from what I've seen so far, it is **very** cool. I love how it makes it possible to describe the whole behaviour of a user interface in a declarative way. – Daniel Neal Mar 07 '13 at 09:06
  • If I'm not mistaken Paul's code requires to wait if the passengers of a car change before the cars do. I updated it to add `StartWith(Unit.Default)` before selecting the `Passengers.Changed` observables and it works. – Damien Chaib Feb 01 '15 at 14:49
  • @PaulBetts so I've used this technique in an app of mine to good effect. But now another question ... how do I track changes on the Passengers? :-) Simple example, say the list of Passengers is bound to a list on the UI. And the Passenger has an IsSelected bool property that is bound to the Selected property of the item in the list. Can I get a derived collection of the selected passengers? Basically, I want to know when the number of selected passengers changes so that a command is only available when there is at least one selected. This one has me stumped so far. – Kevin Kuebler Sep 18 '15 at 20:03
2

Hmm...I'm not all that familiar with ReactiveUI, but just reading thru, it looks like you need to alter where your SelectMany goes:

var jam = cars.SelectMany(x => x.Passengers).CreateDerivedCollection(p => p);
JerKimball
  • 16,584
  • 3
  • 43
  • 55