Search code examples
asp.net-coreblazorblazor-server-side

How can I force a re-render of a component in MainLayout?


I know one answer is don't use MainLayout for this one page, but I prefer to not do that as we then have to keep 2 layout pages in sync on every change in them - and that's just asking for divergence.

Here's the problem. This is my MainLayout (simplified)

<ErrorBoundary>
    <ChildContent>
        <main class="@pageClass">
            <PopupMessageBox>
                <header class="main-header">
                    <PrimaryNav UserAvatarUrl="@UserAvatarUrl" IsManagerOrSysAdmin="@(Principal.IsManagerOrSysAdmin())" />
                </header>
                <div class="main-content">
                    @Body
                </div>
    </ChildContent>

For one @Body page we need to have <PrimaryNav> have a slightly different menu selection. In <PrimaryNav> we can determine what page is in @Body, but having the page change does not cause <PrimaryNav> to re-render.

Is there a way to force it to re-render on every page change?


Solution

  • @body is the current page component. It's available in Routes in the Router as routeData.

    Here I'm simply cascading the Type.

    <Router AppAssembly="typeof(Program).Assembly">
        <Found Context="routeData">
            <CascadingValue Name="PageComponent" Value="routeData.PageType">
                <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
                <FocusOnNavigate RouteData="routeData" Selector="h1" />
            </CascadingValue>
        </Found>
    </Router>
    

    The value will be refreshed with a render cascade on each navigation.

    You can then capture the cascade in NavMenu. Here's a demo that only shows the Weather nav link when you are on Home.

    <div class="top-row ps-3 navbar navbar-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="">BlazorApp1</a>
        </div>
    </div>
    
    <input type="checkbox" title="Navigation menu" class="navbar-toggler" />
    
    <div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
        <nav class="flex-column">
            <div class="nav-item px-3">
                <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                    <span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
                </NavLink>
            </div>
    
            <div class="nav-item px-3">
                <NavLink class="nav-link" href="counter">
                    <span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
                </NavLink>
            </div>
    
    @if(_isHome)
            {
            <div class="nav-item px-3">
                <NavLink class="nav-link" href="weather">
                    <span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
                </NavLink>
            </div>
            }
        </nav>
    </div>
    
    @code {
        [CascadingParameter(Name = "PageComponent")] private Type? RouteType { get; set; }
    
        private bool _isHome => RouteType == typeof(Home);
    }