I have a view model which has a property A. Property A is of a type which has a property B. Now I want to subscribe in my view model's constructor to any direct change of property B. By "direct" I mean that I want to run my subscription only if property B of the current value of property A is changing but not if the value of property A is changed.
By now I have something like this:
this.WhenAnyValue(x => x.A.B)
.Subscribe(b => DoSomethingWithB(b));
However, this will of course also execute DoSomethingWithB
if the value of property A changes. I have already tried around whether WhenAnyObservable
or Switch
extension methods can be used but up to now I couldn't figure out what it has to look like.
Edit:
Since I don't know whether my initial question was clear enough I have added now a working example covering all cases that I need to take into account. For simplicity, property B is of type int
and I have added an ID property to TypeA
to be able to distinguish them.
using ReactiveUI;
using System;
using System.ComponentModel;
namespace ObservePropertyTail
{
class Program
{
static void Main(string[] args)
{
ViewModel vm = new ViewModel();
// Pings but should not because A was changed.
vm.A = new TypeA(1) { B = 1 };
// Pings which is the desired behavior.
vm.A.B = 2;
// Does not ping (by chance because value of B remains the
// same although A is changed) which is the desired behavior.
vm.A = new TypeA(2) { B = 2 };
// Pings but should not because A was changed.
vm.A = new TypeA(3) { B = 3 };
// Should not ping and does not.
vm.A = null;
// Should not ping but does.
vm.A = new TypeA(4) { B = 4 };
// Should ping and does.
vm.A.B = 3;
}
}
class ViewModel : INotifyPropertyChanged
{
private TypeA a;
public ViewModel()
{
this.WhenAnyValue(x => x.A.B)
.Subscribe(b => Console.WriteLine($"Ping: A = {A.ID}, b = {b}"));
}
public TypeA A
{
get => a;
set
{
if (a != value)
{
a = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(A)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
class TypeA : INotifyPropertyChanged
{
private int b;
public TypeA(int id) => ID = id;
public int ID { get; }
public int B
{
get => b;
set
{
if (b != value)
{
b = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(B)));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Output:
Ping: A = 1, b = 1
Ping: A = 1, b = 2
Ping: A = 3, b = 3
Ping: A = 4, b = 4
Ping: A = 4, b = 3
You can use ObservableForProperty()
to build an IObservable<T>
for the properties you have, which will not fire the initial value they have. This in combination with Switch()
allows you to build an IObservable<T>
for the property B
, which will only fire changes to the property B
, but not when the property A
is changed. The code might look like this:
ViewModel vm = new ViewModel();
vm.ObservableForProperty(it => it.A)
.Select(it => it.Value)
.Select(it => it.ObservableForProperty(it2 => it2.B))
.Switch()
.Select(it => it.Value)
.Subscribe(it => {
Console.WriteLine("B is: "+it);
});
// Pings but should not because A was changed.
vm.A = new TypeA(1) { B = 1 };
// Pings which is the desired behavior.
vm.A.B = 2;
// Does not ping (by chance because value of B remains the
// same although A is changed) which is the desired behavior.
vm.A = new TypeA(2) { B = 2 };
// Pings but should not because A was changed.
vm.A = new TypeA(3) { B = 3 };
// Should not ping and does not.
vm.A = null;
// Should not ping but does.
vm.A = new TypeA(4) { B = 4 };
// Should ping and does.
vm.A.B = 3;
This will generate the following output:
B is: 2
B is: 3
As you see, it will only trigger when the property B
is changed, but will not trigger when A
is changed. Also, you have one observable on the property B
and will not notice that the inner value for A
is changed due to the Switch()
call.