How to Show Loading States with Loading.directive and Loading.service in Element UI

Element UI provides two complementary APIs for displaying loading masks—the declarative v-loading directive for template binding and the programmatic Loading.service (or this.$loading) factory for imperative control—both backed by the same underlying Vue component.

The ElemeFE/element repository implements a versatile Loading system that supports both directive-based and service-based invocation patterns. Whether you prefer declarative control in Vue templates or programmatic triggers in JavaScript logic, both approaches funnel through the identical visual component defined in packages/loading/src/loading.vue. Understanding the architectural differences between these APIs ensures you select the optimal integration pattern for your application's UX flow.

Architectural Overview: Directive vs. Service

Element UI exposes the Loading feature through two distinct entry points that share a unified rendering layer but differ in lifecycle management and invocation style.

Directive-Based Approach (v-loading)

The directive implementation in packages/loading/src/directive.js registers a global v-loading directive that manages a Mask instance through standard Vue directive hooks (bind, update, unbind). When a bound boolean evaluates to true, the directive invokes toggleLoading to compute positioning styles and insert the DOM mask; when false, it triggers hide animations and destroys the instance.

Key characteristics:

  • One Mask instance per element
  • Modifiers (fullscreen, body, lock) control positioning and scroll behavior
  • Attributes (element-loading-text, element-loading-spinner, element-loading-background, element-loading-custom-class) configure appearance

Service-Based Approach (Loading.service)

The service factory exported from packages/loading/src/index.js creates a LoadingConstructor instance via Vue.extend(loading.vue). This imperative API accepts an options object, resolves the target element, applies positioning classes, and returns a controllable instance exposing a close() method.

Key characteristics:

  • Returns a programmatic instance with close() method
  • Fullscreen instances are global singletons—lines 79-81 of the service explicitly return an existing fullscreenLoading instance if one exists
  • Options (text, spinner, background, customClass, lock, fullscreen, target) mirror directive capabilities

Using the v-loading Directive

Bind a boolean data property directly to the v-loading directive to toggle loading states declaratively on any Element UI component or DOM element.

Basic Element Binding

<template>
  <el-table
    v-loading="loading"
    :data="tableData"
    style="width: 100%">
    <!-- columns ... -->
  </el-table>

  <el-button @click="toggle">Toggle Loading</el-button>
</template>

<script>
export default {
  data() {
    return {
      loading: false,
      tableData: []
    };
  },
  methods: {
    toggle() {
      this.loading = true;
      setTimeout(() => (this.loading = false), 2000);
    }
  }
};
</script>

Fullscreen and Lock Modifiers

Use modifiers to control mask positioning and prevent body scrolling. The fullscreen modifier creates a viewport-covering mask, while lock disables background scroll.

<el-button
  v-loading.fullscreen.lock="fullscreenLoading"
  @click="showFullScreen">
  Full-screen Loading
</el-button>
export default {
  data() {
    return { fullscreenLoading: false };
  },
  methods: {
    showFullScreen() {
      this.fullscreenLoading = true;
      setTimeout(() => (this.fullscreenLoading = false), 3000);
    }
  }
};

Customization via Attributes

Configure the loading appearance using HTML attributes that the directive reads during the bind hook:

  • element-loading-text – Message displayed below the spinner
  • element-loading-spinner – Icon class for the spinner (e.g., el-icon-loading)
  • element-loading-background – CSS color for the mask overlay (e.g., rgba(0, 0, 0, 0.7))
  • element-loading-custom-class – Additional CSS class applied to the mask

Using Loading.service Programmatically

For asynchronous operations outside of template logic, use the service API to create, control, and destroy loading instances imperatively.

Importing and Invoking the Service

Import Loading from element-ui for on-demand usage, or access this.$loading when Element is globally installed via Vue.use(ElementUI).

import { Loading } from 'element-ui';

