7

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.

3 Answers 3

11

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 :)

Sign up to request clarification or add additional context in comments.

1 Comment

Shouldn't the first solution implement IDisposal and release the OnChange registration on disposal?
3

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.

5 Comments

How can you use cascading value from "a top-level component" to 'low-level' (children) components ? The flow of data is not downstream, but upstream. He wants to issue a message, say, in FetchData page, and see it in the LayoutComponent ( navmenu )
i was able to use the "state container" - way described in the url
@enet Components can pass parameters down to child components via Cascading values. There is nothing that says the value has to be a value object, it can be a class that provides the necessary functionality, like a state container. This is an alternative to using DI. Both methods are valid
@ Quango, what you say is true, but has no bearing on the issue I raise in my comment: He wants to issue a message, say, in FetchData page, and see it in the LayoutComponent ( navmenu )...How do you do that.
@Ole Albers, Please post your working solution, so we can all profit from your good question.
-2

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> 

1 Comment

Thanks for attempting an answer but it's not really very good code. I was going to explain all the bad points but there are only 500 chars in a comment and it would take a lot more to cover all the issues here. I'll post a solution using the the same concept of a scoped service to help you.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.