Skip to content

Commit

Permalink
FluxorLayout (Fixes #8)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrpmorris committed Mar 25, 2020
1 parent 984f8c8 commit 7a83929
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 28 deletions.
31 changes: 3 additions & 28 deletions Source/Fluxor.Blazor.Web/Components/FluxorComponent.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Fluxor.Blazor.Web.Components
{
Expand All @@ -13,7 +10,7 @@ namespace Fluxor.Blazor.Web.Components
public class FluxorComponent : ComponentBase, IDisposable
{
private bool Disposed;
private List<IState> SubscribedStates = new List<IState>();
private IDisposable StateSubscription;

/// <summary>
/// Disposes of the component and unsubscribes from any state
Expand All @@ -29,17 +26,7 @@ public void Dispose()
protected override void OnInitialized()
{
base.OnInitialized();
// Find all state properties
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
IEnumerable<PropertyInfo> stateProperties = GetType().GetProperties(bindingFlags)
.Where(t => typeof(IState).IsAssignableFrom(t.PropertyType));
// Subscribe to each state so that StateHasChanged is executed when the state changes
foreach (PropertyInfo propertyInfo in stateProperties)
{
IState state = (IState)propertyInfo.GetValue(this);
SubscribedStates.Add(state);
state.StateChanged += InvokeStateHasChanged;
}
StateSubscription = StateSubscriber.Subscribe(this, _ => StateHasChanged());
}

protected virtual void Dispose(bool disposing)
Expand All @@ -48,22 +35,10 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
UnsubscribeFromState();
StateSubscription.Dispose();
}
Disposed = true;
}
}

private void InvokeStateHasChanged(object sender, EventArgs e)
{
InvokeAsync(StateHasChanged);
}

private void UnsubscribeFromState()
{
foreach (IState state in SubscribedStates)
state.StateChanged -= InvokeStateHasChanged;
}

}
}
44 changes: 44 additions & 0 deletions Source/Fluxor.Blazor.Web/Components/FluxorLayout.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.Components;
using System;

namespace Fluxor.Blazor.Web.Components
{
/// <summary>
/// A layout that auto-subscribes to state changes on all <see cref="IState"/> properties
/// and ensures <see cref="LayoutComponentBase.StateHasChanged"/> is called
/// </summary>
public class FluxorLayout : LayoutComponentBase, IDisposable
{
private bool Disposed;
private IDisposable StateSubscription;

/// <summary>
/// Disposes of the component and unsubscribes from any state
/// </summary>
public void Dispose()
{
Dispose(true);
}

/// <summary>
/// Subscribes to state properties
/// </summary>
protected override void OnInitialized()
{
base.OnInitialized();
StateSubscription = StateSubscriber.Subscribe(this, _ => StateHasChanged());
}

protected virtual void Dispose(bool disposing)
{
if (!Disposed)
{
if (disposing)
{
StateSubscription.Dispose();
}
Disposed = true;
}
}
}
}
68 changes: 68 additions & 0 deletions Source/Fluxor.Blazor.Web/Components/StateSubscriber.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using GetStateDelegate = System.Func<object, Fluxor.IState>;

namespace Fluxor.Blazor.Web.Components
{
internal static class StateSubscriber
{
private static readonly ConcurrentDictionary<Type, IEnumerable<GetStateDelegate>> ValueDelegatesForType;

static StateSubscriber()
{
ValueDelegatesForType = new ConcurrentDictionary<Type, IEnumerable<GetStateDelegate>>();
}

public static IDisposable Subscribe(object subject, Action<IState> callback)
{
if (subject == null)
throw new ArgumentNullException(nameof(subject));
if (callback == null)
throw new ArgumentNullException(nameof(callback));

IEnumerable<GetStateDelegate> getStateDelegates = GetStateDelegatesForType(subject.GetType());
var subscriptions = new List<(IState State, EventHandler Handler)>();
foreach(GetStateDelegate getState in getStateDelegates)
{
var state = (IState)getState(subject);
var handler = new EventHandler((s, a) => callback(state));

subscriptions.Add((state, handler));
state.StateChanged += handler;
}
return new DisposableCallback(() =>
{
foreach (var subscription in subscriptions)
subscription.State.StateChanged -= subscription.Handler;
});
}

private static IEnumerable<GetStateDelegate> GetStateDelegatesForType(Type type)
{
return ValueDelegatesForType.GetOrAdd(type, _ =>
{
var delegates = new List<GetStateDelegate>();
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
IEnumerable<PropertyInfo> stateProperties = type.GetProperties(bindingFlags)
.Where(p => p.PropertyType.IsGenericType)
.Where(p => p.PropertyType.GetGenericTypeDefinition() == typeof(IState<>));
foreach(PropertyInfo currentProperty in stateProperties)
{
Type stateType = currentProperty.PropertyType.GetGenericArguments()[0];
Type iStateType = typeof(IState<>).MakeGenericType(stateType);
Type getterMethod = typeof(Func<,>).MakeGenericType(type, iStateType);
Delegate stronglyTypedDelegate = Delegate.CreateDelegate(getterMethod, currentProperty.GetGetMethod(true));
var getValueDelegate = new GetStateDelegate(x => (IState)stronglyTypedDelegate.DynamicInvoke(x));
delegates.Add(getValueDelegate);
}
return delegates;
});
}
}
}

0 comments on commit 7a83929

Please sign in to comment.