I created my custom component in Blazor. It's Tabs. I need to remove re-render Tab re-rendering. This's necessary so that when switching tabs again, the components aren't reinitialized, i.e. "Dispose" and "OnInitialized" methods weren't called.
TabPage.razor.cs
partial class TabPage
{
[CascadingParameter]
public Tabs Tabs { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Text { get; set; }
[Parameter]
public string Value { get; set; }
[Parameter]
public bool Enabled { get; set; } = true;
protected override void OnInitialized()
{
if(Tabs is null)
{
throw new ArgumentNullException(nameof(Tabs), "TabPage must exist within Tabs");
}
base.OnInitialized();
if (!Tabs.Tabs.Any())
Tabs.ActiveTab = this;
Tabs.AddPage(this);
}
TabPage.razor.cs
@if(Tabs.ActiveTab == this)
{
@ChildContent
}
Tabs.razor.cs
partial class Tabs
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback<TabPage> OnTabChanged { get; set; }
public TabPage ActiveTab { get; set; }
public List<TabPage> Tabs = new List<TabPage>();
internal void AddPage(TabPage tabPage)
{
Tabs.Add(tabPage);
if(Tabs.Count() == 1)
{
ActiveTab = tabPage;
}
StateHasChanged();
}
private async Task activateTabAsync(TabPage tab)
{
if(tab.Enabled)
{
ActiveTab = tab;
await OnTabChanged.InvokeAsync(tab);
}
}
private string setStyleTab(TabPage tab)
{
if (ActiveTab.Value == tab.Value)
return "show active";
else
return "";
}
}
Tabs.razor.cs
<CascadingValue Value="this">
<ul class="nav nav-tabs">
@foreach(PacsManTabPage tab in Tabs)
{
<li class="nav-item">
<button class="nav-link @setStyleTab(tab)" @onclick=@(async() => await activateTabAsync(tab))>
@tab.Text
</button>
</li>
}
</ul>
<div class="tab-content">
<div class="tab-pane show active">
@ChildContent
</div>
</div>
</CascadingValue>
Usage
<Tabs OnTabChanged="@OnSelectedTabChanged">
<TabPage Text="Storage Details 1" Value="details1">
//Content 1
</TabPage>
<TabPage Text="Storage Details 2" Value="details2">
//Content 2
</TabPage>
</Tabs>
@code {
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
private Task OnSelectedTabChanged(TabPage selectedTab)
{
SelectedTab = selectedTab.Value;
return Task.CompletedTask;
}
}
I tried using RenderFragment as below, but in StorageDetailsTable component, which is associated with _storageDetailsContent2, "Dispose" and "OnInitialized" methods are still called. Usage
@implements IDisposable
<Tabs OnTabChanged="@OnSelectedTabChanged">
<TabPage Text="Storage Details 1" Value="details1">
//Content 1
</TabPage>
<TabPage Text="Storage Details 2" Value="details2">
@_storageDetailsContent2
</TabPage>
</Tabs>
@code {
private RenderFragment? _storageDetailsContent2 = null;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
}
private Task OnSelectedTabChanged(TabPage selectedTab)
{
SelectedTab = selectedTab.Value;
if (SelectedTab == "details2" && _storageDetailsContent2 == null)
_storageDetailsContent2 = StorageDetailsFragment ;
return Task.CompletedTask;
}
public void Dispose()
{
_storageDetailsContent2 = null;
}
protected RenderFragment StorageDetailsFragment => (RenderTreeBuilder builder) =>
{
builder.OpenComponent<StorageDetailsTable>(0);
builder.CloseComponent();
};
}
That requirement could become heavy on memory and resources.
The basic way to do this is to replace this piece
with someting like
note the !=