This one will make your head spin while you wait!

Until version 21, Business Central didn’t provide a way to tell users that the system was working on something, but now we have the WaitSpinner. Check out the video:

https://youtu.be/5bFwobCWJAY

In this video, Erik explores a user control introduced in Business Central version 21: the WaitSpinner. This control provides a visual “loading” indicator for end users, letting them know the system is working on something. Erik walks through creating a simple page that uses the spinner, explains how the control’s triggers work, and demonstrates how you can use it to process work in chunks on the main thread.

The Problem Before Version 21

Prior to version 21, Business Central didn’t offer a great built-in way to communicate to end users that the system was busy processing something. Microsoft needed this capability themselves — particularly for scenarios like installing apps from AppSource — so they introduced the WaitSpinner user control, and it’s available for us to use as well.

Setting Up the Spinner Page

Erik starts by creating a brand new AL project and a simple page. The key element is a usercontrol that references the built-in WaitSpinner control:

page 50100 "Spinner"
{
    ApplicationArea = all;
    layout
    {
        area(Content)
        {
            usercontrol(Spin; WaitSpinner)
            {
                // triggers go here
            }
        }
    }
}

Without even specifying a page type, Erik publishes and navigates to the page. The spinner appears immediately — it just works. Using page inspection, he confirms the default page type resolves to a Card page. The spinner is visible and animating right away.

Understanding the Triggers: Ready and Callback

The WaitSpinner control exposes two triggers:

  • Ready() — Fired when the user control has been fully instantiated and is ready to use. Like all user controls in Business Central, loading is asynchronous, so you can’t interact with the control from OnOpenPage — the control isn’t instantiated yet at that point.
  • Callback() — Fired after the wait duration specified in the Wait() function has elapsed. This is essentially your “ping” back from the control.

The Async Nature of User Controls

Erik demonstrates a common pitfall: trying to call CurrPage.Spin.Wait(10) from the OnOpenPage trigger results in an error because the control hasn’t been instantiated yet. User controls load asynchronously — the page renders first, and then the control signals it’s ready via the Ready() trigger.

This is why you need to start your interaction inside the Ready() trigger, not in OnOpenPage.

Building a Ping-Pong Pattern

Erik draws a comparison to the old “ping-pong” timer control from earlier versions of NAV/Business Central. The pattern is the same: the control fires a callback, you do some work, then you tell the control to wait again, creating a loop.

Here’s the complete source code for the spinner page:

page 50100 "Spinner"
{
    ApplicationArea = all;
    layout
    {
        area(Content)
        {
            usercontrol(Spin; WaitSpinner)
            {
                trigger Ready()
                begin
                    if ControlReady then
                        exit;
                    ControlReady := true;
                    CurrPage.Spin.Wait(0);
                end;

                trigger Callback()
                var
                    Customer: Record Customer;
                begin
                    Customer.SetAutoCalcFields(Balance);
                    if Customer.FindSet() then
                        repeat
                            Balance += Customer.Balance + Random(100);
                        until Customer.Next() = 0;
                    CurrPage.Spin.Wait(0);
                end;
            }
            field(WaitTime; Balance)
            {
                Caption = 'Wating';
                Editable = false;
            }
        }
    }
    trigger OnOpenPage()
    begin
    end;

    var
        ControlReady: Boolean;
        Balance: Decimal;
        WaitTime: Integer;
}

Key Details in the Code

  • Guard against double-firing: The ControlReady boolean in the Ready() trigger prevents the initialization logic from running more than once, in case the trigger fires multiple times.
  • Wait(0) acts as a yield: Erik experiments with Wait(0) and discovers it essentially acts as a yield — the fastest possible round-trip. This is useful when you want to break work into small chunks without introducing artificial delays.
  • Processing in chunks: Inside the Callback() trigger, Erik loops through Customer records, accumulating balances. Each time the callback fires, it processes the full customer table and then calls Wait(0) again, creating a continuous loop. The displayed balance value updates on screen with each iteration, proving that work is being done between spinner callbacks.

Threading Considerations

Erik raises an important point about threading: the spinner runs on the UI thread, and Business Central is not truly multi-threaded. This means if you do heavy processing directly in the callback, you’re occupying the main thread. The spinner will still animate (since it’s a client-side control), but the server-side AL code is blocking.

He mentions several alternative approaches for longer-running operations:

  • Start a separate session and poll for its result
  • Use a page background task to offload processing
  • Break work into small chunks that execute inside each callback cycle (as demonstrated)

For truly long-running processes, a background task paired with the spinner as a visual indicator would be the ideal combination. However, the long process might trigger Business Central’s built-in “working” dialog, which would overlay the spinner anyway.

Where Microsoft Uses It

Microsoft themselves use the WaitSpinner control for the improved app installation experience — particularly when installing apps from AppSource. There’s a dedicated page that displays the spinner while the installation is in progress, giving users clear visual feedback that the system is working.

Summary

The WaitSpinner user control, available from Business Central version 21 onwards, provides a clean way to show users that something is happening. Its two triggers — Ready() and Callback() — enable a ping-pong pattern where you can process work in chunks while keeping the UI responsive with a spinning indicator. While it won’t solve all long-running process scenarios on its own, combined with background tasks or session management, it’s a welcome addition to the AL developer’s toolkit for improving user experience.