当使用路由器时,从布局订阅EventCallback。

huangapple go评论66阅读模式
英文:

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?

&lt;main&gt;
	&lt;CascadingValue Value=&quot;@selectionIds&quot; Name=&quot;selectionIds&quot; &gt;
		@Body 
		&lt;DetailView /&gt;
	&lt;/CascadingValue&gt;
&lt;/main&gt;

答案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&lt;CountryData&gt;();

A CountryDisplay component to dispay the country.

@implements IDisposable
@inject CountryData CountryData

&lt;div class=&quot;alert alert-primary m-2&quot;&gt;@this.CountryData.Country&lt;/div&gt;

@code {

    protected override void OnInitialized()
        =&gt; this.CountryData.CountryChanged += OnCountryChanged;

    private void OnCountryChanged(object? sender, EventArgs e)
        =&gt; this.StateHasChanged();

    public void Dispose()
        =&gt; this.CountryData.CountryChanged += OnCountryChanged;
}

And a CountrySelector component to modify it:

@inject CountryData CountryData

&lt;div class=&quot;bg-light m-2 pt-2 p-4 border border-1&quot;&gt;

    &lt;h3&gt;Country Selector&lt;/h3&gt;

    &lt;InputSelect class=&quot;form-select&quot; @bind-Value:get=&quot;this.CountryData.Country&quot; @bind-Value:set=&quot;this.CountryData.SetCountry&quot;&gt;

        @if (this.CountryData.Country is null)
        {
            &lt;option selected disabled value=&quot;&quot;&gt; -- Select a Country -- &lt;/option&gt;
        }

        @foreach (var country in CountryProvider.CountryList)
        {
            &lt;option value=&quot;@country&quot;&gt;@country&lt;/option&gt;
        }

    &lt;/InputSelect&gt;

&lt;/div&gt;

@code {
    // NOTE - @bind-Value:get may give you an error.  It&#39;s a SDK issue.  The code will compile.
}

CountryLayout

@inherits LayoutComponentBase

&lt;PageTitle&gt;SO76864163&lt;/PageTitle&gt;

&lt;div class=&quot;page&quot;&gt;
    &lt;div class=&quot;sidebar&quot;&gt;
        &lt;NavMenu /&gt;
    &lt;/div&gt;

    &lt;main&gt;
        &lt;div class=&quot;top-row px-4&quot;&gt;
            &lt;a href=&quot;https://docs.microsoft.com/aspnet/&quot; target=&quot;_blank&quot;&gt;About&lt;/a&gt;
        &lt;/div&gt;

        &lt;article class=&quot;content px-4&quot;&gt;
                &lt;CountryDisplay /&gt;
                @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

@code {
    private CountryData _countryData = new();
}

And Index:

@page &quot;/&quot;
@layout CountryLayout

&lt;PageTitle&gt;Index&lt;/PageTitle&gt;

&lt;h1&gt;Hello, world!&lt;/h1&gt;

Welcome to your new app.

&lt;SurveyPrompt Title=&quot;How is Blazor working for you?&quot; /&gt;

&lt;CountrySelector /&gt;

And Counter:

@page &quot;/counter&quot;
@layout CountryLayout

&lt;PageTitle&gt;Counter&lt;/PageTitle&gt;

&lt;h1&gt;Counter&lt;/h1&gt;

&lt;p role=&quot;status&quot;&gt;Current count: @currentCount&lt;/p&gt;

&lt;button class=&quot;btn btn-primary&quot; @onclick=&quot;IncrementCount&quot;&gt;Click me&lt;/button&gt;

&lt;CountrySelector /&gt;

@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.

&lt;main&gt;
  &lt;CascadingValue Value=&quot;@selectionIds&quot; Name=&quot;selectionIds&quot; &gt;
    &lt;CascadingValue Value=&quot;@SelectionChanged&quot; Name=&quot;SelectedChanged&quot; &gt;
      @Body 
      &lt;DetailView /&gt;
    &lt;/CascadingValue&gt;
  &lt;/CascadingValue&gt;
&lt;/main&gt;
@code{
   EventCallback&lt;string&gt; SelectionChanged =&gt; 
        EventCallback.Factory.Create&lt;string&gt;(this, (ids) =&gt; 
           Console.WriteLine($&quot;Selection of {ids} Bubbled Up.&quot;)
        );
}

Then in any of the router delivered pages and/or other components:

[CascadingParameter(Name = &quot;SelectionChanged&quot;)]
public EventCallback&lt;string&gt; SelectionChanged { get; set; }

private async Task someTrigger(){
   await SelectionChanged.InvokeAsync(selectionIds);
}

huangapple
  • 本文由 发表于 2023年8月9日 10:15:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/76864163.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定