Call method in MainLayout from a page component in Blazor
Asked Answered
N

4

19

I have a Blazor app with a MainLayout page, which has a @Body to load the actual page content.

In my case Index.razor is loaded inside the MainLayout page.

Is there a way to call a method from the child page (Index.razor) that lives in the parent page; MainLayout.razor?

Example:

MainLayout.razor

<div class="content">
<ul class="menu">
  <li>menu item 1</li>
</ul>

@Body

</div>

@code
{
    public async Task Test()
    {
        await JsRuntime.InvokeAsync<object>("console.log", "test parent");
    }
}

Index.razor

<h1>This is the index page</h1>
<button @onclick="(async () => await btn_clicked())">Call parent method</button>

@code
{
    // Call method in MainLayout
    public async Task btn_clicked()
    {
        await parent.Test();
    }
}
Nipping answered 26/9, 2019 at 18:51 Comment(2)
you don't need the async lamda any more. You can just use @onclick=btn_clickedBlocky
Another approach may be interesting: #64300030Clarey
B
18

You can do this with the combination of cascading values and EventCallback.

First, create an event call back for your Test. To do so, add the following code in your MainLayout.razor.

EventCallback btn_clicked => EventCallback.Factory.Create(this, Test);

Or, to make sure you only create this object once, you can use the following:

EventCallback _btn_clicked = EventCallback.Empty;
EventCallback btn_clicked  {
    get {
        if (_btn_clicked.Equals(EventCallback.Empty))
            _btn_clicked = EventCallback.Factory.Create(this, Test);
        return _btn_clicked;
    }
}

Next, make sure you cascade this event callback down your body.

<CascadingValue Value=btn_clicked >
    @Body
</CascadingValue>

Now, in your Index.razor code, set the property:

[CascadingParameter]
public EventCallback btn_clicked { get; set; }
Blocky answered 27/9, 2019 at 4:41 Comment(1)
Great, just missing the part await btn_clicked.InvokeAsync(); to call the EventCallback set up in MainLayout.Azilian
C
9

Thanks. The first answer was exactly what I needed, though I needed to pass in a parameter, so it's the same as laid out, except:

In MainLayout.razor:

public EventCallback<string> EventName => EventCallback.Factory.Create<string>(this, MethodName);

In Index.razor:

[CascadingParameter]
protected EventCallback<string> EventName { get; set; }

To call the method:

EventName.InvokeAsync("Name");
Cotidal answered 13/11, 2019 at 6:48 Comment(1)
This worked, whereas the accepted answer given didn't work when I adapted it to take a parameter.Salami
T
4

The callback methods work, but are still fairly limited, the approach I took was to use "Mesaging Center" that has been cloned from Xamarin.Forms...

https://github.com/aksoftware98/blazor-utilities

https://www.nuget.org/packages/AKSoftware.Blazor.Utilities/

https://youtu.be/HdEJ4GD9hwM

It's very simple, flexible, and behaves much more like a pubsub messaging system across components interanally which has many benefits.

As a result you can "broadcast" events to individual or multiple components regardless of nesting state and location - super flexible and useful. Taken from their docs...

Place a using in your _imports.razor

@using AKSoftware.Blazor.Utilities

Then put a subscriber in the MainLayout.razor

public void SubscribeToMessage()
{
    MessagingCenter.Subscribe<Component1, string>(this, "greeting_message", 
(sender, value) => 
    {
    // Do actions against the value 
    // If the value is updating the component make sure to call 
    string greeting = $"Welcome {value}";
    StateHasChanged(); // To update the state of the component 
    });
}

And a publisher in any of the componets you want

public void SendMessage()
{
 string valueToSend = "Hi from Component 1";
 MessagingCenter.Send(this, "greeting_message", valueToSend);
}

You can put subscribers and senders in any combination of components. I've been making a project where realtime date was updating multiple componenets at the same time, this was a great solution that allowed me to put all my hub connetion logic in MainLayout.razor, and then call and respond to that across all compoents as needed, prvented multiple hub connections being created, and works well in tandem with cascading states.

Titanesque answered 1/5, 2022 at 14:18 Comment(3)
I used this package in my latest project and it seems to work pretty well. It's definitely better than trying to reinvent the wheel on a pub/sub model within Blazor. Honestly, I don't know why MSFT doesn't include something like this natively in the framework.Bickering
is there no need to unsubscribe? it is properly disposed. will be there be any memory leaks?Rapacious
will this still work in .net core 6.0 or 7.0?Rapacious
P
2

You can add the mainlayout itself as a cascading parameter:

@inherits LayoutComponentBase
<CascadingValue Value="this">
    @Body
</CascadingValue>

Then in your component:

[CascadingParameter] public <your-namespace>.MainLayout Layout { get; set; }

Now you can call your mainlayout function from your component:

public async Task btn_clicked()
{
    await Layout.Test();
}

Would be interested on any feedback on this approach.

Pone answered 22/7, 2023 at 4:8 Comment(1)
Yes, this will work. You will have to add one or more StateHasChanged() calls but the option to chose where (not) could be a benefit. But architecturally it makes for a very tight coupling. Instead of cascading a value you could inject a service.Zebec

© 2022 - 2024 — McMap. All rights reserved.