Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling async method from inside NavigationManager.LocationChanged

I am using NavigationManager.LocationChanged to capture query strings. After getting the query string value I'm making an ajax call, which is async. LocationChanged itself is synchronous method, and it looks like there is no async version of LocationChanged. And when calling async method from inside LocationChanged, the value set by the async method is lagging one step behind.

Here is the repro:

@page "/investigate"
@implements IDisposable
@inject NavigationManager NM

<h1>Sync: @SyncValue</h1>
<h1>Async: @AsyncValue</h1>
<button @onclick="TriggerLocationChange">Increment</button>

@code {
    private string SyncValue;
    private string AsyncValue;
    private int Counter = 1;

    protected override void OnInitialized()
    {
        NM.LocationChanged += OnLocationChanged;
    }

    public void Dispose()
    {
        NM.LocationChanged -= OnLocationChanged;
    }

    private void OnLocationChanged(object sender, LocationChangedEventArgs args)
    {
        // sync action, just for comparison
        SyncValue = (Counter * 1000).ToString();

        DoSomeAsync();
    }

    private async Task DoSomeAsync()
    {
        // http call to server
        await Task.Delay(1);

        AsyncValue = (Counter * 1000).ToString();
    }

    private void TriggerLocationChange()
    {
        Counter++;
        NM.NavigateTo("investigate?counter=" + Counter);
    }
}

The @AsyncValue is lagging one step behind from the @SyncValue.

How can I prevent the async method from lagging behind when called from inside LocationChanged?

like image 379
Endy Tjahjono Avatar asked May 25 '20 15:05

Endy Tjahjono


2 Answers

After lots of trial and error, here is what I found:

  1. Route parameter values are not yet set when LocationChanged runs. This is not shown in my example above but important in my case. Route parameter can be extracted manually from URL, or we can wait until blazor populates route parameter using await Task.Delay(1).

  2. Call StateHasChanged() at the end of the async method.

  3. According to the documentation we should wrap the call with base.InvokeAsync(() => ...)

After these modifications, OnLocationChanged becomes:

private void OnLocationChanged(object sender, LocationChangedEventArgs args)
{
    // ...

    base.InvokeAsync(async () =>
    {
        await Task.Delay(1);  // wait for blazor to populate route parameters
        await DoSomeAsync();
        StateHasChanged();
    });
}
like image 166
Endy Tjahjono Avatar answered Nov 19 '22 03:11

Endy Tjahjono


Marking a method with async does not change the signature of the method, hence you should be albe to do this:

private async void OnLocationChanged(object sender, LocationChangedEventArgs args)
{
   // sync action:
   SyncValue = (Counter * 1000).ToString();

   await DoSomeAsync();
   StateHasChanged();
}

like image 38
Zsolt Bendes Avatar answered Nov 19 '22 03:11

Zsolt Bendes