Component is the building block of a web page. A component has its UI, the according logic and is also reusable. A web page will have many components to display different parts of the page. For that reason, the interaction between components is needed. The interaction contains sharing data and how to react when the data is changed. There are 4 ways to exchange data between components:
CascadingParameter
to pass parameters from one of the parent components to the child component. One-way notifying.Parameter
to pass parameters from the from the closest parent component to the child component. One-way notifying.Parameter
and delegate
to notify changes from the child component. Two-way notifying.You can download the example code used in this topic on GitHub.
CascadingParameter
Using CascadingParameter
is a good way to share data from the grandparent or parent component to the descendant components. The following image illustrates how CascadingParameter
works.
When using CascadingParameter
to pass data, any data changes that occur in the descendants components will not notify the ancestor component but any data changes from the ancestor component will notify the descendant component.
public Person FirstPerson { get; set; } = new() { Name = "Person from grand parent component" };
CascadingValue
component to pass the value.<CascadingValue Value="FirstPerson"> <CPParent></CPParent> </CascadingValue>
The CascadingValue
component should wrap the parent component. Otherwise it cannot be passed.
CascadingParameter
attribute.[CascadingParameter] public Person ReceivedPerson { get; set; }
<span>Received value: @ReceivedPerson.Name</span>
The property of the descendant is not required to have the same property name. As in the example, you are passing FirstPerson
from the ancestor component to ReceivedPerson
in the descendant component. Instead, CascadingParameter
in the descendant component will look for the same type from the ancestor component. The example code will not work if FirstPerson
and ReceivedPerson
are not the same type. Here is the result of the example.
CascadingParameter
The previous example demonstrates how to pass a single parameter using CascadingParameter
. You can also pass multiple parameters using CascadingParameter
, if you have 2 parameters with the same type, you will have to name that parameter. In the following example, you will pass another parameter with the Person
type.
public Person FirstPerson { get; set; } = new() { Name = "Person from grand parent component" }; public Person SecondPerson { get; set; } = new() { Name = "Another person from grand parent component" };
CascadingValue
to pass the parameters, you can change the order as you want. You will need to name the passing parameter by adding Name="<ParameterName>"
.<CascadingValue Value="FirstPerson" Name="GrandParentFirstPerson"> <CascadingValue Value="SecondPerson" Name="GrandParentSecondPerson"> <CPParent></CPParent> </CascadingValue> </CascadingValue>
CascadingParameter
attribute and pass the name you have specified at the previous step.[CascadingParameter(Name = "GrandParentFirstPerson")] public Person Received1stPerson { get; set; } [CascadingParameter(Name = "GrandParentSecondPerson")] public Person Received2ndPerson { get; set; }
You don't need to declare all the passed parameters in your descendant component, just declare what you need to use. For example, you can remove Received1stPerson
from the descendant component.
Parameter
Parameter
component is a built-in attribute that allows you to pass data from the parent component to the child component.
This approach does not support feedback from the child component. Any changes from the child component will not notify the parent component. However, any changes that come from the parent component will notify the child component. The child component receives the property as a reference to the parent's property, if you make a change in the child component, the parent's property will have a new value but the UI will not show the old value until you call StateHasChanged()
in your parent component.
Parameter
attribute.[Parameter] public Person ReceivingPerson { get; set; }
<div>Received Value: @ReceivingPerson.Name</div>
public Person PersonInParentComponent { get; set; } = new() { Name = "Person from the parent component." };
PersonInParentComponent
to the child component.<PChild ReceivingPerson="PersonInParentComponent"></PChild>
You can see the following result:
A common mistake when using the Parameter
is to change the reference of the child property. This will break the bond between the child component and parent component. Normally, when the change happens inside the child component, the parent component gets the new value but the UI is not updated. Try to avoid changing the reference of the child property.
However, changing the reference when declaring the property is totally fine. The following code is totally fine.
[Parameter] public Person ReceivingPerson { get; set; } = new() { Name = "Default name" };
Parameter
and delegate
This technique is preferred when you want to pass a parameter to a child component, process that parameter and then notify back to the parent component. The following image describes this technique:
Parameter
attribute.[Parameter] public Person ReceivingPerson { get; set; } [Parameter] public Action OnReceivingPersonChanged { get; set; }
In this example, ReceivingPerson
is the data that the parent component will pass and OnReceivingPersonChanged
is the delegate to notify data changes.
You can use any kind of delegate (
Action
,Func
,Predicate
) as the parameter.
OnReceivingPersonChanged
delegate when the data is changed to notify the parent to update its UI.<form> <label> Receiving Person: <input type="text" value="@ReceivingPerson.Name" @oninput="ChangeOnReceivingPerson" /> </label> </form> @code { ... private void ChangeOnReceivingPerson(ChangeEventArgs eventArgs) { ReceivingPerson.Name = eventArgs.Value as string; OnReceivingPersonChanged?.Invoke(); } }
public Person Person { get; set; } = new() { Name = "Person from parent" }; public void OnPersonChanged() { StateHasChanged(); }
<PDChild ReceivingPerson="Person" OnReceivingPersonChanged="OnPersonChanged"></PDChild>
And you are done.
The model contains properties, you can create an event and a delegate for each property or you can just implement the INotifyPropertyChanged
interface. The difference is that by using INotifyPropertyChanged
interface, you will have one event per class instead of one event for each property. In this section example, we will choose the first approach.
public class Game { private string _name; public string Name { get => _name; set { _name = value; OnNameChanged?.Invoke(this, value); } } public delegate void OnNameChangedHandler(object sender, string name); public event OnNameChangedHandler OnNameChanged; }
The transfer service is a scoped service that will hold the data to transfer between the components.
public class GameTransferService { public Game Game { get; set; } = new(); }
After creating the service, head to the Program.cs
to register the transfer service as a scoped service.
public static async Task Main(string[] args) { ... builder.Services.AddScoped<GameTransferService>(); ... }
To use the transfer service in a component, you need to have @inject
directive to inject the transfer service and have the @implements
to implement IDisposable
interface. In the initialized phase, you will subscribe to the event of the Game
model. You will use the property in the transfer service directly instead of creating a property in your component.
@inject GameTransferService GameTransferService @implements IDisposable <div>Shared Game Name: @GameTransferService.Game.Name</div> @code { protected override void OnInitialized() { GameTransferService.Game.OnNameChanged += OnGameNameChanged; } private void OnPersonChanged(object sender, Person person) { StateHasChanged(); } private void OnGameNameChanged(object sender, string name) { StateHasChanged(); } public void Dispose() { GameTransferService.Game.OnNameChanged -= OnGameNameChanged; } }
Always remember to unsubscribe from the event using the
Dispose
method ofIDisposable
interface.
And now with the help of transfer service, you can share the data between components easily.