mbednarski
mbednarski

Reputation: 798

CanExecute depending on two lists using ReactiveUI

In my app I have a ViewModel with two lists. There is also an action that CanExecute property depends on both list states. I'm using ReactiveUI. So far I got following (working) implementation:

public ReactiveList<Defect> Defects { get; } = new ReactiveList<Defect>();
public ReactiveList<State> States { get; } = new ReactiveList<State>();

public ReactiveCommand<object> Print { get; }

// this stream is artificial it is only needed to get notifications from both above
private IObservable<bool> SelectingStream { get; }

ctor()
{
    Defects.ChangeTrackingEnabled = true;
    States.ChangeTrackingEnabled = true;

    SelectingStream = States.ItemChanged.CombineLatest(Defects.ItemChanged, (a, b) =>
    {
        // here is the condition that needs to be met in order to can execute action
        return States.Count(s => s.IsSelected) == 1 &&
               Defects.Any(d => d.IsActive);
    });

    Print = ReactiveCommand.Create(
        this.WhenAnyObservable(x => x.SelectingStream)
    );
}

It does work, however I think this approach is more like workaround. Is it OK ore there is more straightforward solution?

Upvotes: 2

Views: 362

Answers (1)

Gluck
Gluck

Reputation: 2962

As you probably need to listen/react to inner object changes, ItemChanged is probably the simplest it can get (albeit pretty heavy).

Some remarks though:

  • You can avoid the observable property and the whenAnyObservable call and simply write Print = ReactiveCommand.Create(selectingStream)

  • Beware that ItemChanged will not trigger when you add/remove elements to the list, so your observable condition should also be merged with the lists Changed streams to cover for that

  • you should replace CombineLatest by Merge to avoid leaking objects (you don't really need to store the latest state of each stream, a/b aren't used).

  • you could avoid triggering the check when properties other than the ones you're relying upon are changed

Ultimately the result should look like:

new [] {
    States.Changed.SelectUnit(),
    States.ItemChanged.Where(ea => ea.PropertyName == "IsSelected").SelectUnit(),
    Defects.Changed.SelectUnit(),
    Defects.ItemChanged.Where(ea => ea.PropertyName == "IsActive").SelectUnit() }
.Merge()
.Select(_ => States.Count(s => s.IsSelected) == 1 && Defects.Any(d => d.IsActive))

(using a handy SelectUnit() extension method)

Not really simpler, is it ? :)

Upvotes: 4

Related Questions