问题
I have this code.
<ParentComponent>
<ChildComponet>
@renderFragment
</ChildComponent>
<ChildComponetn>
<GridComponent Data="@dataList"/>
</ChildComponent>
</ParentComponent>
where @renderFragment is dynamically render componet and Grid componet is list of some data with actions like "add new", "edit record", "delete".
If we click "add new", form for add new record is opened dynamically in @renderFragment and we want to refresh grid data after submit form but we don't know how to share some data between two child components. Same is about edit form, when some record is edited, we need to refresh grid component to show edited data. If need more code and data about it please comment.
回答1:
You may define a class service that implements the State pattern and the Notifier pattern to handle the state of your objects, pass state to objects, and notify subscriber objects of changes.
Here's a simplified example of such service, which enables a parent component to communicate with his children.
NotifierService.cs
public class NotifierService
{
private readonly List<string> values = new List<string>();
public IReadOnlyList<string> ValuesList => values;
public NotifierService()
{
}
public async Task AddTolist(string value)
{
values.Add(value);
if (Notify != null)
{
await Notify?.Invoke();
}
}
public event Func<Task> Notify;
}
Child1.razor
@inject NotifierService Notifier
@implements IDisposable
<h1>User puts in something</h1>
<input type="text" @bind="@value" />
<button @onclick="@AddValue">Add value</button>
@foreach (var value in Notifier.ValuesList)
{
<p>@value</p>
}
@code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
public async Task OnNotify()
{
await InvokeAsync(() =>
{
StateHasChanged();
});
}
protected override void OnInitialized()
{
Notifier.Notify += OnNotify;
}
public void Dispose()
{
Notifier.Notify -= OnNotify;
}
}
Child2.razor
@inject NotifierService Notifier
<h1>Displays Value from service and lets user put in new value</h1>
<input type="text" @bind="@value" />
<button @onclick="@AddValue">Set Value</button>
@code {
private string value { get; set; }
public async Task AddValue()
{
await Notifier.AddTolist(value);
}
}
Usage
@page "/"
<Child1></Child1>
<Child2></Child2>
Startup.ConfigureServices
services.AddScoped<NotifierService>();
Hope this helps...
回答2:
There are a few ways to do it, I just learned a really cool way using a Singleton class.
I have this component I use to send a message to other users in my chat called SubscriptionService, but you can use any class.
Add this inject to both of your components:
@inject Services.SubscriberService SubscriberService
#region using statements
using DataJuggler.UltimateHelper.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Transactions;
#endregion
namespace BlazorChat.Services
{
#region class SubscriberService
/// <summary>
/// This class is used to subscribe to services, so other windows get a notification a new message
/// came in.
/// </summary>
public class SubscriberService
{
#region Private Variables
private int count;
private Guid serverId;
private List<SubscriberCallback> subscribers;
#endregion
#region Constructor
/// <summary>
/// Create a new instance of a 'SubscriberService' object.
/// </summary>
public SubscriberService()
{
// Create a new Guid
this.ServerId = Guid.NewGuid();
Subscribers = new List<SubscriberCallback>();
}
#endregion
#region Methods
#region BroadcastMessage(SubscriberMessage message)
/// <summary>
/// This method Broadcasts a Message to everyone that ins't blocked.
/// Note To Self: Add Blocked Feature
/// </summary>
public void BroadcastMessage(SubscriberMessage message)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (NullHelper.Exists(message)))
{
// Iterate the collection of SubscriberCallback objects
foreach (SubscriberCallback subscriber in Subscribers)
{
// if the Callback exists
if ((subscriber.HasCallback) && (subscriber.Id != message.FromId))
{
// to do: Add if not blocked
// send the message
subscriber.Callback(message);
}
}
}
}
#endregion
#region GetSubscriberNames()
/// <summary>
/// This method returns a list of Subscriber Names ()
/// </summary>
public List<string> GetSubscriberNames()
{
// initial value
List<string> subscriberNames = null;
// if the value for HasSubscribers is true
if (HasSubscribers)
{
// create the return value
subscriberNames = new List<string>();
// Get the SubscriberNamesl in alphabetical order
List<SubscriberCallback> sortedNames = Subscribers.OrderBy(x => x.Name).ToList();
// Iterate the collection of SubscriberService objects
foreach (SubscriberCallback subscriber in sortedNames)
{
// Add this name
subscriberNames.Add(subscriber.Name);
}
}
// return value
return subscriberNames;
}
#endregion
#region Subscribe(string subscriberName)
/// <summary>
/// method returns a message with their id
/// </summary>
public SubscriberMessage Subscribe(SubscriberCallback subscriber)
{
// initial value
SubscriberMessage message = null;
// If the subscriber object exists
if ((NullHelper.Exists(subscriber)) && (HasSubscribers))
{
// Add this item
Subscribers.Add(subscriber);
// return a test message for now
message = new SubscriberMessage();
// set the message return properties
message.FromName = "Subscriber Service";
message.FromId = ServerId;
message.ToName = subscriber.Name;
message.ToId = subscriber.Id;
message.Data = Subscribers.Count.ToString();
message.Text = "Subscribed";
}
// return value
return message;
}
#endregion
#region Unsubscribe(Guid id)
/// <summary>
/// This method Unsubscribe
/// </summary>
public void Unsubscribe(Guid id)
{
// if the value for HasSubscribers is true
if ((HasSubscribers) && (Subscribers.Count > 0))
{
// attempt to find this callback
SubscriberCallback callback = Subscribers.FirstOrDefault(x => x.Id == id);
// If the callback object exists
if (NullHelper.Exists(callback))
{
// Remove this item
Subscribers.Remove(callback);
// create a new message
SubscriberMessage message = new SubscriberMessage();
// set the message return properties
message.FromId = ServerId;
message.FromName = "Subscriber Service";
message.Text = callback.Name + " has left the conversation.";
message.ToId = Guid.Empty;
message.ToName = "Room";
// Broadcast the message to everyone
BroadcastMessage(message);
}
}
}
#endregion
#endregion
#region Properties
#region Count
/// <summary>
/// This property gets or sets the value for 'Count'.
/// </summary>
public int Count
{
get { return count; }
set { count = value; }
}
#endregion
#region HasSubscribers
/// <summary>
/// This property returns true if this object has a 'Subscribers'.
/// </summary>
public bool HasSubscribers
{
get
{
// initial value
bool hasSubscribers = (this.Subscribers != null);
// return value
return hasSubscribers;
}
}
#endregion
#region ServerId
/// <summary>
/// This property gets or sets the value for 'ServerId'.
/// </summary>
public Guid ServerId
{
get { return serverId; }
set { serverId = value; }
}
#endregion
#region Subscribers
/// <summary>
/// This property gets or sets the value for 'Subscribers'.
/// </summary>
public List<SubscriberCallback> Subscribers
{
get { return subscribers; }
set { subscribers = value; }
}
#endregion
#endregion
}
#endregion
}
For my chat application, I want it available to all instances, so in your configure services method of Startup.cs, add a Sington:
services.AddSingleton<SubscriberService>();
To make it only available to this browser instance:
services.AddScoped(SubscriberService);
Now from both components you can call a method or get to properties on your injected class:
SubscriptionService.GetSubscribers();
Or if you prefer interfaces, I wrote a blog post about that and I don't want to duplicate the text:
https://datajugglerblazor.blogspot.com/2020/01/how-to-use-interfaces-to-communicate.html
The inject way is pretty cool though, as your entire application can communicate with other user instances for chat.
来源:https://stackoverflow.com/questions/62041889/best-way-to-share-data-between-two-child-components-in-blazor