Blazor Components - Code File
Properties
Blazor component properties can be divided into several types and they have different recommended patterns.
Injected service properties should be private, non-nullable, initialized with "default!", and you don’t have to check them for null before using them – if the program can’t provide a value for them, it will throw an exception.
Don’t: 🙁
[Inject] public IToastService? ToastService { get; set; }
...
ToastService?.Notify(ToastType.Error, "Something went wrong", ex.Message);
Do: 🙂
[Inject] private IToastService ToastService { get; set; } = default!;
...
ToastService.Notify(ToastType.Error, "Something went wrong", ex.Message);
Some properties are, logically speaking, required parameters. This means that the operation of the component depends on the property being populated with a value. These required parameters should be public, non-nullable, decorated with the “EditorRequired” attribute (unless it’s a CascadingParameter), initialized with “default!”, and have it’s value checked against default in the “OnParametersSet” method.
For instance, consider this Razor file for a “NameList” component:
@foreach (var name in Names)
{
<li class="name">
@name
</li>
}
Clearly, the “Names” parameter would be mandatory for this component.
[Parameter, EditorRequired] List<string> Names { get; set; } = default!;
While it would be OK for the list to be empty, not passing the list at all would be an error:
<NameList /> @* Error! No "Names" value set! *@
How a check the mandatory parameters:
protected void OnParametersSet()
{
base.OnParametersSet();
if (Names is null)
{
throw new InvalidOperationException($"{nameof(Names)} is required");
}
// Check other mandatory parameters, including CascadingParameters
if (ParentComponent is null)
{
throw new InvalidOperationException($"{nameof(ParentComponent)} is required");
}
// Other code
}
Note: The “EditorRequired” attribute shouldn’t used for the “Value” property of a component that’s intended to be used with the “@bind-Value” syntax, as it will generate a false warning for the “ValueChanged” part.
If the property is not a service or a mandatory parameter, then it is an optional parameter.
For optional parameters, initialize them with a reasonable value (not “default!", but String.Empty, new List<T>, etc.)
Parameters should be auto properties – put complicated code in another method called from OnInitialize and OnParametersSet.
Parameters should not be set in the component’s code - this is an anti-pattern. This includes public methods to set the value of component parameters.
EventCallbacks
When your child component wants to let a parent component know that something happened, add an EventCallback parameter named “OnXXX” to the child component.
[Parameter] public EventCallback OnSomethingHappened { get; set; }
The event may be typed, in which case it will expect an argument to be passed:
[Parameter] public EventCallback<int> OnSomethingHappened { get; set; }
In the child component’s code, add a method to raise the event, and call it when appropriate:
private async Task RaiseSomethingHappened()
{
await OnSomethingHappened.InvokeAsync();
}
In the parent component’s markup, add the child component and include a reference to a method to handle the event:
<ChildComponent SomeParameter="@_someValue"
OnSomethingHappened="@HandleSomethingHappened"/>
And in the parent’s code, add the method:
private async Task HandleSomethingHappened()
{
// Some code
}
Fields
Initialize private fields with "default!" if they are going to be set in a component lifecycle event and before they are referenced, or a reasonable value otherwise.
Name component reference fields “_refXXX” and decorate them with “[AllowNull]"; they don’t have to be otherwise initialized. You shouldn't use the same component reference field for two different component references.
[AllowNull] private Modal _refModal;
Private members are typically fields and their names start with an underscore; public members are typically properties and are camel-cased.
Lifecycle Events
If you have a Parent component like this:
<h3>Parent</h3>
<ChildComponent />
And a Child component like this:
<div>Child</div>
This is the order of lifecycle events between them:
- Parent: OnInitialized
- Parent: OnInitializedAsync
- Parent: OnParametersSet
- Parent: OnParametersSetAsync
- Child: OnInitialized
- Child: OnInitializedAsync
- Child: OnParametersSet
- Child: OnParametersSetAsync
- Parent: OnAfterRenderAsync
When a component is initialized, its parameters are set before OnInitialized runs, even though OnParametersSet is not called before OnInitialized. OnParameters is called after, though.
Therefore,put parameter-based code in OnParametersSet, and not in OnInitialized or OnAfterRender. Specifically, code in OnInitialized only runs once for the lifetime of the component, and code there that uses parameters will not run again when the parameters change; probably not what you want in most cases.
Also, you almost never need to call StateHasChanged from inside a lifecycle event.
Don’t: 🙁
protected override void OnInitialized()
{
base.OnInitialized();
_localValue = ParameterValue + 1;
StateHasChanged();
}
Do: 🙂
protected override void OnParametersSet()
{
base.OnParametersSet();
_localValue = ParameterValue + 1;
}
If you want to limit the amount of work in OnParametersSet, store the previous parameter value and compare.
In overridden life-cycle methods (OnInitialized, OnParametersSet, etc), it’s important to call base members first so that the super class (if any) can initialize itself correctly. The current implementation of ComponentBase does nothing, but that might change one day, or your component may now or in the future derive from another component, so go ahead and get into the practice and save yourself the effort of tracking down a bug or having to change hundreds of components in the future.
Don’t: 🙁
protected override async Task OnInitializedAsync()
{
// Missing something here...
await LoadUser();
}
Also Don’t: 🙁
protected override async Task OnInitializedAsync()
{
await LoadUser();
await base.OnInitializedAsync(); // Argh! Backwards!
}
Do: 🙂
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
await LoadUser();
}
Consider putting code that is not specific to just that one component in a service or static class for re-usability purposes.
EventCallbacks fields are structs and don’t have to be initialized. You also don’t have to check the HasDelegate property before calling them, unless you are calling them asynchronously. Name component EventCallback methods "OnXXX"; OnSubmit, OnClick, etc
EventCallback methods should be “async Task”, not be "async void".
Prefer explicitly stating the inheritance of the component in the code, even if it does inherit from the default ComponentBase.