5

When the user press on a specific button i need to show an animation inside the button and disable it ( the method called need several seconds to accompish). This is my html:

 <div class="row">
            <div class="col-lg-4">
                @if (IsDownloading)
                {
                    <button class="btn btn-primary float-right search disabled" disabled><span class='fa-left fas fa-sync-alt spinning'></span>Downloading...</button>
                }
                else
                {
                    <button id="REC_PDF" class="btn btn-primary" @onclick="@(()=>getPDF())">Download Labels</button>
                }
                
            </div>
        </div>

and this is my code:

protected bool IsDownloading { get; set; }

public async Task getPDF()
{
    IsDownloading = true;
    StateHasChanged();

    await generate_pdf(labels);

    IsDownloading = false;
    StateHasChanged();
}

private async Task generate_pdf(List<Tuple<string, string, string, string>> filtered_label)
{
    loading = true;
    string PDF_PATH = "./PDF/" + CurrentValue;
    foreach (Tuple<string,string,string,string> label in filtered_label)
    {

        if (!Directory.Exists(PDF_PATH))
        {
            Directory.CreateDirectory(PDF_PATH);
        }
.......

The code is working but the rendering of the page called by StateHasChanged(); is executed 2 times when the method getPDF ( and generate_PDF consequentially) is fully executed.

I need page rendering on the frist call to show css loading animation.

( as suggested in other post i have used InvokeAsync(() => StateHasChanged()); but is not working)

Thanks.

1
  • @onclick="@(()=>getPDF())" <-- Why? By removing all task information and thus not awaiting anything. @onclick="getPDF" is the right way... but not the cause of your issue. Commented Aug 16, 2021 at 9:03

3 Answers 3

8

The click event is handled like this in Blazor:

var task = InvokeAsync(EventMethod);
StateHasChanged();
if (!task.IsCompleted)
{
    await task;
    StateHasChanged();
}

Note StateHasChanged doesn't actually re-render the component, it just stacks a RenderFragment delegate on the Render queue.

All the code runs on the UI Synchronisation context, so the render event queued by calling StateHasChanged only gets executed either:

  • at the point the Event Method yields control
  • if no yield takes place (because there's only sync code inside the Task block) at the end.

I can't see any code that yields in your generate_pdf.

Try the following:

public async Task getPDF()
{
    IsDownloading = true;
    await Task.Yield();

    await generate_pdf(labels);

    IsDownloading = false;
}

Note: Directory.CreateDirectory is not an async method. Only FileStream has async methods.

Sign up to request clarification or add additional context in comments.

2 Comments

Ok I get it. This is an implementation on a working app so i was tring to implemente this feature w/o re-write the method structure. Many thanks.
Thank you for the explanation. After 2 days of trying many different approaches, YIELD was what I needed.
1

The problem is that generate_pdf() is not asynchronous. Adding async is not enough. You need something asynchronous to enable a Render.

Since this is a UI problem I suggest to solve it in the top layer:

public async Task getPDF()
{
    IsDownloading = true;
    //StateHasChanged();  // not needed at start
    await task.Delay(1);  // allow the UI to update    

    try
    {
      await generate_pdf(labels);     
    }
    finally
    {
      IsDownloading = false;         
    }
    //StateHasChanged();  // not needed at the end
}

When generate_pdf() does not do any await itself you might as well make it a normal void method.

Comments

0

Try this modifications:

public async Task getPDF()
{
    if (IsDownloading) return;

    IsDownloading = true;
    StateHasChanged();

    await generate_pdf(labels);
}

then at the end of generate_pdf (better inside a try/finally block) add:

    IsDownloading = false;
    StateHasChanged();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.