diff --git a/BlazorSortableList.Lib/SortableList.razor b/BlazorSortableList.Lib/SortableList.razor
index f0b13a4..2bcfec2 100644
--- a/BlazorSortableList.Lib/SortableList.razor
+++ b/BlazorSortableList.Lib/SortableList.razor
@@ -4,6 +4,8 @@
@inject IJSRuntime JS
+@implements IAsyncDisposable
+
@typeparam T
diff --git a/BlazorSortableList.Lib/SortableList.razor.cs b/BlazorSortableList.Lib/SortableList.razor.cs
index 313991f..a466504 100644
--- a/BlazorSortableList.Lib/SortableList.razor.cs
+++ b/BlazorSortableList.Lib/SortableList.razor.cs
@@ -13,6 +13,14 @@ public partial class SortableList
private DotNetObjectReference>? selfReference;
+ private IJSObjectReference? _module;
+
+ ///
+ /// The id the JS instance was registered under. is a mutable parameter, so it
+ /// cannot be trusted to still hold the key the instance is stored against by disposal time.
+ ///
+ private string? _initializedId;
+
private string _cssForSelection;
private string? _multiDragKey = string.Empty;
@@ -68,7 +76,40 @@ public partial class SortableList
[Parameter]
public RenderFragment? SortableItemTemplate { get; set; }
- public void Dispose() => selfReference?.Dispose();
+ public async ValueTask DisposeAsync()
+ {
+ try
+ {
+ if (_module != null)
+ {
+ try
+ {
+ if (_initializedId != null)
+ {
+ await _module.InvokeVoidAsync("destroy", _initializedId);
+ }
+ }
+ finally
+ {
+ await _module.DisposeAsync();
+ }
+ }
+ }
+ catch (JSDisconnectedException)
+ {
+ // The circuit is already gone, so there is nothing left to clean up on the JS side.
+ }
+ catch (JSException)
+ {
+ // A failing teardown on the JS side must not stop the managed references from being released.
+ }
+ finally
+ {
+ _module = null;
+ selfReference?.Dispose();
+ selfReference = null;
+ }
+ }
[JSInvokable]
public void OnDeselectJS(string fromId, int index)
@@ -146,12 +187,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
if (firstRender)
{
selfReference = DotNetObjectReference.Create(this);
- var module = await JS.InvokeAsync("import", "./_content/BlazorSortableList/SortableList.razor.js");
+ _module = await JS.InvokeAsync("import", "./_content/BlazorSortableList/SortableList.razor.js");
//await JS.InvokeVoidAsync("console.log", $"***id:{Id}, group:{Group},pull: {Pull},put:{Put},sort:{Sort}, handle:{Handle}, filter:{Filter}, forceFallback:{ForceFallback}");
- await module.InvokeAsync(
+ _initializedId = Id;
+ await _module.InvokeVoidAsync(
"init",
- Id,
+ _initializedId,
Group,
Pull,
Put,
diff --git a/BlazorSortableList.Lib/SortableList.razor.js b/BlazorSortableList.Lib/SortableList.razor.js
index eb13009..f5271fb 100644
--- a/BlazorSortableList.Lib/SortableList.razor.js
+++ b/BlazorSortableList.Lib/SortableList.razor.js
@@ -1,3 +1,13 @@
+const instances = new Map();
+
+export function destroy(id) {
+ const sortable = instances.get(id);
+ if (sortable) {
+ sortable.destroy();
+ instances.delete(id);
+ }
+}
+
export function init(id, group, pull, put, sort, handle, filter, component, forceFallback, cssForSelection, multiDragKey, avoidImplicitDeselect, fallbackOnBody, swapThreshold) {
const DEBUG_MODE = true;
@@ -51,6 +61,9 @@ export function init(id, group, pull, put, sort, handle, filter, component, forc
// }
// });
+ // A component re-initialising under an id we already track would otherwise orphan the previous instance.
+ destroy(id);
+
var sortable = new SortableCtor(htmlElement, {
animation: 200,
group: {
@@ -187,4 +200,6 @@ export function init(id, group, pull, put, sort, handle, filter, component, forc
}
}
});
+
+ instances.set(id, sortable);
}