Arad
Arad

Reputation: 12694

How to pass a value from a page to the layout in Blazor?

I have a layout (MainLayout.razor), and it has a flag called ShowFooter. On some pages, I want to be able to set that flag to true, and on some others to false.

I haven't been able to find any clear instructions on how a page (i.e. a component with a route) can communicate with its layout. How could/should this be done in Blazor?

Note: You might suggest creating 2 layouts, one with and one without the footer, but that wouldn't really solve my problem, I want to be able to show and hide the footer at different times on the same page. Plus, this is just one scenario where there is a need to communicate between the layout and the page. There are also countless others, to which such workarounds might not even be applicable.

Upvotes: 24

Views: 23043

Answers (4)

Emran Hussain
Emran Hussain

Reputation: 12069

All solutions (Notify changes, Cascading Parameter...) seem ugly, unfortunately. Needless to say, we don't know what bugs will show up.

Rather, I think the clean way is to use SectionOutlet. Basically, you want to alter the UI of the layout, right? So, declare a SectionOutlet in the Layout page and control the content of that outlet from the child page.

For instance, I created the Script Outlet in my layout page like this.

<SectionOutlet SectionId="Sections.Scripts" />

@code
{
    internal static class Sections
    {
        internal static readonly object Scripts = new();
    }
}

Then, on my page,

<SectionContent SectionId="App.Sections.Scripts">
    // my script content
</SectionContent>

I am showing an example from Blazor United (.NET 8).

Upvotes: 2

diegorodny
diegorodny

Reputation: 412

There are a few ways to do it:

  1. The ugliest: If you have two templates you can simply select the template you want to use with the following on the top of the page/component:

    @layout NoFooterLayoutName

  2. Use cascading value in the template ( What I would recommend for your scenerio):

<CascadingValue Value="Footer">
    <Child />
</CascadingValue>

Example fiddle: https://blazorfiddle.com/s/05spcuyk

And Doc: https://learn.microsoft.com/en-us/aspnet/core/blazor/components/cascading-values-and-parameters?view=aspnetcore-5.0

  1. Create a State service and add it to startup as scoped. The state service with footer bool variable, and can then be injected into pages/components and variable used:

In startup.cs ConfigureService method:

services.AddScoped<AppState>();

Create AppState.cs class somewhere in your project (ideally a Services folder):

public class AppState 
{
   public bool ShowFooter { get; set; }
   public event Action StateChanged;
   private void NotifyStateChanged() => StateChanged?.Invoke();
}

Then inject it in your page/components so you can change the ShowFooter Element and in your template you can create event handler (not sure if necessary) for that triggers StateHasChanged():

@inject AppState _AppState;
@implements IDisposable
.
.
.
@code{
    protected override void OnInitialized()
    {
        _appState.StateChanged += StateChanged;
    }

    public void StateChanged()
    {
        StateHasChanged();
    }

    public void Dispose()
    {
        _appState.StateChanged -= StateChanged;
    }
}

Upvotes: 9

enet
enet

Reputation: 45596

The simplest way to do that is to define a public Boolean property named ShowFooter in the MainLaout component, as follows:

public bool ShowFooter {get; set;}

And to cascade a reference to MainLaout component to given components, by wrapping the markup within a CascadingValue component whose Value attribute is set to this, like this:

@inherits LayoutComponentBase


<CascadingValue Value="this">
     <div class="sidebar">
        <NavMenu />
    </div>
    <div class="main">
         <div class="content px-4">
            @Body
        </div>
    </div>
</CascadingValue>
@code
{
    public bool ShowFooter {get; set;}

     protected override void OnInitialized()
    {
      // Put here code that checks the value of ShowFooter and acts in 
      // accordance with your dear wishes

     }
}

Usage in Index.razor

@code{
     // Gets a reference to the MainLayout component
    [CascadingParameter]
    public MainLayout Layout { get; set; } 

    protected override void OnInitialized()
    {
        Layout.ShowFooter= true;
    
    }
}

Upvotes: 45

M. Folmer
M. Folmer

Reputation: 300

You could use an notify service and inject this into the components.

public class NotifyService 
{
     public event Func<bool, Task> Notify;

     public async Task Notify(bool value) 
     {
        if (Notify is object)
        {
            await Notify.Invoke(value);
        }
     }
}

And then register this as a singleton (or scoped if server-side) in the DI container and inject this into your components.

Upvotes: 0

Related Questions