Skip to content

Commit b314c6a

Browse files
committed
Adding ReactiveOwningComponentBase
1 parent cae2b65 commit b314c6a

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright (c) 2025 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
using System.Runtime.CompilerServices;
6+
7+
using Microsoft.AspNetCore.Components;
8+
9+
namespace ReactiveUI.Blazor;
10+
11+
/// <summary>
12+
/// A base component for handling property changes and updating the blazer view appropriately.
13+
/// </summary>
14+
/// <typeparam name="T">The type of view model. Must support INotifyPropertyChanged.</typeparam>
15+
public class ReactiveOwningComponentBase<T> : OwningComponentBase<T>, IViewFor<T>, INotifyPropertyChanged, ICanActivate
16+
where T : class, INotifyPropertyChanged
17+
{
18+
private readonly Subject<Unit> _initSubject = new();
19+
[SuppressMessage("Design", "CA2213: Dispose object", Justification = "Used for deactivation.")]
20+
private readonly Subject<Unit> _deactivateSubject = new();
21+
private readonly CompositeDisposable _compositeDisposable = [];
22+
23+
private bool _disposedValue;
24+
private T? _viewModel;
25+
26+
/// <inheritdoc />
27+
public event PropertyChangedEventHandler? PropertyChanged;
28+
29+
/// <summary>
30+
/// Gets or sets the view model associated with this component.
31+
/// </summary>
32+
[Parameter]
33+
public T? ViewModel
34+
{
35+
get => _viewModel;
36+
set
37+
{
38+
if (EqualityComparer<T?>.Default.Equals(_viewModel, value))
39+
{
40+
return;
41+
}
42+
43+
_viewModel = value;
44+
OnPropertyChanged();
45+
}
46+
}
47+
48+
/// <inheritdoc />
49+
object? IViewFor.ViewModel
50+
{
51+
get => ViewModel;
52+
set => ViewModel = (T?)value;
53+
}
54+
55+
/// <inheritdoc />
56+
public IObservable<Unit> Activated => _initSubject.AsObservable();
57+
58+
/// <inheritdoc />
59+
public IObservable<Unit> Deactivated => _deactivateSubject.AsObservable();
60+
61+
/// <inheritdoc />
62+
protected override void OnInitialized()
63+
{
64+
if (ViewModel is IActivatableViewModel avm)
65+
{
66+
Activated.Subscribe(_ => avm.Activator.Activate()).DisposeWith(_compositeDisposable);
67+
Deactivated.Subscribe(_ => avm.Activator.Deactivate());
68+
}
69+
70+
_initSubject.OnNext(Unit.Default);
71+
base.OnInitialized();
72+
}
73+
74+
/// <inheritdoc/>
75+
#if NET6_0_OR_GREATER
76+
[RequiresDynamicCode("OnAfterRender uses methods that require dynamic code generation")]
77+
[RequiresUnreferencedCode("OnAfterRender uses methods that may require unreferenced code")]
78+
[SuppressMessage("AOT", "IL3051:'RequiresDynamicCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")]
79+
[SuppressMessage("Trimming", "IL2046:'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.", Justification = "ComponentBase is an external reference")]
80+
#endif
81+
protected override void OnAfterRender(bool firstRender)
82+
{
83+
if (firstRender)
84+
{
85+
// The following subscriptions are here because if they are done in OnInitialized, they conflict with certain JavaScript frameworks.
86+
var viewModelChanged =
87+
this.WhenAnyValue<ReactiveOwningComponentBase<T>, T?>(nameof(ViewModel))
88+
.WhereNotNull()
89+
.Publish()
90+
.RefCount(2);
91+
92+
viewModelChanged
93+
.Subscribe(_ => InvokeAsync(StateHasChanged))
94+
.DisposeWith(_compositeDisposable);
95+
96+
viewModelChanged
97+
.Select(x =>
98+
Observable
99+
.FromEvent<PropertyChangedEventHandler?, Unit>(
100+
eventHandler =>
101+
{
102+
void Handler(object? sender, PropertyChangedEventArgs e) => eventHandler(Unit.Default);
103+
return Handler;
104+
},
105+
eh => x.PropertyChanged += eh,
106+
eh => x.PropertyChanged -= eh))
107+
.Switch()
108+
.Subscribe(_ => InvokeAsync(StateHasChanged))
109+
.DisposeWith(_compositeDisposable);
110+
}
111+
112+
base.OnAfterRender(firstRender);
113+
}
114+
115+
/// <summary>
116+
/// Raises the <see cref="PropertyChanged"/> event.
117+
/// </summary>
118+
/// <param name="propertyName">The name of the changed property.</param>
119+
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
120+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
121+
122+
/// <inheritdoc />
123+
protected override void Dispose(bool disposing)
124+
{
125+
if (!_disposedValue)
126+
{
127+
if (disposing)
128+
{
129+
_initSubject.Dispose();
130+
_compositeDisposable.Dispose();
131+
_deactivateSubject.OnNext(Unit.Default);
132+
}
133+
134+
_disposedValue = true;
135+
}
136+
}
137+
}

0 commit comments

Comments
 (0)