Reputation: 4052
I'm stuck as how to optimize rendering of a typical data-driven Blazor component. Consider the following simplified component (partial):
public partial class FooComponent: ComponentBase
{
[Inject]
protected IBarService BarService { get; set; }
[Parameter]
public string OptionalParameter1 { get; set; }
[Parameter]
public string OptionalParameter2 { get; set; };
protected IEnumerable<BarItem> Items { get; set; }
protected override Task OnParametersSetAsync()
{
this.Items = await this.BarService.GetItemsAsync(this.OptionalParameter1, this.OptionalParameter2);
}
}
The component has two optional parameters. They could typically be some filtering values for the service call. The component has no way of knowing whether any of these parameters will be set by the parent component (since they are optional). Whenever a parameter is set, the component retrieves a list of BarItem
items via an (expensive) async service call. The component markup then renders the Items
list in some way.
The problem is that OnParametersSetAsync
is called everytime a parameter is set, causing the component to re-render and do another service call. I can partially optimize this by checking if the parameter values have changed, but there will still be multiple service calls.
In a real-world component with more properties this can quickly result in many unwanted service calls. If OnParametersSetAsync
would be called once for all parameters, this would not be a problem (I understand that Blazor does not work this way).
Is there something I can do to make sure service calls only happen once with the correct parameter values? This seems to me like a very common scenario in data-driven components, and it would be a real shortcoming for Blazor if this is not possible to implement (efficiently).
Upvotes: 0
Views: 234
Reputation: 161
As I faced this issue as well, I can also agree it is quite tedious. Here, I have some feedback in the form of a list, based on my painful experience. I hope you find this useful; if not, simply ignore it.
Avoid reference types in parameters. If I remember correctly (hopefully), reference types always trigger OnParametersSet, whereas value types do so only if there was a change.
Use nullable types like string?, bool?, int? so you can check if a parameter was set.
When checking if parameters have changed, I always declare [Parameter] with the prefix 'Param' and a property which I actually use in the component beneath it, so I never get bamboozled. Example bellow:
@code {
[Parameter] public string? PARAMOptionalParameter1 { get; set; }
private string? OptionalParameter1 { get; set; }
[Parameter] public string? PARAMOptionalParameter2 { get; set; }
private string? OptionalParameter2 { get; set; }
private bool _shouldRender;
private bool _shouldDownloadData;
private void FunctionToCheckForParamtersChange()
{
_shouldRender = false;
_shouldDownloadData = false;
if (PARAMOptionalParameter1 == OptionalParameter1)
{
// Do nothing or whatever you like
}
else
{
OptionalParameter1 = PARAMOptionalParameter1;
_shouldRender = true;
_shouldDownloadData = true;
}
//Other stuff and parameters, you get the idea
}
protected override bool ShouldRender()
{
return _shouldRender;
}
protected override async Task OnParametersSetAsync()
{
FunctionToCheckForParamtersChange();
if (_shouldDownloadData)
{
// DATA download
}
}
}
The cleanest way is to group all your parameters into a class/struct named YourComponentConfig. Implement a custom comparer for this class or struct, and include a boolean flag to indicate whether all parameters are set. As a result, you can simplify the FunctionToCheckForParametersChange method to a single line: if (ParamConfig == Config).
Alternatively, you can use a less conventional approach by creating a reference to the component and modifying its parameters directly from the parent controller (although option 5 is preferable). For example:
<Component @ref ="ComponentRef" />
@code { Component? ComponentRef{get;set;}
void SetData(){ ComponentRef?.SetMyData("Data1","Data2"); // Remember that our custom SetMyData method is out of render circle, so you should put in SetMyData InvokeAsync(StateHasChanged) for render to happen. } }
Upvotes: 0
Reputation: 2589
Yesterday in dotnet Conf Focus Blazor, Daniel Roth showcased something similar. To solve the issue you can use a timer that starts each time one of the parameters are set. And if the timer hits the time limit you invoke the search request. This is called debounce.
I will try to dig out the github repo for the demo if I can.
Upvotes: 0