diff --git a/Directory.Build.props b/Directory.Build.props index 6f1f0ae..1d325f5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - 0.2.9 + 0.3.0 enable enable diff --git a/TrailBlazr/Components/ViewContainer.razor b/TrailBlazr/Components/ViewContainer.razor index 62a3cab..5051c2d 100644 --- a/TrailBlazr/Components/ViewContainer.razor +++ b/TrailBlazr/Components/ViewContainer.razor @@ -1,8 +1,12 @@ - + + + @using Microsoft.Extensions.Logging @using TrailBlazr.Models @using TrailBlazr.Services +@using TrailBlazr.Views +@implements IDisposable @code { [Inject] public required IViewManager ViewManager { get; init; } @@ -41,4 +45,12 @@ this.Logger.LogError(ex, "Error changing view to {newView}", request.ViewType.Name); } } + + public void Dispose() + { + if (this.ViewManager is not null) + { + this.ViewManager.ShowViewRequested -= this.UpdateCurrentView; + } + } } diff --git a/TrailBlazr/ViewModels/ViewModelBase.cs b/TrailBlazr/ViewModels/ViewModelBase.cs index 9ceb977..51fef0d 100644 --- a/TrailBlazr/ViewModels/ViewModelBase.cs +++ b/TrailBlazr/ViewModels/ViewModelBase.cs @@ -3,10 +3,12 @@ using TrailBlazr.Views; namespace TrailBlazr.ViewModels; -public abstract class ViewModelBase : INotifyPropertyChanged +public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable where TViewModel : ViewModelBase where TView : ViewBase { + private bool disposed = false; + public event PropertyChangedEventHandler? PropertyChanged; protected TView? View @@ -42,4 +44,24 @@ public abstract class ViewModelBase : INotifyPropertyChanged public virtual ValueTask Initialize(CancellationToken cancellationToken) => ValueTask.CompletedTask; public virtual ValueTask ParametersSet(TView view, CancellationToken cancellationToken) => ValueTask.CompletedTask; + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.View = null; + this.PropertyChanged = null; + } + + this.disposed = true; + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } } diff --git a/TrailBlazr/Views/ViewBase.cs b/TrailBlazr/Views/ViewBase.cs index a39b43c..4477f65 100644 --- a/TrailBlazr/Views/ViewBase.cs +++ b/TrailBlazr/Views/ViewBase.cs @@ -1,18 +1,22 @@ using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Logging; +using TrailBlazr.Components; using TrailBlazr.ViewModels; namespace TrailBlazr.Views; -public abstract class ViewBase : ComponentBase +public abstract class ViewBase : ComponentBase, IDisposable where TView : ViewBase where TViewModel : ViewModelBase { public readonly static TimeSpan InitializationTimeout = TimeSpan.FromSeconds(5); + private bool disposed = false; [Inject] - public TViewModel ViewModel { get; set; } = default!; + public required TViewModel ViewModel { get; set; } [Inject] - protected ILogger Logger { get; set; } = default!; + public required ILogger Logger { get; set; } + [CascadingParameter] + public required ViewContainer ViewContainer { get; set; } protected override async Task OnAfterRenderAsync(bool firstRender) { @@ -27,6 +31,11 @@ public abstract class ViewBase : ComponentBase throw new InvalidOperationException($"View is not of type {typeof(TView).Name}. Actual type: {this.GetType().Name}"); } + if (this.ViewContainer is null) + { + throw new InvalidOperationException("ViewContainer is not set. Ensure that this view is placed within a ViewContainer component"); + } + this.ViewModel.SetView(view); using var initializationCts = new CancellationTokenSource(InitializationTimeout); _ = this.ViewModel ?? throw new InvalidOperationException("Cannot initialize ViewModel. ViewModel is null"); @@ -61,5 +70,26 @@ public abstract class ViewBase : ComponentBase { await this.InvokeAsync(this.StateHasChanged); } + + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.ViewModel = null!; + this.Logger = null!; + this.ViewContainer = null!; + } + + this.disposed = true; + } + } + + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } }