Question

In reactiveui and reactive extensions you can combine multiple observables into one. Using functions like CombineLatest or Zip you can make the resulting observable use a function to compute result based on values from combined observables. Though CombineLatest gives you an observable which gives you notifications each time any of the combined observables fires, it starts working only when all of those observables fires at least once. I want it to make notifications from the start - give combined observable an initial value, or something. Sure, I can somehow make those observable fire to set the initial values. But it is not that easy sometimes, or it makes the code look ugly. Here's an example: I'm making a wpf application using reactiveui. In one of my view models I'm trying to create properties by combining observables. So, I have a collection (ReactiveList) of objects with NumberToSum property, and I need a property that will be used to display the sum of that property values. Objects in that collection are also view models and their NumberToSum property can be changed by user. Number of objects taken for sum is determined by other property. That's how I tried to do it:

private readonly ReactiveList<Element> _collection = new ReactiveList<Element>();
private readonly ObservableAsPropertyHelper<int> _sum;
private int _elementsToTake = 0;

public int ElementsToTake
{
    get { return _elementsToTake; }
    set { this.RaiseAndSetIfChanged(ref _elementsToTake, value) }
}

public int Sum
{
    get { return _sum.Value; }
}

public MyViewModel
{
    var elementsToTakeObservable = this
        .WhenAny(x => x.ElementsToTake, x => x.Value);
    _collection.ChangeTrackingEnabled = true;
    //here I'm adding some objects to _collection
    var sumObservable = _collection.ItemChanged
        .CombineLatest(elementsToTakeObservable,
            (_, c) => _collection.Take(c)
                .Aggregate(0, (i, x) => i + x.NumberToSum))).
        .ToProperty(this, x => x.Sum, out _sum, 0);
}

So, I use ReactiveList.ItemChanged to track changes in _collection items. Each time change happen the Sum is recalculated. The problem here is initial values of observables combined into sumObservable. I can easily set initial value of elementsToTakeObservable by assigning value to ElementsToTake property, which will raise property changed event and stuff. But it's not so easy to do for _collection.ItemChanged observable. I need to make at least one of my objects in _collection raise propertychanged event, but that just look awful. Is setting these initial values possible? Or maybe I'm doing this entirely wrong?

Was it helpful?

Solution

StartWith will fix your problem. Also, I'm not sure that you want ItemChanged, that signals when an item in the list changes, not when an item is Added or Removed. Here, you probably also want to listen to Changed.

While it's tempting to try to keep a "running total" in this way, it's actually Pretty Hard to do this correctly because of Resets and Moves. An easier approach would be to abuse the fact that CPUs are really fast and just recompute it on every change:

_collection.ChangeTrackingEnabled = true;

Observable.Merge(
         _collection.Changed.Select(_ => Unit.Default), 
         _collection.ItemChanged.Select(_ => Unit.Default))
    .Select(_ => _collection.Sum(x => x.NumberToSum));

Another approach that isn't very good for aggregates like this but can also be super useful, is ActOnEveryObject, which will execute a block on every item in a list, and correctly handles initialization, resets, and moves.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top