Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Recupero di dati remoti con Alpine.js

Alpine.js non ha un sistema nativo per il recupero di dati da un'origine remota, scopriamo quali sono le soluzioni disponibili
Alpine.js non ha un sistema nativo per il recupero di dati da un'origine remota, scopriamo quali sono le soluzioni disponibili
Link copiato negli appunti

Alpine.js non ha un sistema nativo per il recupero di dati da un'origine remota, ma si integra bene con le API di JavaScript e con librerie esterne. Quando si recuperano dati da un'origine remota e li si assegnano ad una proprietà di x-data, Alpine aggiorna reattivamente il DOM. In genere il recupero dei dati avviene in fase di inizializzazione ma può avvenire anche successivamente, ad esempio al verificarsi di un evento clic.

Per il recupero dei dati, si può far ricorso alla fetch API nativa di Javascript o a una libreria di terze parti come axios. Vediamo un primo esempio in cui mettiamo insieme alcuni concetti acquisiti nelle lezioni precedenti.

Fetch asincrono di dati con x-init di Alpine.js

Vediamo un primo semplice esempio di recupero di dati da un'origine remota con x-init e la fetch API. I dati ci vengono forniti dalla Star Wars API:

<div
	x-data="{
		planets: [],
		loading: true,
		error: null
	}"
	x-init="
		fetch('https://swapi.dev/api/planets')
		.then(response => response.json())
		.then(data => {
			planets = data.results;
			loading = false;
		})
		.catch(error => {
			console.error('Errore:', error);
			loading = false;
			error = 'Errore nel caricamento dei dati';
		})
	">
	<template x-if="loading">
		<p>Caricamento...</p>
	</template>
	<template x-if="error">
		<p x-text="error"></p>
	</template>
	<template x-if="!loading && !error">
		<ul>
			<template x-for="planet in planets" :key="planet.name">
				<li x-text="planet.name"></li>
			</template>
		</ul>
	</template>
</div>

Analizziamo il codice.

  • All'avvio del componente (x-init), fetch interroga l'origine dati remota;
  • response => response.json() converte la risposta in JSON;
  • la successiva callback assegna i risultati (data.results) alla proprietà planets e imposta loading su false, a indicare che il caricamento è terminato. Si noti che non è necessario utilizzare .this con le proprietà planet e loading in quanto il codice è scritto direttamente nell'attributo x-init come sequenza di istruzioni, non come un metodo separato. In questo contesto, Alpine.js interpreta automaticamente le variabili come appartenenti allo scope di x-data.
  • In caso di errore (.catch()), viene inviato un messaggio alla console del browser e impostata una proprietà error.
  • All'interno del componente, abbiamo diversi elementi template. I primi sono utilizzati con la direttiva x-if per visualizzare condizionalmente il contenuto del blocco. Un elemento template è quindi utilizzato con x-for e genera un elemento di lista per ogni elemento dell'array planets.

Questo codice dovrebbe generare il seguente elenco:

Un elenco di risultati prelevati da un'origine remota

Un elenco di risultati prelevati da un'origine remota

Fetch asincrono di dati con Alpine: un esempio avanzato

In questo secondo esempio, consentiremo all'utente di selezionare il tipo di dati desiderato utilizzando tutti gli attributi della SWAPI:

{
	"films": "https://swapi.dev/api/films/",
	"people": "https://swapi.dev/api/people/",
	"planets": "https://swapi.dev/api/planets/",
	"species": "https://swapi.dev/api/species/",
	"starships": "https://swapi.dev/api/starships/",
	"vehicles": "https://swapi.dev/api/vehicles/"
}

Ecco il codice completo da copiare e incollare nel proprio editor:

<html>
<head>
	<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body>
	<div x-data="{
		items: [], // array che memorizza gli elementi della risposta
		loading: false,
		selectedType: 'planets', // valore predefinito
		async fetchData() {
			try {
				this.loading = true;
				this.items = []; // resetta il valore precedente
				const response = await fetch(`https://swapi.dev/api/${this.selectedType}/`);
				this.items = (await response.json()).results; // aggiorna con il nuovo set di dati
				this.loading = false;
			} catch (error) {
				console.error('Errore:', error);
				this.loading = false;
			}
		}
	}" x-init="fetchData()">
		<!-- Selezione del tipo di dati -->
		<select x-model="selectedType" @change="fetchData()">
			<option value="films">Films</option>
			<option value="people">People</option>
			<option value="planets" selected>Planets</option>
			<option value="species">Species</option>
			<option value="starships">Starships</option>
			<option value="vehicles">Vehicles</option>
		</select>
		<template x-if="loading">
			<p>Caricamento...</p>
		</template>
		<template x-if="!loading && items.length > 0">
			<ul>
				<template x-for="item in items" :key="item.url">
					<li x-text="item.name || item.title"></li>
				</template>
			</ul>
		</template>
		<template x-if="!loading && items.length === 0">
			<p>Nessun dato disponibile</p>
		</template>
		<button @click="fetchData()">Ricarica</button>
	</div>
