Ole Albers
Ole Albers

Reputation: 9295

Exchange Data between Page and NavMenu in Blazor

I have a standard - blazor - project which has the following components:

-> MainLayout.razor
-> NavMenu.razor
-> Pages/Index.razor
-> Pages/Sub1.razor

The MainLayout looks like this:

<div class="sidebar">
    <NavMenu />
</div>
<div>@Body</div>

Now I want to exchange Data between my pages (index.razor, sub1.razor) and the navmenu so I could add something like this in navmenu:

<div><p>You are now on Page: @CurrentPageName</p></div>

How can I set (navMenu).CurrentPageName directly from within my page? I would expect that using a static class for that is not really a good option.

Upvotes: 7

Views: 6108

Answers (3)

Quango
Quango

Reputation: 13458

A better scoped-service implementation:

 public class CurrentPage
    {
        public string CurrentPageName { get; private set; }

        public void SetCurrentPageName(string name)
        {
            if (!string.Equals(CurrentPageName, name)) 
            {
                CurrentPageName = name;
                NotifyStateChanged();
            }
        }

        public event Action OnChange; // event raised when changed

        private void NotifyStateChanged() => OnChange?.Invoke();
    }

We are not passing round dictionaries of objects, we have a simple service that does one thing. The only way to change the page is call SetCurrentPageName, which raises an event to let consumers know the name changed. This is required between un-nested components as updates would not otherwise propagate across.

We also need to register the service (as scoped since the current page is session-specific) in startup:

            services.AddScoped<CurrentPage>();

We will inject in Index.razor, and use it:

@page "/"
@inject CurrentPage currentPage

<h1>Hello, world!</h1>

Welcome to your new app.
<button @onclick="ChangeName">Set Page Name</button>

<SurveyPrompt Title="How is Blazor working for you?" />
@code
{

    protected override void OnInitialized()
    {
        currentPage.SetCurrentPageName("The Home Page");
        base.OnInitialized();
    }

    void ChangeName() => currentPage.SetCurrentPageName("Name changed");
}

and finally at the top of NavMenu.razor:

@inject CurrentPage currentPage

and further down..


    <p>The current page is @currentPage.CurrentPageName</p>

@code {
    protected override void OnInitialized()
    {
        // if the OnChange event is raised, refresh this view
        currentPage.OnChange += () => StateHasChanged();

        base.OnInitialized();
    }

This state class doesn't know anything about how it's used and there are no objects or references being passed about.

[EDIT] I decided that the inject/override pattern for setting the page name is rather unBlazor, so I also wrote a component to simplify this - PageName.razor:

@inject CurrentPage currentPage;

@code {
    [Parameter]
    public string Name { get; set; }

    protected override void OnParametersSet()
    {
        currentPage.SetCurrentPageName(Name);
    }
}

Now any page wanting to set the title can do this:

@page "/fetchdata"
@inject HttpClient Http
<PageName Name="Weather forecast page!" />

The whole consumer is now a component :)

Upvotes: 11

BlazorPlus
BlazorPlus

Reputation: 193

Use scoped service to share data / objects

In Startup.cs

you can add your own 'Scoped' service :

public void ConfigureServices(IServiceCollection services)
{
   ......
   services.AddScoped<MyScopeService>();
}
public class MyScopeService : Dictionary<string,object>
{
} 

and in NavMenu.razor

@inject MyScopeService mss

@code{
    string _title;
    public void UpdateTitle(string title)
    {
        _title = title;
        StateHasChanged();
    }
}

@{
    mss["NavMenu"] = this;
}
<div>@_title</div>

In target page :


@inject MyScopeService mss
@code{
    void SetTitle()
    {
        NavMenu menu = (NavMenu)mss["NavMenu"];
        menu.UpdateTitle("Hello this is page 1");
    }
}

<button @onclick="SetTitle">SetTitle</button>

Upvotes: -2

Quango
Quango

Reputation: 13458

There are three main ways to communicate between components in Blazor. Chris Sainty has a good article outlining these: https://chrissainty.com/3-ways-to-communicate-between-components-in-blazor/

In this scenario a cascading value or a state container are probably the best options. A cascading value would require a top-level component to contain the value, e.g. something that encapsulates both the <NavMenu> and the @Body:

@inherits LayoutComponentBase
<MenuState>
<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>
</MenuState>

Another approach is to use an injectable service that provides a State service, which you inject into both the <NavMenu> and the page components.

Upvotes: 3

Related Questions