Sharing a Single Model Across Multiple Child Components in Blazor WebAssembly

There are several effective ways to share a single model (data object) between multiple child components in Blazor WebAssembly. Here are the best approaches:

1. Cascading Parameters (Best for hierarchical components)
<!-- ParentComponent.razor -->
@page "/parent"

<CascadingValue Value="@SharedModel">
    <ChildComponent1 />
    <ChildComponent2 />
</CascadingValue>

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [CascadingParameter]
    public MyModel SharedModel { get; set; }
}

2. Component Parameters (Best for direct parent-child relationships)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" />
<ChildComponent2 Model="@SharedModel" />

@code {
    private MyModel SharedModel { get; set; } = new MyModel();
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

<!-- ChildComponent2.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
}

3. State Management Service (Best for app-wide sharing)

// SharedModelService.cs
public class SharedModelService
{
    private MyModel _model = new();
    
    public MyModel Model 
    {
        get => _model;
        set
        {
            _model = value;
            NotifyStateChanged();
        }
    }
    
    public event Action OnChange;
    
    private void NotifyStateChanged() => OnChange?.Invoke();
}

Register the service in Program.cs:

builder.Services.AddSingleton<SharedModelService>();

Use in components:

@inject SharedModelService ModelService

@code {
    protected override void OnInitialized()
    {
        ModelService.OnChange += StateHasChanged;
    }
    
    private void UpdateModel()
    {
        ModelService.Model.Property = "New Value";
    }
}

4. EventCallback Pattern (For parent-child communication)

<!-- ParentComponent.razor -->
@page "/parent"

<ChildComponent1 Model="@SharedModel" ModelChanged="@HandleModelChanged" />
<ChildComponent2 Model="@SharedModel" ModelChanged="@HandleModelChanged" />

@code {
    private MyModel SharedModel { get; set; } = new();
    
    private void HandleModelChanged(MyModel updatedModel)
    {
        SharedModel = updatedModel;
        StateHasChanged(); // Refresh all components
    }
}

<!-- ChildComponent1.razor -->
@code {
    [Parameter]
    public MyModel Model { get; set; }
    
    [Parameter]
    public EventCallback<MyModel> ModelChanged { get; set; }
    
    private async Task UpdateModel()
    {
        Model.Property = "New Value";
        await ModelChanged.InvokeAsync(Model);
    }
}

5. Fluxor/Redux Pattern (For complex state management)

// Install package
dotnet add package Fluxor.Blazor.Web

// Define state
public record MyModelState
{
    public MyModel Model { get; init; } = new();
}

// Define actions
public record UpdateModelAction(MyModel Model);

// Create reducer
public static class Reducers
{
    [ReducerMethod]
    public static MyModelState ReduceUpdateModelAction(MyModelState state, UpdateModelAction action)
        => state with { Model = action.Model };
}

Use in components:

@inject IState<MyModelState> ModelState
@inject IDispatcher Dispatcher

<p>@ModelState.Value.Model.Property</p>

<button @onclick="UpdateModel">Update</button>

@code {
    private void UpdateModel()
    {
        var updatedModel = ModelState.Value.Model with { Property = "New Value" };
        Dispatcher.Dispatch(new UpdateModelAction(updatedModel));
    }
}

  1. For simple parent-child relationships: Use Component Parameters
  2. For deep component trees: Use Cascading Parameters
  3. For app-wide state: Use State Management Service or Fluxor
  4. For complex applications: Consider Fluxor/Redux pattern
  5. Immutable models: When sharing models, consider making them immutable or implementing proper change notifications

Performance Considerations

  • Avoid excessive re-rendering by implementing ShouldRender
  • Use [Parameter] public MyModel Model { get; set; } carefully as it can cause unnecessary renders
  • For large models, consider using view models or DTOs instead of full domain models

Show/Hide element in Blazor WebAssembly

Blazor WebAssembly doesn’t allow direct manipulation of DOM. Here is how to show/hide DOM element without using Javascript Interop;

The hidden html attribute also works to hide an element.

<p hidden>This paragraph should be hidden.</p>

To bind to Model:

 <p hidden="@HideLabel">I am Hidden When HideLabel == true</p>

 <p hidden="@(!HideLabel)">I am Hidden when Hidelabel == false</p>
    
 <button @onclick="@Toggle">Show/Hide</button>

 @code {
      private bool HideLabel { get; set; } = false;
      private void Toggle()
      {
         HideLabel =   !HideLabel;
      }      
 } 

Edit: You can also use a CSS class to hide/show an element:

<div class="font-italic @(HideLabel ? "d-none" : "d-show")">
   I am Hidden When HideLabel == true
</div>

Reference

https://stackoverflow.com/questions/63693734/how-to-show-hide-an-element-in-real-time-blazor

StateHasChanged() vs InvokeAsync(StateHasChanged) in Blazor WebAssembly

I have tried calling StateHasChanged() – instead of InvokeAsync(StateHasChanged) – in a Timer’s Elapsed event, and it works as expected

That must have been on WebAssembly. When you try that on Blazor Serverside I would expect an exception. StateHasChanged() checks if it runs on the right thread.

The core issue is that the rendering and calling StateHasChanged both have to happen on the main (UI) thread. Actually that is “on the SynchronizationContext” but for all intents and purposes you can think of it as being a single-thread, just as in WinForms, WPF and other GUIs). The virtual DOM is not thread-safe.

The main Blazor life-cycle events (OnInit, AfterRender, ButtonClick) are all executed on that special thread so in the rare case that you need StateHasChanged() there it can be called without InvokeAsync().

A Timer is different, it is an “external event” so you can’t be sure it will execute on the correct thread. InvokeAsync() delegates the work to Blazor’s SynchronizationContext that will ensure it does run on the main thread.

But Blazor WebAssembly only has 1 thread so for the time being external events always run on the main thread too. That means that when you get this Invoke pattern wrong you won’t notice anything. Until one day, when Blazor Wasm finally gets real threads, your code will fail. As is the case with your Timer experiment.

What is “Blazor’s synchronization context”?

In .net a synchronization context determines what happens with (after) await. Different platforms have different settings, the Blazor synccontext is a lot like that of WinForms and WPF. Mainly, the default is .ConfigureAwait(true): resume on the same thread/context.

I sometimes see .ConfigureAwait(false) in toplevel Blazor Wasm code. That too will blow up when we get real threads there. It is fine to use in services called from Blazor, but not for the toplevel methods.

And finally, await InvokeAsync(StateHasChanged) or await InvokeAsync(() => StateHasChanged()) is just about lambda’s in C#, nothing to do with Blazor. The first short form is a little more efficient.

I also sometimes see InvokeAsync() called without await

That will work. It probably is better than the other option: making the calling method (like the Timer’s OnTick) an async void. So do use this from a synchronous code path.

Reference

https://stackoverflow.com/questions/65230621/statehaschanged-vs-invokeasyncstatehaschanged-in-blazor