diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 9e011ef08d..19a770feef 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -9127,6 +9127,9 @@ + + + Gets or sets the template to be used to define each sortable item in the list. diff --git a/src/Core/Components/SortableList/FluentSortableList.razor.cs b/src/Core/Components/SortableList/FluentSortableList.razor.cs index 031cd64f18..e3dfaf363d 100644 --- a/src/Core/Components/SortableList/FluentSortableList.razor.cs +++ b/src/Core/Components/SortableList/FluentSortableList.razor.cs @@ -10,10 +10,11 @@ namespace Microsoft.FluentUI.AspNetCore.Components; -public partial class FluentSortableList : FluentComponentBase +public partial class FluentSortableList : FluentComponentBase, IAsyncDisposable { private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/SortableList/FluentSortableList.razor.js"; private DotNetObjectReference>? _selfReference; + private bool _disposed; /// [Inject] @@ -23,6 +24,9 @@ public partial class FluentSortableList : FluentComponentBase [Inject] private IJSRuntime JSRuntime { get; set; } = default!; + /// + private IJSObjectReference? Module { get; set; } + /// /// Gets or sets the template to be used to define each sortable item in the list. /// Use the @context parameter to access the item and its properties. @@ -190,21 +194,16 @@ public partial class FluentSortableList : FluentComponentBase protected override async Task OnAfterRenderAsync(bool firstRender) { - try + + if (firstRender) { - if (firstRender) + _selfReference = DotNetObjectReference.Create(this); + Module = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + if (!_disposed) { - _selfReference = DotNetObjectReference.Create(this); - IJSObjectReference? module = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); - await module.InvokeAsync("init", Element, Group, Clone ? "clone" : null, Drop, Sort, Handle ? ".sortable-grab" : null, Filter, Fallback, _selfReference); + await Module.InvokeAsync("init", Element, Group, Clone ? "clone" : null, Drop, Sort, Handle ? ".sortable-grab" : null, Filter, Fallback, _selfReference); } } - catch (Exception ex) when (ex is JSDisconnectedException || - ex is OperationCanceledException) - { - // This exception is expected when the user navigates away from the page - // and the component is disposed. We can ignore it. - } } protected bool GetItemFiltered(TItem item) @@ -239,5 +238,27 @@ public void OnRemoveJS(int oldIndex, int newIndex, string fromListId, string toL } } - public void Dispose() => _selfReference?.Dispose(); + public async ValueTask DisposeAsync() + { + if (_disposed) + { + return; + } + + try + { + _selfReference?.Dispose(); + _disposed = true; + if (Module is not null) + { + await Module.DisposeAsync(); + } + } + catch (Exception ex) when (ex is JSDisconnectedException || + ex is OperationCanceledException) + { + // The JSRuntime side may routinely be gone already if the reason we're disposing is that + // the client disconnected. This is not an error. + } + } }