Reputation: 9295
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
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
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
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