Blazor – Event Handling in complex pages

by | Sep 28, 2022 | Blog, Dev Team

One of the key challenges our team faced when building out the greatideaz trellispark platform was how to efficiently refresh a complex user experience.

The trellispark platform renders a complete user experience at runtime using only configuration data. There are no custom user experience pages! And we make full use of all of the awesome capabilities of Blazor. We need to efficiently tear down and rebuild a page almost from scratch every time we change, or reload a new record. We also need to efficiently deal with all of the partial page refreshes as the user interacts with the components.

Our objective is to be able to create any user experience for any type of record using only a dynamic hierarchy of Blazor components. A typical page may look something like this:

trellispark field definition example

To be able to render this sort of page we created a straightforward Blazor component hierarchy that looks like this:

Blazor component hierarchy

On our more complex pages we may be dealing with tens, or even hundreds of interacting Blazor components. As the user works with the page they will raise a set of events that might do anything! In most cases, we will only need to refresh part of the screen. In others, we need to do a complete page refresh as the state of a record has changed or the user has navigated to a new record.

The first thing that tripped us up was the fact that we are not dealing directly with a set of HTML components on a page. Blazor is acting upon the page Document Object Model (DOM) in such a way as to minimize updates. This means that when you “refresh” the page in Blazor the underlying DOM isn’t being reset as you might expect. We found that as we navigated between page tabs that if we had a field in the “same location with the same type” that the field was not being disposed of and then re-initialized. Blazor kept the original control in the DOM and “repurposed” it for the new tab. This meant that we were seeing data and configuration from the previous tab in a different control on the new tab!

To get around this issue we had to move the initialization of the field component from the OnInitializedAsync method (called when the component is first created) to the OnParametersSetAsync (called when the parent component is refreshed/re-rendered).

In our Blazor components, we are only (rarely) using the OnInitializedAsync for setting up one-time configuration of the component including registering for events. Any component that registers events also implements IDisposable to ensure that the events are properly removed. This is important as during our initial testing we found that we were unnecessarily refreshing the user experience multiple times to handle a singe event!

Moving the field configuration into the OnParametersSetAsync method meant that we could safely rely on the Blazor DOM updates to flow from our parent components down to their child components when we executed a this.StateHasChanged() on the parent. As the parent refreshed they would reorganize their child components in the DOM and any repurposed components would still be correctly re-initialized. The trick here was ultimately to build a flexible yet simple Blazor component hierarchy that could handle most of the events being generated by the user without having to rebuild the entire page.

The purpose of trellispark is to build a dynamic user experience for each record that the user wished to interact with. This means that from the beginning we were reliant on interacting with secure Data Agnostic Storage through a set of Services calling Open APIs. We used the Services to maintain the entire application state and track changes being made by the user experience components.

This meant that we could use the Services to raise a small set of events to the user experience component to initiate partial, or full page updates. For example, if the user modified the data in a field, then we could detect the data change in the Service and raise an OnFieldChange event to the CommandBar component to enable the Save functionality. This type of event handling enabled us to initiate partial page refreshes outside of the component’s parent-child hierarchy.

The largest scope event being raised by the Services is when the user executes a command on a record, or navigates to a new record. In this case, the event flows to the Instance page and initiates a refresh of the entire page.

Conclusion

The Blazor event handling model is very simple when correctly implemented even in a complex use case. The trick is to build a flexible component hierarchy over a set of Services that handle application state and initiate a small set of events to targeted components.

The big gotchas:

  • Blazor interacts with the DOM in ways you might not expect so do most of your component initialization in OnParametersSetAsync instead of OnInitializedAsync.
  • Make sure that you implement IDisposable to remove event handlers so that you don’t register for the same event multiple times and trigger multiple refreshes.

If you want to get a full copy of trellispark, please download the latest release from our website:

https://portal.greatideaz.com

trellispark is free for up to 10 users per workspace.