One of the most common questions I get asked, or I see asked, is what is the best way to communicate between components? I think the answer, like so many things in software development is, it depends. We’re going to look at 3 different ways to communicate between components and how you can best use them.

The three techniques that we’re going to look at are.

  • EventCallbacks
  • Cascading Values
  • State Container

1. EventCallbacks

We’re going to start with EventCallbacks. EventCallback and EventCallback<T> were added to Blazor in .NET Core 3 Preview 3. They give us a better way to define component callbacks over using Action or Func.

The reason for this is that when using Action or Func the callback method had to make a call to StateHasChanged in order to render any changes. With EventCallback this call is made for you, automatically by the framework. You can also provide a synchronous or asynchronous method to an EventCallback without having to make any code changes.

EventCallbacks are great for situations where you have nested components and you need a child component to trigger a parent components method upon a certain event.

<!-- Child Component -->

<button @onclick="@(() => OnClick.InvokeAsync("Hello from ChildComponent"))">Click me</button>

@code {

    [Parameter] public EventCallback<string> OnClick { get; set; }

}
<!-- Parent Component -->

<ChildComponent OnClick="ClickHandler"></ChildComponent>

<p>@message</p>

@code {

    string message = "Hello from ParentComponent";

    void ClickHandler(string newMessage)
    {
        message = newMessage;
    }

}

In the example above, the child component exposes an EventCallback<string> parameter, OnClick. The parent component has registered its ClickHandler method with the child component. When the button is clicked the parent components method is invoked with the string from the child. Due to the automatic call to StateHasChanged, the message the parent component displays is automatically updated.

2. Cascading Values

The second method we are going to look at is Cascading Values. If you want a detailed rundown of Cascading Values and Parameters then checkout one of my earlier posts. But in a nutshell, Cascading values and parameters are a way to pass a value from a component to all of its descendants without having to use traditional component parameters.

This makes them a great option when building UI controls which need to manage some common state. One prominent example is Blazors form and validation components. The EditForm component cascades a EditContext value to all the controls in the form. This is used to coordinate validation and invoke form events.

Let’s have a look at an example.

<!-- Tab Container -->

<h1>@SelectedTab</h1>

<CascadingValue Value="this">
    @ChildContent
</CascadingValue>

@code {

    [Parameter] public RenderFragment ChildContent { get; set; }

    public string SelectedTab { get; private set; }

    public void SetSelectedTab(string selectedTab)
    {
        SelectedTab = selectedTab;
        StateHasChanged();
    }
}
<!-- Tab Component -->

<div @onclick="SetSelectedTab">@Title @(TabContainer.SelectedTab == Title ? "Selected" : "")</div>


@code {

    [CascadingParameter] TabContainer TabContainer { get; set; }

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

    void SetSelectedTab()
    {
        TabContainer.SetSelectedTab(Title);
    }
}

In the code above, we’ve setup a TabContainer component. It displays the currently selected tab, it also sets up a cascading value. In this example, the value that is cascaded is the TabContainer component itself.

In the Tab component, we receive that cascaded value and use it to call the SetSelectedTab method on the TabContainer whenever the div is clicked. We also check the value of the current SelectedTab, again using the cascaded value, and if it matches the title of the tab, we add “Selected” to the tab title.

3. State Container

The last method we’re going to look at is using a state container. There are various degrees of complexity you can go to when implementing a state container. It can be a simple class injected as a singleton or scoped service, depending on if you’re using Blazor client-side or server-side respectively. Or you could implement a much more complex pattern such as Flux.

This is the most complex solution out of the 3. With this solution it is possible to manage and coordinate many components across whole applications.

In the example, we’ll look at using a simple AppState class as our state container.

public class AppState
{
    public string SelectedColour { get; private set; }

    public event Action OnChange;

    public void SetColour(string colour)
    {
        SelectedColour = colour;
        NotifyStateChanged();
    }

    private void NotifyStateChanged() => OnChange?.Invoke();
}

This is our AppState class. It stores the currently selected colour as well as exposing a method to update the selected colour. There’s also a OnChange event, this will be needed by any components wishing to show the selected colour.

<!-- Red Component -->

@inject AppState AppState

<button @onclick="SelectColour">Select Red</button>

@code {

    void SelectColour()
    {
        AppState.SetColour("Red");
    }

}
<!-- Blue Component -->

@inject AppState AppState

<button @onclick="SelectColour">Select Blue</button>

@code {

    void SelectColour()
    {
        AppState.SetColour("Blue");
    }

}

Next we have two component, Red and Blue. These components just update the selected colour on the AppState using a button click.

@inject AppState AppState
@implements IDisposable

@AppState.SelectedColour

<RedComponent />
<BlueComponent />

@code {

    protected override void OnInitialized()
    {
        AppState.OnChange += StateHasChanged;
    }

    public void Dispose()
    {
        AppState.OnChange -= StateHasChanged;
    }

}

This last component ties everything together. It handles the OnChange event exposed by the AppState class. Whenever the selected colour is changed StateHasChanged will be invoked and the component will re-render with the new selected colour.

It’s important to remember to unsubscribe the components StateHasChanged method from the AppState’s OnChange event - otherwise we could introduce a memory leak. We can do this my implementing the IDisposable interface as per the example code above.

Summary

We’ve looked at 3 different ways to handle communication between components in Blazor.

We started off looking at simple scenario of parent child communication using EventCallbacks. We then looked at how we could use Cascading Values to coordinate communication amount closely related, nested, components. Finally, we looked at a larger more application wide component communication method, state containers.

What’s your preferred method?