Skip to content

Étape 13 — Filtre par type et tri alphabétique

Objectifs

  • Filtrer les Pokémon par type avec un <v-select>
  • Trier les Pokémon par nom (A-Z / Z-A) avec Array.sort() et localeCompare()
  • Chaîner plusieurs computed pour combiner recherche, filtre et tri

Résultat attendu

Page d'accueil avec recherche, filtre par type et tri alphabétique

Contexte

On a déjà la recherche par nom (étape 12). On veut maintenant pouvoir filtrer par type (Feu, Eau, Plante...) et trier par ordre alphabétique. Ces trois transformations doivent fonctionner ensemble.

Encart oral — Chaîner les computed

Chaque computed prend le résultat du précédent comme source de données. On construit un pipeline de transformation :

pokemonStore.pokemons     (données brutes)

  filteredByType           (filtre par type)

  filteredBySearch          (filtre par nom)

  sortedPokemons            (tri alphabétique)

Avantages :

  • Chaque étape a une responsabilité unique (un filtre ou un tri)
  • On peut ajouter ou retirer une étape sans toucher aux autres
  • Vue.js recalcule uniquement les étapes impactées par un changement

Tâches

1. Ajouter le filtre par type

Dans le <script setup>, ajoutez une variable réactive pour le type sélectionné :

js
const selectedType = ref(null)

Dans le <template>, ajoutez un <v-select> dans la <v-row> de recherche, à côté du champ de texte :

ComposantRôleDocumentation
<v-select>Menu déroulant avec liste de choixSelects
vue
<v-col cols="12" sm="6" md="4">
  <v-select
    v-model="selectedType"
    :items="pokemonStore.types"
    item-title="name"
    item-value="id"
    label="Filtrer par type"
    prepend-inner-icon="mdi-filter"
    clearable
    hide-details
    variant="outlined"
    density="compact"
  />
</v-col>
  • :items="pokemonStore.types" : les types chargés depuis l'API (Feu, Eau, Plante...)
  • item-title="name" : propriété affichée dans le menu (le nom du type)
  • item-value="id" : propriété utilisée comme valeur (l'ID du type)
  • clearable : permet de désélectionner le filtre

2. Créer le computed de filtre par type

Ajoutez un computed avant filteredBySearch (l'ordre est important pour le chaînage) :

js
const filteredByType = computed(() => {
  if (!selectedType.value) return pokemonStore.pokemons
  return pokemonStore.pokemons.filter(pokemon =>
    pokemon.types.includes(selectedType.value),
  )
})
  • Si aucun type n'est sélectionné (null) → on retourne tous les Pokémon
  • pokemon.types est un tableau d'IDs (ex: [1, 3]) — on vérifie si le type sélectionné est dedans

3. Chaîner la recherche sur le filtre par type

Modifiez filteredBySearch pour qu'il utilise filteredByType comme source (au lieu de pokemonStore.pokemons) :

js
const filteredBySearch = computed(() => {
  if (!searchQuery.value) return filteredByType.value 
  const query = searchQuery.value.toLowerCase()
  return filteredByType.value.filter(pokemon =>
    pokemon.name.toLowerCase().includes(query),
  )
})

Le chaînage est maintenant en place : pokemons → filteredByType → filteredBySearch.

4. Ajouter le tri alphabétique

Dans le <script setup>, ajoutez une variable réactive pour l'ordre de tri :

js
const sortOrder = ref('asc')

Ajoutez la fonction pour inverser l'ordre :

js
function toggleSort() {
  sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
}

Créez le computed de tri (le dernier de la chaîne) :

js
const sortedPokemons = computed(() => {
  return [...filteredBySearch.value].sort((a, b) => {
    const comparison = a.name.localeCompare(b.name, 'fr')
    return sortOrder.value === 'asc' ? comparison : -comparison
  })
})

Points importants :

  • [...filteredBySearch.value] crée une copie du tableau (ne pas trier l'original)
  • localeCompare('fr') gère correctement les accents et caractères spéciaux français
  • localeCompare retourne : négatif si a < b, 0 si a = b, positif si a > b
  • On multiplie par -1 (en inversant le signe) pour le tri descendant

5. Ajouter le bouton de tri dans le template

Ajoutez un troisième <v-col> dans la <v-row> de contrôles :

vue
<v-col cols="12" md="4" class="d-flex align-center">
  <v-btn
    variant="outlined"
    :prepend-icon="sortOrder === 'asc'
      ? 'mdi-sort-alphabetical-ascending'
      : 'mdi-sort-alphabetical-descending'"
    @click="toggleSort"
  >
    Tri {{ sortOrder === 'asc' ? 'A → Z' : 'Z → A' }}
  </v-btn>
</v-col>

6. Utiliser sortedPokemons dans le template

Remplacez filteredBySearch par sortedPokemons dans le v-for et dans le v-if du message "aucun résultat" :

vue
<v-alert
  v-if="sortedPokemons.length === 0"
  type="info"
  variant="tonal"
  class="mb-6"
>
  Aucun Pokémon ne correspond à votre recherche.
</v-alert>

<v-row v-else>
  <v-col
    v-for="pokemon in sortedPokemons"
    :key="pokemon.id"
    cols="12"
    sm="6"
    md="4"
    lg="3"
  >
    <pokemon-card :pokemon="pokemon" />
  </v-col>
</v-row>

Schéma — Flux des computed chaînés

Projet perso — À faire en parallèle

Dans votre projet individuel :

  1. Ajoutez un filtre pertinent pour vos données (catégorie, statut, etc.)
  2. Ajoutez un tri alphabétique avec un bouton toggle
  3. Chaînez vos computed : données → filtre → recherche → tri

Références utiles

Tests

  • Le <v-select> affiche la liste des types (Feu, Eau, Plante...)
  • Sélectionner "Feu" n'affiche que les Pokémon de type Feu
  • Vider le filtre type réaffiche tous les Pokémon
  • Le filtre type et la recherche fonctionnent ensemble (ex: type Feu + recherche "sal")
  • Le bouton de tri alterne entre A-Z et Z-A
  • Le tri fonctionne avec la recherche et le filtre actifs
  • 0 erreurs dans la console

Solution

src/pages/index.vue
vue
<template>
  <!-- Conteneur principal — centre le contenu et limite la largeur -->
  <v-container>
    <h1 class="text-h3 text-center my-6">
      Pokédex
      <span class="text-subtitle-1">({{ pokemonStore.totalPokemons }})</span>
    </h1>

    <!--
    Barre de recherche, filtre et tri
      * v-row avec 3 colonnes pour les contrôles
    -->
    <v-row class="mb-4">
      <!--
      Champ de recherche par nom
        * v-model lie la valeur au ref searchQuery
        * clearable ajoute un bouton pour vider le champ
      -->
      <v-col cols="12" sm="6" md="4">
        <v-text-field
          v-model="searchQuery"
          label="Rechercher un Pokémon"
          prepend-inner-icon="mdi-magnify"
          clearable
          hide-details
          variant="outlined"
          density="compact"
        />
      </v-col>

      <!--
      Filtre par type
        * v-select affiche un menu déroulant avec les types disponibles
        * :items utilise les types chargés depuis l'API
        * item-title et item-value définissent quelles propriétés afficher/utiliser
      -->
      <v-col cols="12" sm="6" md="4">
        <v-select
          v-model="selectedType"
          :items="pokemonStore.types"
          item-title="name"
          item-value="id"
          label="Filtrer par type"
          prepend-inner-icon="mdi-filter"
          clearable
          hide-details
          variant="outlined"
          density="compact"
        />
      </v-col>

      <!--
      Bouton de tri
        * Alterne entre tri ascendant (A→Z) et descendant (Z→A)
        * L'icône change selon l'ordre actuel
      -->
      <v-col cols="12" md="4" class="d-flex align-center">
        <v-btn
          variant="outlined"
          :prepend-icon="sortOrder === 'asc'
            ? 'mdi-sort-alphabetical-ascending'
            : 'mdi-sort-alphabetical-descending'"
          @click="toggleSort"
        >
          Tri {{ sortOrder === 'asc' ? 'A → Z' : 'Z → A' }}
        </v-btn>
      </v-col>
    </v-row>

    <!--
    Message si aucun résultat
      * Affiché si la recherche ou le filtre ne donne aucun résultat
    -->
    <v-alert
      v-if="sortedPokemons.length === 0"
      type="info"
      variant="tonal"
      class="mb-6"
    >
      Aucun Pokémon ne correspond à votre recherche.
    </v-alert>

    <!--
    Grille de cartes Pokémon
      * On itère sur sortedPokemons (le dernier computed de la chaîne)
    -->
    <v-row v-else>
      <v-col
        v-for="pokemon in sortedPokemons"
        :key="pokemon.id"
        cols="12"
        sm="6"
        md="4"
        lg="3"
      >
        <pokemon-card :pokemon="pokemon" />
      </v-col>
    </v-row>
  </v-container>
</template>

<script setup>
import { usePokemonStore } from '@/stores/pokemonStore'
import PokemonCard from '@/components/PokemonCard.vue'

// Récupération du store Pokémon
const pokemonStore = usePokemonStore()

/**
 * Variables réactives pour la recherche, le filtre et le tri
 * ref() est auto-importé grâce à unplugin-auto-import
 */
const searchQuery = ref('')
const selectedType = ref(null)
const sortOrder = ref('asc')

/**
 * COMPUTED CHAÎNÉS : Chaque étape filtre ou transforme le résultat de la précédente
 * pokemons → filteredByType → filteredBySearch → sortedPokemons
 */

/**
 * Étape 1 : Filtrer par type
 * Si un type est sélectionné, ne garder que les Pokémon qui possèdent ce type
 */
const filteredByType = computed(() => {
  if (!selectedType.value) return pokemonStore.pokemons
  return pokemonStore.pokemons.filter(pokemon =>
    pokemon.types.includes(selectedType.value),
  )
})

/**
 * Étape 2 : Filtrer par recherche
 * Utilise filteredByType comme source (chaînage)
 * La recherche est insensible à la casse (toLowerCase)
 */
const filteredBySearch = computed(() => {
  if (!searchQuery.value) return filteredByType.value
  const query = searchQuery.value.toLowerCase()
  return filteredByType.value.filter(pokemon =>
    pokemon.name.toLowerCase().includes(query),
  )
})

/**
 * Étape 3 : Trier par nom
 * [...] crée une copie pour ne pas modifier le tableau filtré
 * localeCompare('fr') gère les accents français
 */
const sortedPokemons = computed(() => {
  return [...filteredBySearch.value].sort((a, b) => {
    const comparison = a.name.localeCompare(b.name, 'fr')
    return sortOrder.value === 'asc' ? comparison : -comparison
  })
})

/**
 * Inverse l'ordre de tri entre ascendant et descendant
 */
function toggleSort() {
  sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
}
</script>

Commit

bash
git add -A
git commit -m "feat: filtre par type et tri alphabétique avec computed chaînés"

Documentation pour les cours de développement web