Dynamic Components in Blazor with Parameters and Event Handling

on

Blazor’s Dynamic Components were something that I was really looking forward to with .NET 6 coming. Many tutorials have been written about how to use Dynamic Components, however, a lot of them only cover the basics. Very few go into details about how to pass parameters or how to set up event handlers etc. So, as I needed to use the DynamicComponent in a real life project, I decided to follow it up with an article, like I often do. And the code to follow along is available on my GitHub.

Dynamic Components in Blazor – the basics

A DynamicComponent is a component that renders another component dynamically according to its Type parameter. It can be really practical for rendering components without using conditional logic or iteration through possible types. You can think of it as a place holder “My component goes here…” and the actual component will be defined through code.

Let’s provide a basic example of how this works. We will have a main page with a dropdown. Based on the control you select from the dropdown you will see the component render dynamically. We can start by making three dummy components: CarComponent.razor, BikeComponent.razor, MotorcycleComponent.razor each having content like:

<h3>Car Component</h3>

And then add a page that has a dropdown that uses the DynamicComponent tag to render the selected component:

Markup:

@inherits IndexBase
@page "/"

<PageTitle>DynamicComponent Demo</PageTitle>

<h1>DynamicComponent Demo</h1>

<label>Choose a vehicle:</label>
<select value="@SelectedVehicleType" @onchange="SelectedVehicleChanged">
    <option value="1">Car</option>
    <option value="2">Motorcycle</option>
    <option value="3">Bike</option>
</select>

<h2 class="mt-4">Basic DynamicComponent Loaded: </h2>
<DynamicComponent Type="DynamicComponentType" />

Code behind:

using Microsoft.AspNetCore.Components;

namespace DynamicComponentsDemo.Pages
{
    public class IndexBase : ComponentBase
    {
        private const string BASIC_CAR_COMPONENT = "DynamicComponentsDemo.BasicComponents.CarComponent";
        private const string BASIC_MOTORCYCLE_COMPONENT = "DynamicComponentsDemo.BasicComponents.MotorcycleComponent";
        private const string BASIC_BIKE_COMPONENT = "DynamicComponentsDemo.BasicComponents.BikeComponent";
        protected Type DynamicComponentType { get; set; } = Type.GetType(BASIC_CAR_COMPONENT);
        protected int SelectedVehicleType { get; set; } = 1;

        protected void SelectedVehicleChanged(ChangeEventArgs e)
        {
            SelectedVehicleType = Convert.ToInt32(e.Value);
            switch (SelectedVehicleType)
            {
                case 1:
                    DynamicComponentType = Type.GetType(BASIC_CAR_COMPONENT);
                    break;
                case 2:
                    DynamicComponentType = Type.GetType(BASIC_MOTORCYCLE_COMPONENT);
                    break;
                case 3:
                    DynamicComponentType = Type.GetType(BASIC_BIKE_COMPONENT);
                    break;
                default:
                    break;
            }
        }
    }
}

So, as you can see, the dropdown is bound to the value of the SelectedVehicleType variable and the dynamic component uses the Type.GetType() to get the type of the component to be rendered. This is how it looks like, when run:

DynamicComponent Basic Demo

And this is what every tutorial blog post and/or video on the Internet teaches you. Now, let’s dig a little bit deeper into more real life cases.

Pass Parameters to Dynamic Components in Blazor

Of course, your components will have parameters.

Normally when you have a component, you’d just add a parameter there like:

[Parameter]
public string MyParameter { get; set; }

And then just provide it a value when you add a component to your code:

<MyComponent MyParameter="Some value" />

However, the DynamicComponent does not yet have a way to “force” what kind of component will be taking its place. It is currently literally a placeholder for any type of component. So, it is not possible to define parameters on a DynamicComponent and then have all components that will replace it be forced to have those parameters.

So you cannot do something like:

<DynamicComponent Type="DynamicComponentType" MyParameter="Some value" />

This is not allowed, and it will not work.

Parameters are passed to the DynamicComponent through a dictionary where the key is the string representation of a property, and the value is any object (Dictionary<string, object>). So, in your code you’d define and fill this dictionary and pass it to the Parameters property of the DynamicComponent.

If we want to make our basic vehicle components a bit more complex, we can add a parameter of type DateTime and call it CreatedAt. So, a component would look like this:

