Reputation: 12694
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
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
Reputation: 412
There are a few ways to do it:
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
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
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
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
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