英文:
Subscribe to an EventCallback from a Layout when using Router
问题
具有Blazor WebAssembly的工作知识,但不是太高级。
我对来自子组件的EventCallback很熟悉。但我在任何地方都没有看到如何订阅来自Blazor路由器渲染的页面的EventCallback的文档。
使用情况是,我有一个带有@Main占位符的布局页面。我想订阅布局中某个特定页面的EventCallback,但无法直接引用该页面的EventCallback,因为@Main可以是任何页面,而且它没有类类型。
具体来说,假设我有几个页面允许用户进行选择。进一步假设它们都共享相同的布局页面。请参考下面的布局页面代码。我有一个级联参数,用于在所有页面和组件之间共享选择。
如何订阅从布局页面插入到@Main中的页面的EventCallbacks?
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
@Body
<DetailView />
</CascadingValue>
</main>
英文:
Have working knowledge of Blazor WebAssembly but not too advanced.
I'm good with EventCallback from child components. What I haven't seen anywhere documented is how to subscribe to EventCallback from pages rendered from Blazors Router.
The use case is i've got a layout page with the placeholder for @Main. I'd like to subscribe to an EventCallback for a certain page within the layout but there's no way to reference that page's EventCallback directly since @Main could be any page and also it has no class type.
Specifically lets say i have several pages that allow a user to make a selection. Further assume they all share the same layout page. See the code below for the layout page code. I've got a cascading parameter to share the selections across all the pages and components.
How would I subscribe to the EventCallbacks in the pages that would be inserted into @Main from the layout page?
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
@Body
<DetailView />
</CascadingValue>
</main>
答案1
得分: 1
这是一个使用Scoped
服务来维护数据的Blazor通知模式实现。我使用了一个简单的国家选择来演示原理。在上下文之外,selectionIds
没有太多意义。
第一步是将数据和状态从组件中分离出来:
public class CountryData
{
public string? Country { get; private set; }
public event EventHandler? CountryChanged;
public void SetCountry(string? value )
{
this.Country = value;
this.CountryChanged?.Invoke( this, EventArgs.Empty );
}
}
注册为Scoped服务:
builder.Services.AddScoped<CountryData>();
一个用于显示国家的CountryDisplay
组件。
@implements IDisposable
@inject CountryData CountryData
<div class="alert alert-primary m-2">@this.CountryData.Country</div>
@code {
protected override void OnInitialized()
=> this.CountryData.CountryChanged += OnCountryChanged;
private void OnCountryChanged(object? sender, EventArgs e)
=> this.StateHasChanged();
public void Dispose()
=> this.CountryData.CountryChanged += OnCountryChanged;
}
以及一个用于修改它的CountrySelector
组件:
@inject CountryData CountryData
<div class="bg-light m-2 pt-2 p-4 border border-1">
<h3>Country Selector</h3>
<InputSelect class="form-select" @bind-Value:get="this.CountryData.Country" @bind-Value:set="this.CountryData.SetCountry">
@if (this.CountryData.Country is null)
{
<option selected disabled value=""> -- Select a Country -- </option>
}
@foreach (var country in CountryProvider.CountryList)
{
<option value="@country">@country</option>
}
</InputSelect>
</div>
@code {
// 注意 - @bind-Value:get 可能会出错。这是一个SDK问题。代码将会编译。
}
CountryLayout
:
@inherits LayoutComponentBase
<PageTitle>SO76864163</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
<CountryDisplay />
@Body
</article>
</main>
</div>
@code {
private CountryData _countryData = new();
}
以及Index
:
@page "/"
@layout CountryLayout
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<CountrySelector />
和Counter
:
@page "/counter"
@layout CountryLayout
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<CountrySelector />
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
英文:
Here's a Blazor Notification Pattern implementation that uses a Scoped
service to maintain the data. I've used a simple country select to demonstrate the principles. selectionIds
doesn't mean a lot out of context.
The first step is to separate the data and state from the component:
public class CountryData
{
public string? Country { get; private set; }
public event EventHandler? CountryChanged;
public void SetCountry(string? value )
{
this.Country = value;
this.CountryChanged?.Invoke( this, EventArgs.Empty );
}
}
Registered as a Scoped Service
builder.Services.AddScoped<CountryData>();
A CountryDisplay
component to dispay the country.
@implements IDisposable
@inject CountryData CountryData
<div class="alert alert-primary m-2">@this.CountryData.Country</div>
@code {
protected override void OnInitialized()
=> this.CountryData.CountryChanged += OnCountryChanged;
private void OnCountryChanged(object? sender, EventArgs e)
=> this.StateHasChanged();
public void Dispose()
=> this.CountryData.CountryChanged += OnCountryChanged;
}
And a CountrySelector
component to modify it:
@inject CountryData CountryData
<div class="bg-light m-2 pt-2 p-4 border border-1">
<h3>Country Selector</h3>
<InputSelect class="form-select" @bind-Value:get="this.CountryData.Country" @bind-Value:set="this.CountryData.SetCountry">
@if (this.CountryData.Country is null)
{
<option selected disabled value=""> -- Select a Country -- </option>
}
@foreach (var country in CountryProvider.CountryList)
{
<option value="@country">@country</option>
}
</InputSelect>
</div>
@code {
// NOTE - @bind-Value:get may give you an error. It's a SDK issue. The code will compile.
}
CountryLayout
@inherits LayoutComponentBase
<PageTitle>SO76864163</PageTitle>
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
<article class="content px-4">
<CountryDisplay />
@Body
</article>
</main>
</div>
@code {
private CountryData _countryData = new();
}
And Index
:
@page "/"
@layout CountryLayout
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<CountrySelector />
And Counter
:
@page "/counter"
@layout CountryLayout
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
<CountrySelector />
@code {
private int currentCount = 0;
private void IncrementCount()
{
currentCount++;
}
}
答案2
得分: -2
我进行了更多的研究,对于这个常见的用例来说,文档资料似乎太少了。下面的修改可以工作。
并不明显的是,EventCallback可以像任何其他属性一样通过CascadingParameter传递。所以你只需要用另一个CascadingParameter来包装@Main路由占位符,用于EventCallback。
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
<CascadingValue Value="@SelectionChanged" Name="SelectedChanged" >
@Body
<DetailView />
</CascadingValue>
</CascadingValue>
</main>
@code{
EventCallback<string> SelectionChanged =>
EventCallback.Factory.Create<string>(this, (ids) =>
Console.WriteLine($"Selection of {ids} Bubbled Up.")
);
}
然后在任何路由传递的页面和/或其他组件中:
[CascadingParameter(Name = "SelectionChanged")]
public EventCallback<string> SelectionChanged { get; set; }
private async Task someTrigger(){
await SelectionChanged.InvokeAsync(selectionIds);
}
英文:
I did considerable more research and for what seems like a common use case this feels far too under documented. The below modifications work.
It wasn't obvious that an EventCallback can be passed just like any other property via CascadingParameter. So you simply need to wrap the @Main route placeholder with an another CascadingParameter for the EventCallback.
<main>
<CascadingValue Value="@selectionIds" Name="selectionIds" >
<CascadingValue Value="@SelectionChanged" Name="SelectedChanged" >
@Body
<DetailView />
</CascadingValue>
</CascadingValue>
</main>
@code{
EventCallback<string> SelectionChanged =>
EventCallback.Factory.Create<string>(this, (ids) =>
Console.WriteLine($"Selection of {ids} Bubbled Up.")
);
}
Then in any of the router delivered pages and/or other components:
[CascadingParameter(Name = "SelectionChanged")]
public EventCallback<string> SelectionChanged { get; set; }
private async Task someTrigger(){
await SelectionChanged.InvokeAsync(selectionIds);
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论