Blazor Client: multiple root components

In Blazor Client (i.e. WASM) it's possible to have multiple root components on the same page.
This is possible with the WebAssemblyHostBuilder.RootComponents collection.
Let's play a bit and try it.
First, from Visual Studio, create a new Blazor client application:
- choose the Blazor WebAssembly template:
- no ASP.NET Core backend is necessary:
Now, let's modify a couple of CSS to allow the visualization of multiple root components on the page:
- in Shared/MainLayout.razor.css, change the .sidebar.height from 100vh to 50vh:
.sidebar { width: 250px; height: 50vh; position: sticky; top: 0; }
- in Shared/NavMenu.razor.css, change the .nav-scrollable.height from calc(100vh - 3.5rem) to calc(50vh - 3.5rem):
.nav-scrollable { /* Allow sidebar to scroll for tall menus */ height: calc(50vh - 3.5rem); overflow-y: auto; }
- running the app, we should see the usual UI using only the top-half of the page:
Let's add now the new root component to the solution:
- start adding a SecondApp.razor file and pasting the following content:
@using BlazorApp1.Pages <h3>SecondApp</h3> <Counter />
- add a reference to component to wwwroot/index.html:
... <body> <div id="app">...</div> <div> SOME OTHER HTML </div> <div id="secondApp"> <div>Loading second app...</div> </div> <div id="blazor-error-ui">...</div> <script src="_framework/blazor.webassembly.js"></script> </body> </html>
- in Program.cs add a binding between the SecondApp root component and the secondApp style:
var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<SecondApp>("#secondApp"); builder.RootComponents.Add<HeadOutlet>("head::after");
- running the app, we will see the new app root:
Now: the two counter components are two different instances. Let's suppose instead that we want to share state:
- we could start modifying Pages/Counter.razor, making the currentCount variable static:
private static int currentCount = 0;
- the problem is that clicking on the first Counter component, the static variable is incremented, but the second instance of the Counter component is not automatically updated. I have followed this blog post: 3 Ways to Communicate Between Components in Blazor.
- add an AppState.cs file, with the following content:
public class AppState { private int _currentCount = 0; public int CurrentCount { get => _currentCount; set { _currentCount = value; NotifyStateChanged(); } } public event Action? OnChange; private void NotifyStateChanged() => OnChange?.Invoke(); }
- in Program.cs add a reference to this singleton:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddSingleton<AppState>();
- modify Pages/Counter.razor injecting the AppState service, un-subscribing to the notification events, and replacing references to the Counter property:
@page "/counter" @inject AppState AppState <PageTitle>Counter</PageTitle> <h1>Counter</h1> <p role="status">Current count: @AppState.CurrentCount</p> <button class="btn btn-primary" @onclick="IncrementCount">Click me</button> @code { //private static int currentCount = 0; protected override void OnInitialized() { AppState.OnChange += StateHasChanged; } public void Dispose() { AppState.OnChange -= StateHasChanged; } private void IncrementCount() { AppState.CurrentCount++; } }
- running the app now, the state of the two instances will be synchronized now: