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 graphqlStruttura 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’activityLinkva prima dell’HttpLinkper 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 watchsullo 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.