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

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.