When using AJAX in Magento 2, it takes a little time to get new content from the server based on its execution time. When the AJAX is processing, developers could show a loader to let users know or prevent users from doing any action before the AJAX is done.

In this article, we will guide you on how to show loader on AJAX call in Magento 2.

How to show loader on AJAX call

In this example, I create a button to send an AJAX request after clicking the button. If the setting "showLoader" is set to true, a loader will be shown when the AJAX is being processed.

<?php /** @var Magenest\LoadingScreen\Block\Ajax $block */ ?>
<div id="send-ajax-form">
  <button class="send-ajax-button">
       <?= __("Send Ajax") ?>
  </button>
  <div class="msg">

  </div>
</div>
<script>
   require(["jquery"], function ($) {
       $(".send-ajax-button").click(function (event) {
           event.preventDefault();
           let url = "<?= $block->getUrl("ajax/ajax/test") ?>";
           $.ajax({
               url: url,
               type: 'GET',
               dataType: 'json',
               showLoader: true
           }).done(function (data){
               $(".msg").text(data.msg);
           })
       })
   })
</script>

How does showLoader setting work?

The execution logic of show loading mask functionality is described in lib/web/mage/loader.js library. There are two widgets with regard to the functionality are mage.loader (register event to hide or show the loading mask) and mage.loaderAjax (trigger events of mage.loader).

When an ajax is called, the document triggers an event, loader will catch this event and call _onAjaxSend. If the ajax is completed, it triggers the 'ajaxComplete'. In onAjaxSend method, it checks if the showLoader option is set to true. If true, it triggers a processStart event (or triggers a processStop event in case ajaxComplete event is triggered).

/**
* @private
*/
_bind: function () {
   $(document).on({
       'ajaxSend': this._onAjaxSend.bind(this),
       'ajaxComplete': this._onAjaxComplete.bind(this)
   });
},
...
/**
* @param {jQuery.Event} e
* @param {Object} jqxhr
* @param {Object} settings
* @private
*/
_onAjaxSend: function (e, jqxhr, settings) {
   ...
   if (settings && settings.showLoader) {
       ctx = this._getJqueryObj(settings.loaderContext);
       ctx.trigger('processStart');

       // Check to make sure the loader is there on the page if not report it on the console.
       // NOTE that this check should be removed before going live. It is just an aid to help
       // in finding the uses of the loader that maybe broken.
       if (window.console && !ctx.parents('[data-role="loader"]').length) {
           console.warn('Expected to start loader but did not find one in the dom');
       }
   }
},

Next, the mage.loader widget will catch the event processStart then call to show the loader. It also catches the event processStop to hide the loader.

/**
* Bind on ajax events
* @protected
*/
_bind: function () {
   this._on({
       'processStop': 'hide',
       'processStart': 'show',
       'show.loader': 'show',
       'hide.loader': 'hide',
       'contentUpdated.loader': '_contentUpdated'
   });
},
...
/**
* Show loader
*/
show: function (e, ctx) {
   this._render();
   this.loaderStarted++;
   this.spinner.show();

   if (ctx) {
       this.spinner
           .css({
               width: ctx.outerWidth(),
               height: ctx.outerHeight(),
               position: 'absolute'
           })
           .position({
               my: 'top left',
               at: 'top left',
               of: ctx
           });
   }

   return false;
},
...
/**
* Hide loader
*/
hide: function () {
   if (this.loaderStarted > 0) {
       this.loaderStarted--;

       if (this.loaderStarted === 0) {
           this.spinner.hide();
       }
   }

   return false;
},

Customize Magento loading mask

To customize Magento loading mask, we can overwrite mage/loader template (located in vendor/magento/magento2-base/lib/web/mage/loader.js) to change widget option or restyle CSS.

Thank you for reading!