<h3>Bike Component</h3>
<span>Bike created at: @CreatedAt.ToString()</span>

@code {
    [Parameter]
    public DateTime CreatedAt { get; set; }
}

So, let’s update our Index.razor.cs file to manage the dictionary:

using Microsoft.AspNetCore.Components;

namespace DynamicComponentsDemo.Pages
{
    public class IndexBase : ComponentBase
    {
        private const string BASIC_CAR_COMPONENT = "DynamicComponentsDemo.BasicComponents.CarComponent";
        private const string BASIC_MOTORCYCLE_COMPONENT = "DynamicComponentsDemo.BasicComponents.MotorcycleComponent";
        private const string BASIC_BIKE_COMPONENT = "DynamicComponentsDemo.BasicComponents.BikeComponent";
        private const string CREATED_AT_PARAMETER = "CreatedAt";
        
        protected Type DynamicComponentType { get; set; } = Type.GetType(BASIC_CAR_COMPONENT);
        protected int SelectedVehicleType { get; set; } = 1;
        protected Dictionary<string, object> DynamicComponentParameters { get; set; }

        protected override void OnInitialized()
        {
            base.OnInitialized();
            DynamicComponentParameters = new();
            DynamicComponentParameters.Add(CREATED_AT_PARAMETER, DateTime.Now);
        }

        protected void SelectedVehicleChanged(ChangeEventArgs e)
        {
            SelectedVehicleType = Convert.ToInt32(e.Value);
            switch (SelectedVehicleType)
            {
                case 1:
                    DynamicComponentType = Type.GetType(BASIC_CAR_COMPONENT);
                    break;
                case 2:
                    DynamicComponentType = Type.GetType(BASIC_MOTORCYCLE_COMPONENT);
                    break;
                case 3:
                    DynamicComponentType = Type.GetType(BASIC_BIKE_COMPONENT);
                    break;
                default:
                    break;
            }
            DynamicComponentParameters[CREATED_AT_PARAMETER] = DateTime.Now;
        }
    }
}

And in the Index.razor we need to pass the dictionary to the DynamicComponent:

<DynamicComponent Type="DynamicComponentType" Parameters="DynamicComponentParameters" />

And it works. Now every time you change the vehicle type in the dropdown, you will get a new time printed out below.

If you think this is clumsy as it is using a lot of magic strings, making intellisense obsolete, prone to error as some components might not have all the parameters defined that are in the dictionary and vice versa… you’re completely right. It’s clumsy as hell. But I’ll be discussing this further in this post, let’s just go through a few more basic things first.

EventHandlers and Dynamic Components in Blazor

Apart from having parameters, Blazor components can invoke events. So, you pass an event handler to the Blazor Component and whenever it invokes the event, your event handler gets triggered.

How does this work with the DynamicComponent?

To do this, we need to add an EventCallback<> public parameter to the component. We can add an OnButtonClick event callback to our components, and add a button that invokes it:

<h3>Car Component</h3>
<span>Car created at: @CreatedAt.ToString()</span>
<button @onclick="() => { OnButtonClicked.InvokeAsync(ActionDescription); }" >Click the Car button!</button>

@code {
    [Parameter]
    public DateTime CreatedAt { get; set; }
    [Parameter]
    public EventCallback<string> OnButtonClicked { get; set; }
    protected string ActionDescription = "Car Button Clicked!";
}

When a button is clicked, the event callback is invoked passing a string value.

If you were to add this component directly to a component, you’d do something like:

<CarComponent OnButtonClicked="SomeButtonClicked" />

@code {
    protected void SomeButtonClicked (string inputString)
    {
        // Do something here
    }
}

This means that the SomeButtonClicked event handler is passed to the component and that method gets executed when the event callback is invoked.

As we notice, the EventCallback<> is just a public parameter, and the dictionary used to pass parameters to Dynamic Components is of type <string, object>, so it should be possible to pass the event handler as an object through the parameter dictionary.

And this is exactly how you do it, though, there is a catch! You cannot pass a method directly.

DynamicComponentParameters.Add("OnButtonClicked", "SomeButtonClicked");

This won’t work and will throw an error in the compiler.

