Skip to content

Commit

Permalink
First pass at observing nested property changes
Browse files Browse the repository at this point in the history
Added a new method to observe changes in nested properties of INotifyPropertyChanged objects. This allows for more granular observation of property changes, particularly useful when dealing with complex objects. Also updated the test suite to cover this new functionality.
  • Loading branch information
michaelstonis committed Mar 4, 2024
1 parent 8af5962 commit 62ab623
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 2 deletions.
35 changes: 33 additions & 2 deletions src/R3/Factories/ObserveProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static Observable<TProperty> ObservePropertyChanged<T, TProperty>(this T
Func<T, TProperty> propertySelector,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
[CallerArgumentExpression("propertySelector")] string? expr = null)
[CallerArgumentExpression(nameof(propertySelector))] string? expr = null)
where T : INotifyPropertyChanged
{
if (expr == null) throw new ArgumentNullException(expr);
Expand All @@ -22,6 +22,37 @@ public static Observable<TProperty> ObservePropertyChanged<T, TProperty>(this T
return new ObservePropertyChanged<T, TProperty>(value, propertySelector, propertyName, pushCurrentValueOnSubscribe, cancellationToken);
}

/// <summary>
/// Convert INotifyPropertyChanged to Observable.
/// `propertySelector` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
/// </summary>
public static Observable<TProperty2> ObservePropertyChanged<T, TProperty1, TProperty2>(this T value,
Func<T, TProperty1> propertySelector1,
Func<TProperty1, TProperty2> propertySelector2,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
[CallerArgumentExpression(nameof(propertySelector1))] string? propertySelector1Expr = null,
[CallerArgumentExpression(nameof(propertySelector2))] string? propertySelector2Expr = null)
where T : INotifyPropertyChanged
where TProperty1 : INotifyPropertyChanged
{
if (propertySelector1Expr == null) throw new ArgumentNullException(propertySelector1Expr);
if (propertySelector2Expr == null) throw new ArgumentNullException(propertySelector2Expr);

var property1Name = propertySelector1Expr!.Substring(propertySelector1Expr.LastIndexOf('.') + 1);
var property2Name = propertySelector2Expr!.Substring(propertySelector2Expr.LastIndexOf('.') + 1);

var firstPropertyChanged = new ObservePropertyChanged<T, TProperty1>(value, propertySelector1, property1Name, pushCurrentValueOnSubscribe, cancellationToken);

return firstPropertyChanged
.Select(
(propertySelector2, property2Name, pushCurrentValueOnSubscribe, cancellationToken),
(firstPropertyValue, state) =>
(Observable<TProperty2>)new ObservePropertyChanged<TProperty1, TProperty2>(firstPropertyValue, state.propertySelector2, state.property2Name, state.pushCurrentValueOnSubscribe, state.cancellationToken))
.Switch();
}


/// <summary>
/// Convert INotifyPropertyChanging to Observable.
/// `propertySelector` must be a Func specifying a simple property. For example, it extracts "Foo" from `x => x.Foo`.
Expand All @@ -30,7 +61,7 @@ public static Observable<TProperty> ObservePropertyChanging<T, TProperty>(this T
Func<T, TProperty> propertySelector,
bool pushCurrentValueOnSubscribe = true,
CancellationToken cancellationToken = default,
[CallerArgumentExpression("propertySelector")] string? expr = null)
[CallerArgumentExpression(nameof(propertySelector))] string? expr = null)
where T : INotifyPropertyChanging
{
if (expr == null) throw new ArgumentNullException(expr);
Expand Down
27 changes: 27 additions & 0 deletions tests/R3.Tests/FactoryTests/ObservePropertyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ public void PropertyChanged()
liveList.AssertEqual([0, 1]);
}

[Fact]
public void NestedPropertyChanged()
{
ChangesProperty propertyChanger = new();

using var liveList = propertyChanger
.ObservePropertyChanged(x => x.InnerPropertyChanged, x => x.Value)
.ToLiveList();

liveList.AssertEqual([]);

propertyChanger.InnerPropertyChanged = new();

liveList.AssertEqual([0]);

propertyChanger.InnerPropertyChanged.Value = 1;

liveList.AssertEqual([0, 1]);
}

[Fact]
public void PropertyChanging()
{
Expand All @@ -40,6 +60,7 @@ public void PropertyChanging()
class ChangesProperty : INotifyPropertyChanged, INotifyPropertyChanging
{
private int _value;
private ChangesProperty _innerPropertyChanged;

public event PropertyChangedEventHandler? PropertyChanged;
public event PropertyChangingEventHandler? PropertyChanging;
Expand All @@ -50,6 +71,12 @@ public int Value
set => SetField(ref _value, value);
}

public ChangesProperty InnerPropertyChanged
{
get => _innerPropertyChanged;
set => SetField(ref _innerPropertyChanged, value);
}

private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
Expand Down

0 comments on commit 62ab623

Please sign in to comment.