Blazor Client: multiple root components

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:
    Blazor1
  • no ASP.NET Core backend is necessary:
    Blazor2

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:
    Blazor_3

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:
    Blazor_4

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:
    Blazor_5