export default {
  methods: {
    fetchData() {
      const loading = Loading.service({
        lock: true,
        text: 'Loading…',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      });

      setTimeout(() => {
        loading.close();
      }, 2000);
    }
  }
};

Using the Vue Prototype Shortcut

When Element UI is installed globally, access the service through the Vue instance:

export default {
  methods: {
    processFile() {
      const loading = this.$loading({ 
        text: 'Processing…',
        fullscreen: true 
      });
      
      this.asyncTask().then(() => {
        loading.close();
      });
    }
  }
};

Target Resolution and Singleton Behavior

The service resolves the target option through document.querySelector if a string selector is provided. If the target is not document.body, the fullscreen option is forced to false. For fullscreen requests, the service checks the fullscreenLoading singleton reference (lines 79-81 in packages/loading/src/index.js) and returns the existing instance rather than creating duplicates.

Core Implementation Files

Understanding the source architecture helps debug customizations and edge cases.

packages/loading/src/loading.vue

The shared visual component that renders the spinner animation, mask overlay, and text label. Both the directive and service instantiate this component, passing configuration data via the data option.

packages/loading/src/directive.js

Registers the global v-loading directive and implements the toggleLoading function that inserts or removes the mask DOM element. Handles modifier parsing and attribute extraction during the bind hook.

packages/loading/src/index.js

Exports the Loading factory function. Manages the fullscreenLoading singleton, computes mask styles via addStyle, and applies helper classes (el-loading-parent--relative, el-loading-parent--hidden) to target parents.

types/loading.d.ts

TypeScript definitions declaring LoadingServiceOptions, the ElLoading interface (including service and $loading), and the ElLoadingComponent with its close(): void method signature.

Summary

  • The v-loading directive in packages/loading/src/directive.js provides declarative control through bind, update, and unbind hooks, creating a Mask instance per element that syncs with a boolean data property.
  • Loading.service in packages/loading/src/index.js offers imperative control via a factory function returning a closable instance, with fullscreen instances enforced as global singletons to prevent UI conflicts.
  • Both APIs consume the same underlying component from loading.vue and accept identical customization properties (text, spinner, background, customClass).
  • Directive modifiers (fullscreen, body, lock) correspond directly to service options, enabling consistent positioning and scroll-locking behavior across both invocation patterns.
  • Service instances must be explicitly dismissed via the returned object's close() method, while directive instances automatically synchronize with the bound reactive value.

Frequently Asked Questions

What is the difference between v-loading and Loading.service?

The v-loading directive ties loading state to reactive data properties in Vue templates, automatically managing DOM insertion and removal through Vue's reactivity system. Loading.service creates loading masks imperatively from JavaScript, making it ideal for asynchronous operations outside component templates, such as in Vuex actions or utility functions where template access is unavailable.

How do I prevent users from interacting with the page during fullscreen loading?

Pass the lock option to disable body scrolling and pointer events. In the directive, append the modifier: v-loading.fullscreen.lock="isLoading". In the service, include lock: true in the options object: Loading.service({ fullscreen: true, lock: true }). This adds the el-loading-parent--hidden class to suppress scroll and interaction on the underlying content.

Can I display multiple fullscreen loading instances simultaneously?

No. According to the implementation in packages/loading/src/index.js (lines 79-81), fullscreen service calls check for an existing fullscreenLoading reference and return that instance rather than creating a new mask. This singleton design prevents conflicting overlays and ensures predictable UI behavior when multiple async operations trigger fullscreen loading states concurrently.

How do I customize the spinner icon and background color in both APIs?

Use the element-loading-spinner and element-loading-background attributes when using the v-loading directive, or the spinner and background properties in the service options object. For example, directive: <div v-loading="true" element-loading-spinner="el-icon-refresh" element-loading-background="#f3f3f3">. Service: Loading.service({ spinner: 'el-icon-refresh', background: '#f3f3f3' }). Both values are passed to the underlying loading.vue component as props.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →