How Magento's JavaScript Block Loader Works

Published: December 11, 2022

Tags:

I’ve been looking at a Magento site where multiple loaders show up in different areas on the cart page. The loaders look something like this:

Screenshot of what the loader looks like

I did a bit of a deep dive into what actually causes these loader to show up. In this post I’ll share my findings.

Magento_Ui/js/block-loader

Loaders that show up in a specific area of the page are typically powered by Magento_Ui/js/block-loader.

This component creates a custom knockout binding called blockLoader

ko.bindingHandlers.blockLoader = {
    /**
     * Process loader for block
     * @param {String} element
     * @param {Boolean} displayBlockLoader
     */
    update: function (element, displayBlockLoader) {
        element = $(element);

        if (ko.unwrap(displayBlockLoader())) {
            blockLoaderElement.done(addBlockLoader(element));
        } else {
            blockLoaderElement.done(removeBlockLoader(element));
        }
    }
};

Reference: https://github.com/magento/magento2/blob/2.4.5-p1/app/code/Magento/Ui/view/base/web/js/block-loader.js#L76-L93

If the binding value is true the addBlockLoader function will be called (and if false it will call removeBlockLoader.

Using the binding

Magento_Checkout/js/view/cart/totals provides a good example of how to use the binding.

The component has an isLoading property, which is mapped to isLoading in Magento_Checkout/js/model/totals

define([
    'jquery',
    'uiComponent',
    'Magento_Checkout/js/model/totals',
    'Magento_Checkout/js/model/shipping-service'
], function ($, Component, totalsService, shippingService) {
    'use strict';

    return Component.extend({
        isLoading: totalsService.isLoading,

Reference: https://github.com/magento/magento2/blob/2.4.5-p1/app/code/Magento/Checkout/view/frontend/web/js/view/cart/totals.js#L5-L14

Then in the template file Magento_Checkout/template/cart/totals.html we can see the custom binding set on the wrapper div.

<div class="table-wrapper" data-bind="blockLoader: isLoading">

Reference: https://github.com/magento/magento2/blob/2.4.5-p1/app/code/Magento/Checkout/view/frontend/web/template/cart/totals.html

In Magento_Checkout/js/model/totals we can see that isLoading is an observable, initially set to false.

isLoading: ko.observable(false),

Reference: https://github.com/magento/magento2/blob/2.4.5-p1/app/code/Magento/Checkout/view/frontend/web/js/model/totals.js#L31

If we search for isLoading across the codebase we find many places that toggle the value, which will cause the loader to show. For example in Magento_Checkout/js/action/get-totals.js

        totals.isLoading(true);

        return storage.get(
            resourceUrlManager.getUrlForCartTotals(quote),
            false
        ).done(function (response) {
            var proceed = true;

            totals.isLoading(false);

Reference: https://github.com/magento/magento2/blob/2.4.5-p1/app/code/Magento/Checkout/view/frontend/web/js/action/get-totals.js#L21-L29

Monitoring loader display

One thing that could be interesting to put in place is monitoring to track how much time the site users spend seeing loaders. Doing so would require patching Magento_Ui/js/block-loader.

Adding a tracker that flags when the block loader shows and when it’s hidden can be done as follows.

addBlockLoader

function addBlockLoader(element) {
    console.log(`addBlockLoader ${element.context.className} ${element.context.id} ${performance.now()}`);

removeBlockLoader

function removeBlockLoader(element) {
    if (!element.has(blockLoaderClass).length) {
        return;
    }
    console.log(`removeBlockLoader ${element.context.className} ${element.context.id} ${performance.now()}`);

From here we can extrapolate how much time the loader was visibile for, and on which area of the page.

Obviously in the real world we would not use console.log and instead something like newrelic.addPageAction (assuming the project is using New Relic).

Wrap Up

That’s all for now. Hopefully you found this helpful because I sure as heck was scratching my head trying to figure out what was causing the spinners to show up.

Max Chadwick Hi, I'm Max!

I'm a software developer who mainly works in PHP, but loves dabbling in other languages like Go and Ruby. Technical topics that interest me are monitoring, security and performance. I'm also a stickler for good documentation and clear technical writing.

During the day I lead a team of developers and solve challenging technical problems at Rightpoint where I mainly work with the Magento platform. I've also spoken at a number of events.

In my spare time I blog about tech, work on open source and participate in bug bounty programs.

If you'd like to get in contact, you can find me on Twitter and LinkedIn.