Skip to content

Instantly share code, notes, and snippets.

@jpbecotte
Last active November 12, 2018 15:31
Show Gist options
  • Save jpbecotte/3595cf95c43fd6e7880618f99cf8fa55 to your computer and use it in GitHub Desktop.
Save jpbecotte/3595cf95c43fd6e7880618f99cf8fa55 to your computer and use it in GitHub Desktop.
Le gestion des collections avec Vuex

Le gestion des collections avec Vuex (ou comment bien intégrer vos appels API dans le store)

Ce document traite de la partie «lecture» des données. Vous savez comment utiliser Axios pour aller chercher les données donc il suffit maintenant de savoir comment les organiser.

On parle aussi de module de collection, car votre module gèrera une collection (liste sans ordre apparant) d'objets.

La première étape consiste à bien recenser vos actions, car ce sont elles qui détermineront la suite des chose. En gros, faites une liste à partir des différentes parties d'écran. Pour chaque action:

  • Y a-t-il des paramètres?
  • Quels sont les appels d'API nécessaires? On veut aussi le moins possibles d'actions sans toutefois charger des données inutilement. Par exemple, pour les compétences, on veut pouvoir:
  • Récupérer la liste des compétences (pour l'afficher. On n'a besoin que du Code, ID et libellé).
  • Récupérer une compétence pour afficher le devis ministériel
  • Récupérer les éléments d'analyse d'une compétence (savoirs, etc.)

Composer la collection

L'état de votre module sera composé simplement d'une propriété «all» (par défaut vide [], pour que vos composants puissent faire un rendu initial). Cette propriété indique une liste d'objets (JS) qui représentera chacun des objets de la listes.

  state() {
    return {
      all: [],
    };
  },

Ainsi, dans votre composant, vous aurez:

computed: {
  competences() {
    return this.$store.state.competences.all;
  }
}

L'action et la mutation ressembleront à ceci:

  actions: {
    getCompetences({ commit }) {
      return axios.get('/api/Competences', {
        headers: {
          Authorization: `bearer ${JSON.parse(localStorage.getItem('token')).access_token}`,
        },
      }).then((response) => {
        commit('UPDATE_COLLECTION', { items: response.data })
      });
    },
  },

  mutations: {
    UPDATE_COLLECTION(state, { items }) {
      items.forEach(item => {
        const index = state.all.findIndex(x => x.id === item.id);
        if (index < 0) {
          /* l'item ne se trouve pas dans la liste: il faut l'ajouter */
          state.all.push(item);
        } else {
          /* l'item se trouve dans la liste, il faut le remplacer */
          state.all.splice(index, 1, {
            ...state.all[index], /* on copie l'item tel qul actuellement dans l'état ... */
            ...item, /* et on fusionne le nouvel item ... */
          });
        }
      });
    },
  },

Ici, on utilise une stratégie non destructrice sur la collection. C'est un avantage: on peut mettre la collection à jour plusieurs fois sans à avoir à effacer ce qui a déjà été télélchargé! Bon, cette stratégie présente un petit défaut qui, pour l'instant, ne sera pas problématique: il faudrait enlever les items qui ont disparus (compétence supprimée, par exemple).

Mettre à jour la collection avec des items individuels

Notre store contient ce qu'il faut pour afficher une liste de compétence. Cependant, il faut rajouter des choses pour qu'on puisse afficher l'une d'entre elles.

Pour cela, nous utiliserons la stratégie d'éclatement-greffon. Autrement dit, on éclate l'objet de la compétence (en éléments de compétences et savoirs), puis, une fois que cet objet est créé, on met à jour la collection.

Ainsi, la liste comprendra des compétences éclatées et non éclatées. C'est exactement ce que nous voulons!

Nous avions dit que le module des compétences comporterait deux actions de lecture de compétences: l'une pour le l'affichage de l'onglet «aperçu» (le devis ministériel) et l'autre pour l'affichage de l'écran «analyse».

Dans les deux cas, nous devrons gérer le statut de chaque object dans la collection. Le statut se résume aux états suivants:

  • NOT_LOADED (undefined par défaut): l'information correspondante n'a pas été encore téléchargée à partir de l'API.
  • LOADING: L'appel est fait à l'API, mais la réponse n'est pas encore rendue.
  • LOADED: Le data correspondant a été mis à jour dans le store.

Ainsi, nous aurons l'action suivante (je prendrai seulement l'exemple de l'aperçu):

  actions: {
    ...
    getCompetenceForDevis({ commit, dispatch }, { id }) {
      commit('UPDATE_ITEM_LOADING_STATUS', 'forDevisStatus', 'LOADING');
      return axios.get(`/api/Competences/${id}`, {
        headers: {
          Authorization: `bearer ${JSON.parse(localStorage.getItem('token')).access_token}`,
        },
      }).then((response) => {
        const item = response.data;
        /* À faire: le chaînage des appels pour éléments de compétence */
        /* au final, la variable item, un objet, contiendra la compétence, les éléments de compétence et es savoir */
        return dispatch('loadCompetenceElements');
      }).then((itemFinal) => {
        commit('UPDATE_ITEM_LOADING_STATUS', id, 'forDevisStatus', 'LOADED');
        commit('UPDATE_COLLECTION', { items: [itemFinal] });
      });
    },
  }

On peut ré-utiliser le commit fait pour un autre action (ici UPDATE_COLLECTION). C'est pratique!

Voici le commit UPDATE_ITEM_LOADING_STATUS:

  mutations: {
    ...
    UPDATE_ITEM_LOADING_STATUS(state, { id, prop, status }) {
      /* on prend pour acquis que la liste est déjà loadée */
      const item = state.all.find(x => x.id === id);
      item[prop] = status;
    },
  },

La suite

Finalement, il nous reste à :

  • Dispatcher les actions au bon endroit
  • Utiliser (consommer) les données du store au bon endroit
  • Créer les actions de modifications des données (ajout de savoir, modification de données, suppression)

Pour consommer un élément de la collection, c'est très simple:

      computed: {
        competences() {
          return this.$store.state.competences.all;
        },      
        competence() {
          const id = this.$route.params.id;
          return this.competences.find(x => x.id === id);
        },
      },
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment