Skip to content

Instantly share code, notes, and snippets.

@RolandPheasant
Last active August 6, 2021 17:00
Show Gist options
  • Save RolandPheasant/81dcc207bf95f6c4cf83f0ecd48ed740 to your computer and use it in GitHub Desktop.
Save RolandPheasant/81dcc207bf95f6c4cf83f0ecd48ed740 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.ObjectModel;
using System.Reactive;
using System.Reactive.Linq;
using DynamicData.Binding;
namespace DynamicData.Tests
{
public class DeepNested
{
public void Main()
{
var t1 = new Type1(); // Initial call into Type1's CheckChanges happens here
var t2 = new Type2();
var t3 = new Type3 { Name = "First" };
t2.Type3s.Add(t3);
t1.Type2s.Add(t2); // Would also like see call to Type1's CheckChanges method
t3.Name = "Second"; // Would also like see call to Type1's CheckChanges method
var hasAnythingChanged = t1.HasAnythingChanged
.Subscribe(_=> Console.WriteLine("There has been a change"));
}
}
public class Type1 : AbstractNotifyPropertyChanged
{
private ObservableCollection<Type2> _type2s = new ObservableCollection<Type2>();
public Type1()
{
this.WhenValueChanged(x => x.Type2s)
.Subscribe(_ => CheckIfHasChanges());
}
/// <summary>
///
/// </summary>
public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type2s)
.SelectMany(t =>
{
//account for the dreaded null
if (t == null) return Observable.Return(Unit.Default);
//watch Type2 collection changes
var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default);
//watch for changes from Type2.HasAnythingChanged
var propertyChanged = t.ToObservableChangeSet().MergeMany(x=>x.HasAnythingChanged).Select(_ => Unit.Default);
return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default);
});
private void CheckIfHasChanges()
{
// Do something on change
}
public ObservableCollection<Type2> Type2s
{
get => _type2s;
set => this.SetAndRaise(ref _type2s, value, nameof(Type2s));
}
}
public class Type2 : AbstractNotifyPropertyChanged
{
public IObservable<Unit> HasAnythingChanged => this.WhenValueChanged(t => t.Type3s)
.SelectMany(t =>
{
//account for the dreaded null
if (t == null) return Observable.Return(Unit.Default);
//watch the collection changes
var collectionChanges = t.ToObservableChangeSet().Select(_ => Unit.Default);
//watch the property changes - use .WhenValueChanged(t=>t.Name, false) is you do not want the initial value and only want subsequent changes
var propertyChanged = t.ToObservableChangeSet().WhenValueChanged(t=>t.Name).Select(_ => Unit.Default);
//start with Unit.Default so it always fires after Type3 is set
return collectionChanges.Merge(propertyChanged).StartWith(Unit.Default);
});
private ObservableCollection<Type3> _type3s = new ObservableCollection<Type3>();
public ObservableCollection<Type3> Type3s
{
get => _type3s;
set => this.SetAndRaise(ref _type3s, value, nameof(Type3s));
}
}
public class Type3 : AbstractNotifyPropertyChanged
{
private string _name;
public string Name
{
get => _name;
set => this.SetAndRaise(ref _name, value, nameof(Name));
}
}
}
@jkears
Copy link

jkears commented Aug 6, 2021

Thanks again Roland, very much appreciated!

I recently completed an internal Domain Driven Design based modeling tool that provides the means to capture any business domain and then using Roslyn, code generate out respective backing microservices as well as code-generated client-side integration libraries that connect to the code-generate services, plus client-side domain-based ViewModels that bind to the client-side services.

The backing microservices follow the CQRS + EventSourcing pattern and default to using EventStore Cloud as the backing write store, and a default projection into Elastic Search Cloud as a primary read store.

I am now currently designing an internal front end designer tooling that will provide a Designer an ability to create Blazor and/or MAUI based apps that will bind to these code generated domain-based ViewModels.

My approach is to further code-generate two additional ViewModel layers which will sit above the domain-based ViewModels.

The top level will be a Client-Tech-Specific ViewModel that will handle client-specific concerns and then beneath that will be an App-Specific ViewModel.

I am planning to create a graphical UI-Behavioral editor tool to plug into the UI designer that will provide the ability to associate existing layout elements within the designer surface with Reactive type capabilities, and from that will generate the respective Client-Tech-Specific, and App-Specific Reactive ViewModels.

A simple example, might be a login screen where user name and password fields must both have x amount of characters prior to enabling the login button. As I am sure you know how simple that Reactive pipeline would be to support that scenario.

I am pretty confident I can get the UI Designer tooling stood but will likely struggle some to get the appropriate backing Reactive pipelines generated properly, at least initially.

My overall goal is to reduce the amount of code both on back-end (microservices) and front-end clients, and to be able to reuse as much Behavior between different Client types (Blazor/MAUI) for the same Application, so as to provide a rapid platform for developing the most sophisticated applications with great speed and agility.

Wish my luck!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment