8

Is it possible to render components "dynamically" using Blazor?

 3 years ago
source link: https://jonhilton.net/blazor-dynamic-components/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Is it possible to render components "dynamically" using Blazor?

November 11, 2020 · 8 minute read · Tags: blazor

Nothing twists a developer’s brain more than things being ‘dynamic’ (except perhaps, recursion).

It’s all very nice and predictable when you can look at the code and see exactly which components are being rendered on any given page.

But when you start building your web applications using Blazor you might get to a point where you decide you need to render your components ‘dynamically’.

This usually means you need the application to render different things based on data (in other words the decision about which component(s) to render occurs at run-time and not compile-time).

Say for example you want to build a customisable dashboard where your users can pick and choose the widgets they want displayed.

Be Pragmatic!

It’s worth highlighting that Blazor’s component model approach means you often don’t need to get clever with how you render components in your app.

Often a simple @foreach and standard component declaration will suffice. But when you do actually need to render different components based on data the following approaches are worth knowing.

It turns out there are a few ways to do this in Blazor:

@foreach loop and switch statement

One option is to create a List of your components, loop over them and use a switch statement to render the relevant component…

@page "/SimpleDashboard"

@foreach (var widget in _components)
{
    switch (widget)
    {
        case WidgetA:
            <WidgetA/>
            break;
        case WidgetB:
            <WidgetB/>
            break;
    }
}

@code {
    readonly List<IComponent> _components = new List<IComponent>
    {
        new WidgetA(),
        new WidgetB()
    };
}

Here we’ve a List which can contain any object which implements the IComponent interface.

The switch takes care of figuring out what to render by essentially checking the type of each item in the list.

With this approach you can create any component you like, add a case to the switch statement and then if an instance of that component appears in the list the relevant arm of the switch statement will take care of rendering the component.

This works but does carry a few downsides.

Firstly it feels a bit redundant to create new instances of the components to add them to the _components list. In reality we never use these instances other than to evaluate their type as part of the switch statement.

Why not store the component type rather than an instance of the component?

You might imagine you could store a Type in the list and use that in the switch case.

Unfortunately this isn’t possible as there’s no way (that I know of) to perform a switch comparison against a Type. You can however check the type of an object and use that as your condition (as we are here).

The other real downside is the switch statement itself will grow as you add new components. Plus you will need to come back and deploy new code to handle these new component types, so you could end up with a situation where the data refers to a widget you haven’t handled yet.

Saying that, this is conceptually the simplest way to achieve this kind of pseudo-polymorphism, and unless you’re coming up with new component types on a regular basis the cost of spending a few moments to wire up a new branch in the switch statement is probably manageable.

RenderFragments declared in code

Another option is to declare RenderFragments rather than separate components.

@page "/LessSimpleDashboard"

@foreach (var widget in _components)
{
    @widget
}

@code {
    readonly List<RenderFragment> _components = new List<RenderFragment>
    {
        RenderWidgetA,
        RenderWidgetB
    };

    static readonly RenderFragment RenderWidgetA = __builder =>
    {
        <h3>Widget A</h3>
        <p>This is a widget</p>
    };

    static readonly RenderFragment RenderWidgetB = __builder =>
    {
        <h3>Widget B</h3>
        <p>This is another widget</p>
    };
}

With this approach we can declare anything we like in each render fragment and add as many of them as we like to the list.

Then we foreach over each RenderFragment in the list and render each one!

This gets rid of the switch statement but still provides plenty of flexibility in terms of what you render and how.

The obvious downside is that you end up with all your UI code lumped together in one place and lose the benefits that come from separating your UI into separate components. Generally speaking separate components will be easier to maintain and reason about than a bunch of RenderFragment declarations.

It’s worth noting there are some performance considerations when it comes to creating components vs RenderFragments. If you want to find out more about those check out this handy list of best practices for Blazor WASM from the official docs.

Build your own Render Tree

Finally we have an option which comes with a big caveat.

This approach uses fairly low-level APIs which you wouldn’t typically make use of when building apps using Blazor. It’s also worth noting there are developments coming in this area with a proposal for a DynamicComponent which would essentially render this approach redundant.

You can track the progress of that issue here.

Saying that, this option is technically possible right now and shines a light on how Blazor renders components which is useful to know anyway!

When you build your Blazor app you generally declare components using the Razor templating language.

But under the hood there are a few more steps before your UI is rendered in the browser.

Updating the DOM is an expensive operation for the browser; CPU intensive and slow (in relative terms).

Blazor takes a similar approach to other frameworks like React whereby it keeps an in-memory representation of the DOM elements. This is represented as a tree of HTML elements.

You can see the initial render tree if you head to the obj folder after building a Blazor project.

In obj/Debug/net5.0/Razor (if you’re using .NET 5) you’ll find folders representing your application.

Here’s part of the standard Counter example as it appears in obj/Debug/net5.0/Razor/Pages/Counter.razor.g.cs.

 protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
        {
            __builder.AddMarkupContent(0, "<h1>Counter</h1>\r\n\r\n");
            __builder.OpenElement(1, "p");
            __builder.AddContent(2, "Current count: ");
			__builder.AddContent(3, currentCount);
			__builder.CloseElement();
			
			// other code omitted
		}

So you can see that although we generally write our components using Razor, Blazor is ultimately going to express that Razor code as a RenderTreeBuilder.

As your users interact with your UI new render trees will be generated in memory.

There are a couple of benefits to this virtual DOM.

Firstly it can be updated many times (when executing logic, as various components are updated based on new parameters etc.) without performing expensive browser DOM updates until everything’s figured out. This means you get one DOM update at the end, rather than lots of DOM updates as a component (or tree of components) renders.

Secondly Blazor can perform incremental rendering. This is where it compares the two Render Trees (the old one and the new one), figures out what’s changed then makes the smallest number of changes possible to update the DOM to match the new tree.

Typically you wouldn’t write RenderTree logic yourself, but it can be handy when declaring ‘dynamic’ components.

Here’s an example:

@page "/dashboard"

@foreach (var widget in _widgets)
{
    <h1>@widget.Key</h1>
    @renderWidget(widget.Value)
}

@code {

    readonly Dictionary<string, Type> _widgets = new Dictionary<string, Type>
    {
        ["Counter"] = typeof(Counter),
        ["Weather"] = typeof(FetchData)
    };

    private RenderFragment renderWidget(Type t) => builder =>
    {
        builder.OpenComponent(0, t);
        builder.CloseComponent();
    };

}

Here we’ve declared a dictionary which has a string key (essentially the name or label for the widget) and a Type value.

With this we can add as many ‘widgets’ as we like.

In this case I’ve stuck to the default Counter and FetchData components. renderWidget returns a delegate which accepts a RenderTreeBuilder parameter.

From here you can build your own tree however you see fit!

In this example we simply open the component (using the Type passed in from the dictionary) then close it. This is a very procedural way of writing your components but in this case it means we can throw any component (of any Type) into the dictionary and it will be rendered accordingly.

The obvious caveat is this doesn’t currently set any parameters for the component.

You can set parameters easily enough:

private RenderFragment renderWidget(Type t) => builder =>
{
	builder.OpenComponent(0, t);
	builder.AddAttribute(1, "some-parameter", "a value");
	builder.CloseComponent();
};

The trick would be knowing what parameters are available for the various components you wish to render.

This is something that the proposed DynamicComponent would handle by accepting a dictionary of parameters.

No perfect option

So there are a few different ways to render components when you don’t know what you need to render at compile time.

Again it’s worth reiterating you can often take a simpler approach with Blazor.

A simple foreach and static component declaration will often do the job, but occasionally you’ll run into more complex requirements.

If so, and until DynamicComponent sees the light of day it pays to take the simplest approach and limit how much ‘dynanism’ you incorporate into your code. Just because you can doesn’t mean you should, especially when it comes to building your own render tree.

But all that said, it’s good to know what options exists if you find yourself going down this path!

References

For more on the Virtual and Incremental DOMs check out this handy explanation from Blazor University.

ASP.NET Core Blazor WebAssembly performance best practices outlines a few tips for optimising your Blazor WASM applications for performance and also covers declaring RenderFragments in @code (rather than separate components).

Considering Blazor for your next project?

Learn my simple, repeatable process for transforming ideas into features using Blazor's component model.

4 days, 4 emails; enter your email in the box below and I'll send you Lesson #1.

Email address

Next up

Dark mode for your web applications (using Blazor and Tailwind CSS)
Eyestrain is a real problem; help your users by adapting your site to their dark mode preferences
Render Blazor WASM components in your existing MVC/Razor Pages applications
You can render individual Blazor WASM components in your existing Razor Pages (or MVC) Core app.
Prerendering your Blazor WASM application with .NET 5 (part 2 - solving the missing HttpClient problem)
If you refresh your prerendered Blazor WASM site today you’re in for a big surprise…
Login
Lars Holm Jensen
0 points
31 days ago

Why would you foreach and switch on anything but your models?

Jon Hilton
0 points
31 days ago

Hey Lars.

What do you mean by 'models' in this context?

I'm guessing you're referring to data which you'd get back from an HTTP call or similar?

Assuming that's the case it's worth noting Blazor has no built-in concept of models. 'Model' is the term we've used for a number of years with things like MVC and Razor Pages, but Blazor simply works on the basis that anything you declare in the @code for your component is accessible for your markup to bind to.

So you can just as easily display the value of a private string variable in your component as you can loop through a list of objects (to render markup for each one).

In these examples I've used hardcoded data as that's the simplest way to spin up these examples and explore the various options.

In a real-world application it's likely these lists would be populated via a query of some sort (possibly to a database) but again, Blazor itself wouldn't know or care where the data came from, or that we're thinking of it as a 'model'. It's just data :-)

Hope that helps (and makes sense!)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK