Exemple d’un composant utilisant du chargement asynchrone

Cet exemple présente un composant réalisant du chargement asynchrone de données en utilisant un effet créé avec la fonction createEffect().

Description du composant

Le composant affiche une liste de résumé d’articles d’actualité avec un bouton pour en charger plus et un autre bouton pour réduire et rafraichir la liste. Le composant peut par ailleurs rafraîchir la liste lui-même.

Le composant est un fonction nommée Infos attendant 3 propriétés et retournant un élément <div>.

Les 3 propriétés sont :

  1. count : une donnée observable d’un nombre d’articles à charger au démarrage,
  2. loadBy : un nombre entier indiquant le nombre d’articles à charger en plus,
  3. refreshTimeout : un nombre entier représentant en millisecondes le délai de rafraîchissement de la liste.

La propriété count est requise, les autres ne le sont pas, elles ont une valeur par défaut.

Code du composant

Le code du composant est représenté ci-dessous mais pour plus de lisibilité il est présenté en 2 parties, le code de la fonction du composant et le code d’une fonction du composant.

Fonction Infos()

Le composant est la fonction Infos(), son code est représenté ci-dessous.

function Infos({count, loadBy = 4, refreshTimeout = 600000}) {
const {div, p, strong, a, button} = Room.elements();

const start = Room.createData(0);
let limit = count.value;
let id = 0;

const add = () => {start.value = count.value; limit = loadBy};
const reset = () => {start.value = 0; limit = loadBy};
const refresh = () => {start.value = 0; limit = count.value};
const stop = () => {clearInterval(id); id = 0};

const loadIndicator = div({class: "bounceLoader"}, div(),div(),div());
const container = div({class: "infos"});
const addButton = button({type: "button", onClick: add}, "Charger la suite");

const loadInfos = () => {...};

Room.createEffect(loadInfos);
return div({onUnmount: stop},
loadIndicator,
container,
div({class: "buttons"},
addButton,
a({href: "#infos", onClick: reset, title: "Réduire et rafraîchir la liste"}, "Reset")
)
);
}

Le composant commence par créer un effet en utilisant la fonction loadInfos() et retourne un élément <div> contenant 2 éléments pré-construits (loadIndicator et container, cf. ci-dessous) et un élément <div> de classe buttons contenant les 2 boutons pour charger plus d’articles et réduire et rafraichir la liste, le premier bouton étant pré-construit (addButton, cf. ci-dessous).

Trois variables sont définies par le composant :

  1. start : une donnée observable d’un nombre entier initialisée à zéro et qui donne l’indice du premier article à récupérer,
  2. limit : un nombre entier initialisé avec la valeur de la donnée observable count et qui donne le nombre d’articles à récupérer,
  3. id : un nombre initialisé à zéro et qui contient l’identifiant du timer créé avec la fonction setInterval().

Sans compter la fonction loadInfos(), quatre fonctions sont définies par le composant :

  1. add() : cette fonction est appelée au clic sur le bouton de chargement de plus d’articles,
  2. reset() : cette fonction est appelée au clic sur le bouton de rafraîchissement de la liste,
  3. refresh() : cette fonction est appelée par le timer,
  4. stop() : cette fonction supprime le timer précédemment créer.

Trois éléments sont pré-construits par le composant pour être accessibles par la fonction loadInfos() :

  1. loadIndicator : un élément <div> de classe bounceLoader qui est un indicateur de chargement,
  2. container : un élément <div> de classe infos qui contient la liste des articles,
  3. addButton : un élément <button> qui est le bouton de chargement de plus d’articles.

L’utilisation d’un effet est idéale dans cet exemple, la simple modification d’une donnée déclenche l’effet ce qui est le cas avec les fonctions add(), reset() et refresh() et la donnée observable start. Sans cet effet, il faudrait que ces fonctions appellent la fonction loadInfos() ce qui alourdirait le code.

