I'm trying to avoid repetitive OnParametersSet events from triggering in child components when the parent page/component has a "long" running OnParametersSet event of its own. For example, here's a basic page with some child components.
@page "/test"
<Node>
<Node>
<Node></Node>
</Node>
</Node>
@code {
protected override async Task OnInitializedAsync()
{
Console.WriteLine("Page: OnInitializedAsync - start");
await Task.Delay(2000);
Console.WriteLine("Page: OnInitializedAsync - finish");
}
}
The node component is very simple as well:
<div>Node: @GetHashCode()</div>
@ChildContent
@code {
[Parameter] public RenderFragment ChildContent { get; set; }
protected override void OnParametersSet()
{
Console.WriteLine("Node {0}: OnParametersSet", GetHashCode());
}
}
Here is what I see in the console. Note that after the page has finished its OnParametersSet event, two of the three child components call OnParametersSet again.
Page: OnInitializedAsync - start
Node 924945978: OnParametersSet
Node 1026183343: OnParametersSet
Node 213373360: OnParametersSet
Page: OnInitializedAsync - finish
Node 924945978: OnParametersSet
Node 1026183343: OnParametersSet
Is this just a flaw with Blazor or is there a better way to avoid these extra events? The node components have nothing to do with the content on the page itself. In the real world I may have many child components, each that are trying to fetch data async so I want to prevent these extra events from firing.
The only workaround I've found is to wrap the nodes block with an @if(pageSetParametersEventHasFinished) statement that prevents the node components from initializing until the page is "ready".
Went with an abstract component like the following that handles parameter changes.
public abstract class WatchComponent : ComponentBase
{
private bool _isDirty = true;
private bool _shouldRender = true;
protected abstract Task OnParametersChangedAsync();
protected override async Task OnParametersSetAsync()
{
if (!_isDirty) return;
await OnParametersChangedAsync();
_isDirty = false;
}
protected override void OnAfterRender(bool firstRender)
{
_shouldRender = false; // disable if child component has RenderFragment parameter
Console.WriteLine("{0}: OnAfterRender", GetHashCode());
base.OnAfterRender(firstRender);
}
protected override bool ShouldRender() => _shouldRender;
protected void SetField<T>(ref T field, T value)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return;
Console.WriteLine("parameter changed from {0} to {1}", field, value);
field = value;
_isDirty = true;
_shouldRender = true;
}
}
Using this with the demo from above I tried the following and everything seemed to work.
Updated page:
@page "/test"
<Node Value="@_value1">
<Node Value="@_value2">
<Node></Node>
</Node>
</Node>
@code {
private string _value1 = "v1";
private string _value2 = "v2";
protected override async Task OnInitializedAsync()
{
Console.WriteLine("Page: OnInitializedAsync - start");
await Task.Delay(2000);
Console.WriteLine("Page: OnInitializedAsync - finish");
_value1 = "v1.1";
_value2 = "v2";
}
}
Updated Node component:
@inherits WatchComponent
<div>Node: @GetHashCode(), Value="@Value"</div>
@ChildContent
@code {
private string _value;
[Parameter]
public string Value
{
get => _value;
set => SetField(ref _value, value);
}
[Parameter] public RenderFragment ChildContent { get; set; }
protected override Task OnParametersChangedAsync()
{
// load something async here
return Task.CompletedTask;
}
}
The console output:
Page: OnInitializedAsync - start
parameter changed from to v1
parameter changed from to v2
833522111: OnAfterRender
854912238: OnAfterRender
668729564: OnAfterRender
Page: OnInitializedAsync - finish
parameter changed from v1 to v1.1
833522111: OnAfterRender