Instead, you need to use the EventCallback.Factory to create the event callback:

 DynamicComponentParameters.Add("OnButtonClicked", EventCallback.Factory.Create<string>(this, SomeButtonClicked));

This now works and if you use this, the SomeButtonClicked method will get called once any component that replaces the DynamicComponent invokes the `OnButtonClick` event callback.

Pass Object as a Parameter to Dynamic Component in Blazor

As I mentioned earlier, passing parameters through a `Dictionary<string, object>` object type is just ugly. It involves a lot of magic strings, it makes it hard to use intellisense, it requires a lot of null reference checking and is just… not how I’d prefer to do things if I had a choice.

What I would love to do is to have a class that defines the parameters, make an object out of it and just define the parameters and pass that object to the dynamic component.

And while you cannot do exactly that due to the way the DynamicComponent receives parameters, you can do a “close enough” workaround. We start by creating a class called ComponentParameters and make a GetParameterDictionary method in it:

public class ComponentParameters
{
    public Dictionary<string, object> GetParameterDictionary()
    {
        Dictionary<string, object> parameters = new Dictionary<string,object>();
        foreach (var property in this.GetType().GetProperties())
        {
            parameters.Add(property.Name, property.GetValue(this));
        }
        return parameters;
    }
}

The method above creates a Dictionary with the property name as key and the object as a value for every property defined on a class.

So, in order to pass parameters to our components we can make a class that defines the parameters and extends the ComponentParameters class:

public class MyComponentParameters : ComponentParameters
{
    public DateTime CreatedAt { get; set; }
    public EventCallback<string> OnButtonClicked { get; set; }
}

and then just make an instance of that class and use the method to get the dictionary to be passed:

var myParams = new MyComponentParameters
{
    CreatedAt = DateTime.Now,
    OnButtonClicked = EventCallback.Factory.Create<string>(this, SomeButtonClicked)
}

var dictionaryToBePassed = myParams.GetParameterDictionary();

For me this is a MUCH cleaner way of doing things.

Parameters and Components implementing an Interface

Another potentially difficult situation that occurs is when you need to add or modify a parameter. The compiler will not notice that one of the components that could replace the DynamicComponent does not have some parameter. This will not break in compile time, it will break in runtime!

So, you would manually need to ensure that, when you add a parameter, that all the components have that parameter. I am lazy and I’d like my compiler to help me with this.

An approach that can be useful is to make an interface that defines the parameters and callbacks and have both the components and the data model implement it. This way, when you decide to add or remove a parameter, you just modify the interface and this will actually cause the compiler to identify all errors ensuring you don’t miss anything:

public interface IMyComponentParameters
{
    DateTime CreatedAt { get; set; }
    EventCallback<string> OnButtonClicked { get; set; }
}

Then you can have your component and your parameters model implement it. In order to make this easy I always keep my code separate from my view logic (see more: Organizing and Naming Components in Blazor, Create new component in Blazor – useful tool).

So, we make a data class called ComponentParameters that implements IComponentParameters:

public class MyComponentParameters : ComponentParameters, IMyComponentParameters
{
    public DateTime CreatedAt { get; set; }
    public EventCallback<string> OnButtonClicked { get; set; }
}

And we can have the bases of our components also implement the same interface:

public class HovercraftComponentBase : ComponentBase, IMyComponentParameters
{
    [Parameter]
    public DateTime CreatedAt { get; set; }
    [Parameter]
    public EventCallback<string> OnButtonClicked { get; set; }
    protected string ActionDescription = "Hovercraft button clicked!";
}

Now, whenever I want to make any changes to how the parameters will be passed between dynamic components, I just modify the IMyComponentParameters interface, and the compiler will force me to modify any place where the parameters are set as well as any component that implements it.

This approach helps me use the compiler, intellisense and all the other cool benefits of high level languages, removing magic strings completely.

Conclusion

In the project I worked on we have a “UI frame” and then different entities that can be edited from it. This was a perfect case as, by implementing the DynamicComponent I could have each of my entity types have its own editor component and the whole frame did not need to care what it was. Furthermore, it allows me to add new entity types and editors in the future without touching the “UI frame” code at all – my components just need to implement the interface and they just work and take care of themselves!

I would love to hear about the issues you are having and the applications you found for dynamic components, please feel free to share in the comments.

Leave a Reply

Your email address will not be published.

You are currently offline