Il est à noter que l’événement unmount de Room est utilisé et associé à la fonction stop() pour l’élément <div> retourné par le composant pour supprimer le timer quand le composant est supprimé du DOM de la page.

Fonction loadInfos()

Cette fonction est utilisée comme effet et sert à charger des articles.

Elle est dépendante de la donnée observable start et est donc appelée à la création de l’effet et à chaque affectation d’une valeur à start. Elle considère qu’elle fait un chargement complet de la liste des articles quand start.value vaut 0.

Le code cette fonction est représenté ici.

const loadInfos = () => {
stop();
addButton.style.visibility = "hidden";
container.insertAdjacentElement(start.value ? "afterend" : "beforebegin", loadIndicator);
!start.value && (container.style.visibility = "hidden");
const url = `/fragments/infos?start=${start}&limit=${limit}`;
fetch(url).then(r => r.text()).then(infos => {
let articles = new DOMParser().parseFromString(infos, "text/html").body;
count.value = start.value + articles.children.length;
addButton.style.visibility = infos.length ? "visible" : "hidden";
!start.value && (container.textContent = "");
container.append(...articles.children);
}).catch(() => {
container.textContent = "";
container.append(p(strong("Désolé, impossible de récuper les données.")));
}).finally(() => {
loadIndicator.remove();
container.style.visibility = "visible";
id = setInterval(refresh, refreshTimeout);
});
};

Avant de déclencher le chargement des articles, la fonction commence par :

  • supprimer le timer,
  • masquer le bouton de chargement de plus d’articles,
  • ajouter un indicateur de chargement avant ou après la liste des articles,
  • masquer la liste des articles en cas de chargement complet uniquement.

La liste des articles est obtenue par une requête à une URL de notre site qui prend comme paramètres l’indice du premier article à récupérer et le nombre d’articles à retourner. Elle retourne un fragment de HTML constitué d’une série d’éléments <article> ou une chaîne vide s’il n’y a pas d’articles pour les paramètres donnés.

Après la requête, la fonction termine le traitement en :

  • convertissant le fragment de HTML en éléments avec l’utilisation de la fonction parseFromString(),
  • mettant à jour la donnée observable count,
  • rendant visible le bouton de chargement de plus d’articles (s’il y a encore des articles),
  • ajoutant le résultat dans la liste (ou en le remplaçant en cas de chargement complet) .

En cas d’erreur, la liste des articles est vidée (un message d’erreur la remplace) et seul le bouton pour réduire et rafraîchir la liste est disponible.

Quoi qu’il arrive (erreur ou pas), l’indicateur de chargement est supprimé, la liste des articles et rendue visible et un timer pour le rafraîchissement automatique est ajouté.

Si l’URL de notre site retournait du JSON à la place d’un fragment de HTML, il faudrait le transformer en éléments HTML avec une fonction JavaScript ce qui serait bien moins efficace que d’utiliser la fonction parseFromString() qui est codée nativement dans le navigateur.

Utilisation du composant

Pour utiliser ce composant, une donnée observable count doit être créée avec la fonction createData() de Room et passée au composant. Afin de la conserver, sa valeur est enregistrées dans le localStorage.

Pour enregistrer la donnée, une fonction save() est attribuée à l’élément <div> contenant le composant pour les évènements unmount et pageHide. La donnée est ainsi sauvegardée quand le composant est supprimé du DOM ou en cas de rechargement de la page.

Ceci donne les lignes de code suivantes :

const {div} = Room.elements();
const loadBy = 4;
const count = Room.createData(loadBy);
const name = "Infos";
try {
Room.setData(count, JSON.parse(localStorage.getItem(name)) || count);
} catch (error) {}
const save = () => {
try {
localStorage.setItem(name, JSON.stringify(Room.getData(count)));
} catch (error) {}
};
element.append(div({onUnmount: save, onPageHide: save}, Infos({count, loadBy})));

Et le résultat est le suivant :

Les résumés d’articles présentés ci-dessous proviennent des fils RSS du site franceinfo.

Dernière mise à jour :