In a mechanical machine, there are many cogs. Each cog collaborates with the others to function as a machine. To build a system capable of accomplishing an interesting task, multiple components are needed. These components often need to work in coordination together and, thus, must be able to communicate with each other. Data must flow between them. There are multiple ways to share data between components and we will go through all possible ways to share data between components. With each method, you will not only learn how but also when to choose that method. Note that advanced techniques for each approach will be covered in later tutorials. In this tutorial, we will cover:
In Blazor, the simplest component hierarchy is a 3-level structure, as shown in the image below:
This foundational setup consists of an outer-most component (Level 0), a middle component (Level 1), and an inner-most component (Level 2). More complex hierarchies are merely extensions of this model, and the same communication techniques can be applied. Data typically flows from the root (Level 0) down to the deepest level (Level 2). For clarity, this tutorial uses a colour-coded approach: Level 0 is blue, Level 1 is green, and Level 2 is red.
In Blazor, the parent-child relationship is a fundamental aspect of component interaction, limited to 2 adjacent levels: a parent (Level 0) and its direct child (Level 1). Data can flow in 2 directions - parent to child or child to parent. This tutorial focuses solely on the parent to child flow, we will introduce more information in the next tutorials.
Consider the following child component, SimpleChild.razor:
<div> <h3>SimpleChild (Level 1)</h3> <div>@Parameter1</div> </div> @code { [Parameter] public string Parameter1 { get; set; } = ""; }
Here, SimpleChild.razor uses the [Parameter]
attribute to define Parameter1
, a string
it renders within a div. This child can be reused in a parent component, SimpleParent.razor, which passes data to it:
<div> <h3>SimpleParent (level 0)</h3> <SimpleChild Parameter1="@Text" /> </div> @code { public string Text { get; set; } = "Hello Blazor School!"; }
In this example, SimpleParent.razor (Level 0) passes the value of Text
to SimpleChild.razor (Level 1) via the Parameter1
parameter. When rendered, SimpleChild.razor displays "Hello Blazor School!" within its div
, demonstrating how parent to child communication enables the child to render dynamic data from the parent, aligning with the hierarchical structure previously introduced.
[Parameter]
Attribute?While the [Parameter]
attribute is effective for direct parent-child communication in Blazor, it becomes cumbersome when passing data beyond adjacent levels, such as from Level 0 to Level 2 in a 3-level hierarchy. In these cases, developers should opt for alternative communication methods to avoid unnecessary complexity.
Consider the following scenario, illustrated in the diagram:
Here, Component3 needs a parameter from Component1, but Component2 doesn’t require it. Using [Parameter]
would force the parameter to be passed through Component2: Component1 passes to Component2, which must declare the parameter just to forward it to Component3. This "prop drilling" creates 2 issues: Component2 becomes cluttered with unused parameters, and in larger hierarchies with 5 or more levels, managing multiple parameters this way becomes unmaintainable.
In such cases, using CascadingParameter
will allow developers to pass a parameter from Level 0 to Level 2 directly without flowing through Level 1.
Previously, we explored communication within nested components, such as parent-child relationships. In this section, we are discussing the communication of components at the same laevel, such as siblings or unrelated components. The following image shows an example of sibling components:
In such a simple case, sibling components can communicate indirectly through a shared parent using parameters. Consider the following setup:
Component1.razor:
<div> <h3>Component1 (level 0)</h3> <div>@Message</div> </div> @code { [Parameter] public string Message { get; set; } = ""; }
Component2.razor:
<div> <h3>Component2 (level 0)</h3> <div>@Message</div> </div> @code { [Parameter] public string Message { get; set; } = ""; }
To enable these siblings to share data, a parent component can pass the same parameter to both:
<div> <Component1 Message="@HelloMessage" /> <Component2 Message="@HelloMessage" /> </div> @code { public string HelloMessage { get; set; } = "Hello Blazor School!"; }
In Blazor, components at the same level may not always share the same parent—sometimes one is nested while the other is not, as shown below:
Here, Component2 and Component3 need to share data, despite Component2 being nested within Component1. Using CascadingParameter
and CascadingValue
, you can pass data across these components without prop drilling through intermediate levels.
Component2.razor
<div> <h3>Component2 (level 1)</h3> <div>@HelloMessage</div> </div> @code { [Parameter] public string HelloMessage { get; set; } = ""; }
Component1.razor
<div> <h3>Component1 (level 0)</h3> <div>@PersonName:</div> <Component2 /> </div> @code { [CascadingParameter(Name = "CurrentUserName")] public string PersonName { get; set; } = ""; }
Component3.razor
<div> <h3>Component3 (level 0)</h3> <div>@PersonName: @HelloMessage I am @PersonName.</div> </div> @code { [CascadingParameter(Name = "SayHelloMessage")] public string HelloMessage { get; set; } = ""; [CascadingParameter(Name = "CurrentUserName")] public string PersonName { get; set; } = ""; }
In Blazor, using Parameter
or CascadingParameter
to pass data results in each component holding its own copy of the parameter. If those copies are not synchronised, they may become out of sync, and all the components will have a parameter with different values. We will walk you through how to synchronise those copies of parameters in the next tutorials. For now, this section introduces a single source of truth approach, ideal for scenarios where your application needs a single, consistent instance of data across all components. This method suits features like:
To implement a single source of truth in Blazor, you can follow 3 steps:
public class BlazorSchoolTransferService { private string _helloMessage = "Hello Blazor School!"; public string HelloMessage { get => _helloMessage; set { _helloMessage = value; HelloMessageChanged?.Invoke(this, value); } } public event EventHandler<string>? HelloMessageChanged; }
Here, HelloMessage
uses a backing field (_helloMessage
) rather than an auto-property, allowing the service to raise the HelloMessageChanged
event whenever the value updates.
Program.cs
:var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.Services.AddScoped<BlazorSchoolTransferService>();
@inject BlazorSchoolTransferService BlazorSchoolTransferService @implements IDisposable <div>@BlazorSchoolTransferService.HelloMessage</div> @code { protected override void OnInitialized() { BlazorSchoolTransferService.HelloMessageChanged += OnHelloMessageChanged; } public void OnHelloMessageChanged(object? sender, string args) => StateHasChanged(); public void Dispose() { BlazorSchoolTransferService.HelloMessageChanged -= OnHelloMessageChanged; } }
There are some scenarios where a single source of truth is not suitable:
Parameter
or CascadingParameter
is more efficient. For example, passing a message from SimpleParent
to SimpleChild
(as shown earlier) doesn’t require a service, as the data flow is direct and doesn’t benefit from app-wide centralisation.