0
votes

I have two independent components in a Blazor wasm app between whichi am trying to communicate. under certain cases the communication fails and I cannot understand why.

The(simplified) setup is as follows

<ParentComponent>
    <HeaderComponent>
        <ProgressBar IsLoading="<Set by IsLoading property from header>" />
    </HeaderComponent>
    <ResultContainer />
</ParentComponent>

The code behind looks something like this:

public class ResultContainerStateManager
{
    public event Action OnLoadStart;

    public event Action OnLoadFinish;

    public NotifyLoadStart() => this.OnLoadStart?.Invoke();

    public NotifyLoadFinish() => this.OnLoadFinish?.Invoke();
}

public partial class HeaderComponent
{
    [Inject]
    public ResultContainerStateManager ResultContainerStateManager { get; set; }

    private bool IsLoading { get; set; }

    protected override void OnInitialized()
    {
          this.ResultContainerStateManager.OnLoadStart += () => this.IsLoading = true;

          this.ResultContainerStateManager.OnLoadFinish += () => this.IsLoading = false;

          base.OnInitializer();
    }
}

public partial class ResultContainer
{
    [Inject]
    public ResultContainerStateManager ResultContainerStateManager { get; set; }

    private bool IsLoading { get; set; }

    protected override async Task OnParametersSetAsync()
    {
          <code>

          if (shouldLoadData)
          {
               this.ResultContainerStateManager.NotifyLoadStart();

               <more code>

               this.ResultContainerStateManager.NotifyLoadFinish();
          }

          await base.OnParametersSetAsync();
    }
}

public partial class ProgressBar
{
     [Parameter]
     public bool IsLoading { get; set; }
}

Where the IsLoading parameter from the progress bar is set from the IsLoading property from HeaderComponent, like

<div id="headerComponent">
    
    <More html here>

    <ProgressBar IsLoading="@this.IsLoading" />
</div>

I don't think it matters, but the progress bar itself uses the MatProgress component, like so:

@if (this.IsLoading)
{
    <MatProgress Indeterminate="true" />
}
else 
{
    <Other html code>
}

The problem is that the progress bar starts when the ResultContainer executes the NotifyLoadStart() method, but it doesn't stop when the NotifyLoadFinish() method is executed. I can see when debugging that the IsLoading property of the HeaderComponent is set back to false after the NotifyLoadFinish() call, but it has no Effect on the UI.

What I have tried so far:

  • injecting the ResultContainerStateManager directly into the Progress bar
  • I have tried changing the envents to Func and handling at all asynchronously
  • I have tried adding await Task.Yield() after each Notify call
  • I have tried adding this.StateHasChanged() calls in the event handlers and after each Notify call (I know the latter should not change anything at all, since it is not in the same hierarchy)

None of that changed anything and I would really like to understand why.

The only success I've had was when using EventCallbacks instead of the events. But I am using events in lots of other places and they all seem to work fine.

Could somebody tell me why events seem to fail and how this can be fixed?

1
The value of parameter IsLoading of the ProgressBar component is defined in what other component? What component "owns" it? Is it ParentComponent, HeaderComponent or the page? - Just the benno
@Justthebenno it is in the HeaderComponent. The header passes it's own IsLoading property into the ProgressBar. i'll change the code to make that clearer. - yu_ominae
I guess that base.OnInitializer() is a typo? - Henk Holterman

1 Answers

1
votes

Try this code

 public async Task OnLoadStart()
    {
        this.IsLoading = true;
        await InvokeAsync(() => { StateHasChanged(); });
    }



 public async Task OnLoadFinish()
    {
        this.IsLoading = false; 
        await InvokeAsync(() => { StateHasChanged(); });
    }

protected override void OnInitialized()
{
     this.ResultContainerStateManager.OnLoadStart += OnLoadStart;
     this.ResultContainerStateManager.OnLoadFinish += OnLoadFinish;
 }

Change : public event Action OnLoadStart;

To: public event Func<Task> OnLoadStart;

And: public event Action OnLoadFinish;

Tp: public event Func<Task> OnLoadFinish;

Implement IDisposable in the HeaderComponent component:

@implements IDisposable

public void Dispose()
{
    this.ResultContainerStateManager.OnLoadStart -= OnLoadStart;
    this.ResultContainerStateManager.OnLoadFinish -= OnLoadFinish;
}

Start coding asynchronously wherever you can.