19
votes

I'm using Server-side Blazor components in ASP.NET Core 3 preview 4.

I have a parent component, and child components, using the same shared model, like this :

Model :

public class CountModel
{
    public int Count { get; set; }

    public void Increment()
    {
        Count++;
    }
}

Parent component :

@page "/count"

<CascadingValue Value="currentCount">
    <h1>Count parent</h1>

    <p>Current count is : @currentCount.Count</p>

    <button class="btn btn-primary" onclick="@currentCount.Increment">+1 from parent</button>

    <CountChild></CountChild>
</CascadingValue>

@functions {
    private CountModel currentCount = new CountModel();
}

Child component :

<h1>Count child</h1>

<p>Current count is : @currentCount.Count</p>

<button class="btn btn-primary" onclick="@currentCount.Increment">+1 from child</button>


@functions {
    [CascadingParameter]
    private CountModel currentCount { get; set; }
}

It's the same instance of the model used for the parent and the child. When the model is updated from the parent, both display the correct incremented value. When it's updated from the child, only the child display the correct value.

How can I force the parent component to be refreshed when it is updated from the child ?

Note, here I have a function to update the model, but I would like the solution to work when data is bound to an input.

4
What about to call StateHasChanged to notify changes? Like public void Increment() { Count++; StateHasChanged(); } Take a look to Chris Sainty post 3 Ways to Communicate Between Components in Blazor He uses it. Also related: github.com/aspnet/Blazor/issues/420 - dani herrera
@dani herrera, I don't think the issue is related to not calling StateHasChanged. See my answer above... - enet
Hi @Issac, I prefer a delegate over CascadingParameter, I guess Chris Sainty cascading sample match exactly this scenario. - dani herrera
Yes, I also for delegate... - enet

4 Answers

31
votes

Create a shared service. Subscribe to the service's RefreshRequested event in the parent and Invoke() from the child. In the parent method call StateHasChanged();

public interface IMyService
{
    event Action RefreshRequested;
    void CallRequestRefresh();
 }

public class MyService: IMyService
{
    public event Action RefreshRequested;
    public void CallRequestRefresh()
    {
         RefreshRequested?.Invoke();
    }
}


//child component
MyService.CallRequestRefresh();


//parent component
MyService.RefreshRequested += RefreshMe;

private void RefreshMe()
{
    StateHasChanged();
}
7
votes

The following code snippet is the most appropriate method to refresh a parent component when a model is updated from its child component. But it adds more to the bargains: no dependency between parent and child. It is not specifically created to notify of a state change. It notifies when a property, any property has changed, and it can provides to subscribers the name of the property whose value has changed, the new value, etc.

 using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using System.ComponentModel.DataAnnotations;

The main point to note here is that our model class implements the INotifyPropertyChanged interface...

CountModel.cs

public class CountModel : INotifyPropertyChanged
{
    private int count;
    public int Count
    {
        get => count;
        set => SetProperty(ref count, value);
    }

    public event PropertyChangedEventHandler PropertyChanged;
    void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new 
                                   PropertyChangedEventArgs(propertyName));
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string 
                                                     propertyName = null)
    {
        if (Equals(storage, value))
        {
            return false;
        }

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    public void Increment()
    {
        Count++;
    }
}

Count.razor

@page "/count"
@implements IDisposable

<CascadingValue Value="currentCount">
    <h1>Count parent</h1>

    <p>Current count is : @currentCount.Count</p>

    <button class="btn btn-primary" @onclick="@currentCount.Increment">+1 
                                                     from parent</button>

    <CountChild></CountChild>
</CascadingValue>

@code {
    private CountModel currentCount = new CountModel();

    protected override void OnInitialized()
    {
       currentCount.PropertyChanged += (sender, args) => StateHasChanged();
    }

    public void Dispose()
    {
        currentCount.PropertyChanged -= (sender, args) => StateHasChanged();
    }
}

CountChild.razor

<h1>Count child</h1>

<p>Current count is : @currentCount.Count</p>

<button class="btn btn-primary" @onclick="@currentCount.Increment">+1 from 
                                                            child</button>


@code {
     [CascadingParameter]
     private CountModel currentCount { get; set; }


}

Hope this helps...

7
votes

The flow of Cascading parameters is downwards. For your parent to be refreshed, you want to provide a callback that the child component can call, passing it some value. I've already shown in the Blazor section here how to create a callback on the parent component, and how to trigger the callback, passing it a value.

4
votes

Update Parent State by calling it's StateHasChanged method

Create a Method to Update the State on Parent:

public void RefreshState(){
     this.StateHasChanged();
}

Pass the Parent to the Child's by cascading Value or Parameter Example:

<CascadingValue Value="this">
  <ChildComponent /> 
</CascadingValue>

Now on the Child's component declare the Cascading Parameter:

[CascadingParameter]
public ParentPageType _Parent { get; set; }

And Now When you want to refresh the parent just call:

_Parent.RefreshState();