</body>
</html>

In questo esempio non carichiamo i dati solo all'inizializzazione del componente ma ogni volta che l'utente interagisce con l'applicazione. Ha senso, quindi, spostare la logica all'interno di x-data e dichiarare la costante response in questo modo:

const response = await fetch(`https://swapi.dev/api/${this.selectedType}/`);

Il menu select è collegato alla proprietà selectedType e invoca fetchData() ogni volta che l'utente seleziona un elemento diverso del menu:

<select x-model="selectedType" @change="fetchData()">
	<option value="films">Films</option>
	<option value="people">People</option>
	<option value="planets" selected>Planets</option>
	<option value="species">Species</option>
	<option value="starships">Starships</option>
	<option value="vehicles">Vehicles</option>
</select>

Cambiando la selezione, si aggiorna il valore di selectedType e viene eseguito nuovamente il metodo fetchData() con la nuova richiesta.

Il diverso testo visualizzato negli elementi di lista (<li x-text="item.name || item.title"></li>) dipende dal fatto che alcune risorse dell'API usano la proprietà name, altre la proprietà title.

Un elenco di risultati prelevati a seguito di input dell'utente

Un elenco di risultati prelevati a seguito di input dell'utente

Un sistema di navigazione tra i dati

La SWAPI fornisce 10 risultati per ogni richiesta. Possiamo perfezionare il componente aggiungendo un sistema di navigazione tra i dati:

<div x-data="{
	items: [],
	loading: false,
	selectedType: 'planets',
	currentPage: 1, // Pagina corrente
	nextUrl: null, // URL della pagina successiva
	prevUrl: null, // URL della pagina precedente
	async fetchData(url = `https://swapi.dev/api/${this.selectedType}/`) {
		this.loading = true;
		this.items = [];
		const response = await fetch(url);
		const data = await response.json();
		this.items = data.results;
		this.nextUrl = data.next;
		this.prevUrl = data.previous;
		this.loading = false;
	}
}" x-init="fetchData()">
	<!-- Selezione del tipo di dati -->
	<div>
		<label for="type-select">Seleziona tipo:</label>
		<select id="type-select" x-model="selectedType" @change="currentPage = 1; fetchData()">
			<option value="films">Films</option>
			<option value="people">People</option>
			<option value="planets" selected>Planets</option>
			<option value="species">Species</option>
			<option value="starships">Starships</option>
			<option value="vehicles">Vehicles</option>
		</select>
	</div>
	<!-- Stato di caricamento -->
	<template x-if="loading">
		<p>Caricamento...</p>
	</template>
	<!-- Lista dei risultati -->
	<template x-if="!loading && items.length > 0">
		<div>
			<ul>
				<template x-for="item in items" :key="item.url">
					<li x-text="item.name || item.title"></li>
				</template>
			</ul>
			<!-- Pulsanti per la paginazione -->
			<div>
				<button @click="fetchData(prevUrl); currentPage--" :disabled="!prevUrl">Precedente</button>
				<span x-text="`Pagina ${currentPage}`"></span>
				<button @click="fetchData(nextUrl); currentPage++" :disabled="!nextUrl">Successivo</button>
			</div>
		</div>
	</template>
	<template x-if="!loading && items.length === 0">
		<p>Nessun risultato trovato</p>
	</template>
	<button @click="fetchData(`https://swapi.dev/api/${selectedType}/?page=${currentPage}`)">Ricarica</button>
</div>

Abbiamo aggiunto currentPage, nextUrl e prevUrl al nostro set di dati.

Nel codice HTML abbiamo, quindi, aggiunto due pulsanti di navigazione e uno span:

<div>
	<button @click="fetchData(prevUrl)" :disabled="!prevUrl">Precedente</button>
	<span x-text="`Pagina ${currentPage}`"></span>
	<button @click="fetchData(nextUrl); currentPage++" :disabled="!nextUrl">Successivo</button>
</div>

Infine, abbiamo aggiunto page=${currentPage} all'URL passata a fetchData al clic sul pulsante "Ricarica" in modo da rimanere sulla stessa pagina in caso di ricarica manuale da parte dell'utente.

Il componente completo con un sistema di navigazione

Il componente completo con un sistema di navigazione

Conclusioni

L'applicazione è completa, ma per i nostri lettori potrebbe essere un utile esercizio migliorarla

  • aggiungendo altri dati al set della risposta;
  • salvando i dati della risposta nel localStorage;
  • utilizzando x-show per mostrare o nascondere i dati aggiuntivi di ogni elemento.

Ti consigliamo anche