Best way to share data between two child components in Blazor
Asked Answered
S

2

7

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.

Scaphoid answered 27/5, 2020 at 11:22 Comment(2)
Either events through the parent, or a data service, or a cascading parameter at a higher level (like shared parent)Diestock
Blazor is essentially React#. The way you share information is the same as React - the state should be held by the parent, and components should trigger updates to that state. Other child components will be updated in response to that state changeSlocum
C
9

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

        await Notify?.Invoke();
        
    }

    public event Func<Task> Notify;
}

Child1.razor

    @inject NotifierService Notifier
@implements IDisposable

<div>User puts in something</div>
<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

<div>Displays Value from service and lets user put in new value</div>

<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 "/"

 <p>
    <Child1></Child1>
 </p>
<p></p>
<p>
   <Child2></Child2>
</p>

Startup.ConfigureServices

services.AddScoped<NotifierService>();

Hope this helps...

Cullan answered 27/5, 2020 at 11:59 Comment(2)
Just one more thing, you can create a local variable and bind it to the NotifierService in the OnNotify method like this: public async Task OnNotify() { await InvokeAsync(() => { yourLocalVariable = Notifier.ValuesList; StateHasChanged(); }); }Corpulent
It looks like the comm is happening on same page between two components - any idea whether it should be same for two pages open on two browser tab and for the same user scope ? Looks like two different instance of NotifierService gets created and hence the subscribed method is not invoked when update happens from another page ?!Contraception
I
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.

Incurvate answered 27/5, 2020 at 11:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.