Creare un loader globale in Vue.js per le richieste GraphQL

In questo articolo configureremo un loader globale che si attiva quando qualunque richiesta GraphQL è in corso e si disattiva quando tutte le richieste sono terminate.

Useremo un'istanza di ApolloLink per contare le richieste attive e uno store reattivo Vue per mostrarne lo stato nell’interfaccia.

Prerequisiti

npm i @apollo/client graphql

Struttura dei file


src/
  apollo/
    activityLink.js
    client.js
  components/
    Loader.vue
  stores/
    apolloLoading.js
  App.vue

Store reattivo: contatore richieste attive

Creiamo uno store minimale con un contatore e un computed per sapere se c’è almeno una richiesta in corso.

// src/stores/apolloLoading.js
import { ref, computed } from "vue";

const active = ref(0);

export const apolloActiveRequests = {
	inc() { active.value = Math.max(0, active.value + 1); },
	dec() { active.value = Math.max(0, active.value - 1); },
	active,                                  // espone il numero esatto
	isLoading: computed(() => active.value > 0), // true se >= 1 richiesta
}; 

2) Apollo Link: incrementa/decrementa il contatore

Il link comprende forward(operation) e gestisce i casi complete, error e unsubscribe, evitando loader bloccanti.

// src/apollo/activityLink.js
import { ApolloLink, Observable } from "@apollo/client/core";
import { apolloActiveRequests } from "@/stores/apolloLoading";

export const activityLink = new ApolloLink((operation, forward) => {
	// opzionale: escludi alcune richieste dal loader globale
	if (operation.getContext().silent) {
	    return forward(operation);
	}

	apolloActiveRequests.inc();

	return new Observable((observer) => {
	    const sub = forward(operation).subscribe({
	    next: (value) => observer.next(value),
	    error: (err) => { apolloActiveRequests.dec(); observer.error(err); },
 	    complete: () => { apolloActiveRequests.dec(); observer.complete(); },
	});



	return () => { sub.unsubscribe(); apolloActiveRequests.dec(); };

	});
}); 

Client Apollo

Qui non usiamo la funzione from(), deprecata, ma ApolloLink.from([...]).

// src/apollo/client.js
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";
import { ApolloLink } from "@apollo/client/core";
import { activityLink } from "./activityLink";

const httpLink = new HttpLink({ uri: "/graphql" });

const link = ApolloLink.from([
	activityLink, // deve stare PRIMA per intercettare tutto
	httpLink,
]);

export const apollo = new ApolloClient({
	link,
	cache: new InMemoryCache(),
}); 

Componente Vue: barra di caricamento globale

Mostriamo/nascondiamo la barra in base allo stato dello store. Gli stili CSS sono puramente indicativi.

<!-- src/components/Loader.vue -->
<script setup>
import { computed } from "vue";
import { apolloActiveRequests } from "@/stores/apolloLoading";

const isLoading = computed(() => apolloActiveRequests.isLoading.value);

</script>

<template>
  <div :class="{'visible': isLoading}" class="loader"></div>
</template>

<style>
.loader {
 position: fixed;
 left: 0; 
 right: 0; 
 top: 0;
 height: 3px;
 background: linear-gradient(90deg, #bbb, #333, #bbb);
 background-size: 200px 3px;
 animation: glide 1s linear infinite;
 z-index: 9999;
 display: none; 
}

.loader.visible {
  display: block;
}
@keyframes glide {
 from { background-position: 0 0; }
 to   { background-position: 200px 0; }
}
</style>

Inserimento nel layout dell'applicazione

Inseriamo il loader globale una sola volta, ad esempio nel root component.

<!-- src/App.vue -->
<script setupt>
import { RouterView } from "vue-router";
import Loader from "@/components/Loader.vue";
</script>

<template>
  <Loader />
  <RouterView />
</template>

Note e consigli

  • Ordine dei link: l’activityLink va prima dell’HttpLink per intercettare tutto.
  • Anti-flash: se il loader lampeggia per richieste brevissime, applica un piccolo ritardo nel mostrare la barra (debounce di 150–250 ms) con un watch sullo store.
  • Errori/abort: decrementiamo il contatore su complete, error e unsubscribe per non lasciare il loader in uno stato non conforme allo step corrente dell'attività di Apollo Client.
  • SSR/Non-browser: evita il CSS del loader globale lato server.
Torna su