I have a blazor page with different components.
Component structure is the following:
page component1 component11 component2
When event happens on component11, component2 should update it's state.
On the same component I use OnStateChange
event.
For update state of parent component, I can use EventCallback too.
But here I need to pass event two levels up and then down to other component.
Looks a little bit complex. Maybe exist some easy way to do this.
Can event bubbling work automatically? Maybe some application settings?
You need to use some form of notification pattern.
Here's a simple provider. Note it requires the WeatherForecastService
from DI to demo injecting services.
public class TimeStampProvider
{
private readonly WeatherForecastService _weatherForecastService;
public TimeStampProvider(WeatherForecastService weatherForecastService)
=> _weatherForecastService = weatherForecastService;
public event EventHandler? TimeStampChanged;
public string TimeStamp = string.Empty;
public void NotifyTimeStampChanged(string timeStamp)
{
TimeStamp = timeStamp;
this.TimeStampChanged?.Invoke(this, EventArgs.Empty);
}
}
You can either register this as a scoped DI service or, if you're using it within the scope of a page create an instance in the page component and cascade it.
Next a few components that use TimeStampProvider
.
TimeStampSetter
<div class="m-2 p-2">
<button class="btn btn-primary" @onclick=this.Clicked>Update Time Stamp</button>
</div>
@code {
[CascadingParameter] private TimeStampProvider myProvider { get; set; } = default!;
protected override void OnInitialized()
{
ArgumentNullException.ThrowIfNull(this.myProvider);
}
private void Clicked()
=> myProvider.NotifyTimeStampChanged(DateTime.Now.ToLongTimeString());
}
TimeStampViewer
@implements IDisposable
<div class="bg-dark text-white m-2 p-2">
<pre>Stamp : @myProvider.TimeStamp </pre>
</div>
@code {
[CascadingParameter] private TimeStampProvider myProvider { get; set; } = default!;
protected override void OnInitialized()
{
ArgumentNullException.ThrowIfNull(this.myProvider);
this.myProvider.TimeStampChanged += this.OnChanged;
}
private void OnChanged(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
public void Dispose()
=> this.myProvider.TimeStampChanged -= this.OnChanged;
}
MyComponent
<div class="bg-secondary m-3 p-2">
<h3>MyComponent</h3>
<TimeStampViewer />
<TimeStampSetter />
</div>
@code {
}
And final the page, with lots of sub-components to keep in sync. Note the fixed
cascading value.
@page "/"
@inject IServiceProvider ServiceProvider
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
<CascadingValue Value=_myStateProvider IsFixed>
<MyComponent />
<TimeStampSetter />
<TimeStampViewer />
<TimeStampViewer />
<TimeStampViewer />
<TimeStampViewer />
<TimeStampViewer />
<TimeStampSetter />
</CascadingValue>
@code {
private TimeStampProvider _myStateProvider = default!;
protected override void OnInitialized()
{
// creates a new instance of TimeStampProvider in the context of the DI service container
// which populates any DI services in the object
_myStateProvider = ActivatorUtilities.CreateInstance<TimeStampProvider>(ServiceProvider);
ArgumentNullException.ThrowIfNull(_myStateProvider);
}
}
Test it and you'll see everything stays in sync!