Skip to content

Étape 12 — Recherche par nom ​

Objectifs ​

  • Créer une donnée calculée (computed) qui filtre les Pokémon par nom
  • Utiliser v-model pour lier un champ de texte à une variable réactive
  • Afficher un message quand aucun résultat ne correspond

Prérequis — Point de départ ​

Cette étape nécessite d'avoir terminé les étapes 1 à 11 (séquences 1-3). Si votre code n'est pas à jour, vous pouvez repartir de la branche etape-12-start :

bash
# Ajouter le dépôt du prof (une seule fois)
git remote add prof https://github.com/fallinov/esig-141-pokedex-vuetify.git
# Récupérer les branches et basculer
git fetch prof
git checkout -b etape-12-start prof/etape-12-start
npm install

Voir la branche sur GitHub

Contexte ​

La page d'accueil affiche tous les Pokémon. On veut ajouter un champ de recherche pour filtrer par nom en temps réel, sans modifier les données du store.

Encart oral — computed : donnée calculée réactive

Un computed est une donnée dérivée qui se recalcule automatiquement quand ses dépendances changent.

js
const filteredList = computed(() => {
  return list.value.filter(item => item.name.includes(query.value))
})
  • Si query change → filteredList est recalculé automatiquement
  • Si list change → filteredList est recalculé automatiquement
  • Vue.js ne recalcule que quand c'est nécessaire (mise en cache)
  • On ne modifie jamais les données sources — on crée une vue filtrée

Tâches ​

1. Ajouter le champ de recherche ​

Dans src/pages/index.vue, ajoutez une variable réactive et un champ de texte au-dessus de la grille :

ComposantRôleDocumentation
<v-text-field>Champ de saisie avec label, icône, bouton effacerText Fields

Dans le <script setup>, créez la variable réactive :

js
const searchQuery = ref('')

Dans le <template>, ajoutez une <v-row> entre le titre et la grille de cartes :

vue
<v-row class="mb-4">
  <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>
</v-row>
  • v-model="searchQuery" : lie la valeur du champ à la variable réactive (binding bidirectionnel)
  • clearable : ajoute un bouton ✕ pour vider le champ
  • prepend-inner-icon : affiche une icône de recherche à l'intérieur du champ
  • hide-details : masque l'espace réservé aux messages de validation
  • variant="outlined" : style avec bordure
  • density="compact" : réduit la hauteur du champ

2. Créer le computed de filtrage ​

Dans le <script setup>, créez un computed qui filtre les Pokémon par nom :

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

Points importants :

  • Si le champ est vide → on retourne tous les Pokémon (pas de filtre)
  • toLowerCase() rend la recherche insensible à la casse ("pika" trouve "Pikachu")
  • includes() cherche une sous-chaîne ("chu" trouve "Pikachu")
  • On ne modifie jamais pokemonStore.pokemons — le computed crée un nouveau tableau

3. Utiliser le computed dans le template ​

Remplacez pokemonStore.pokemons par filteredBySearch dans le v-for de la grille :

vue
<v-col
  v-for="pokemon in filteredBySearch"
  :key="pokemon.id"
  cols="12"
  sm="6"
  md="4"
  lg="3"
>

4. Afficher un message si aucun résultat ​

Ajoutez un <v-alert> qui s'affiche quand la liste filtrée est vide :

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

<v-row v-else>
  <!-- la grille de cartes existante -->
</v-row>

Projet perso — À faire en parallèle

Dans votre projet individuel :

  1. Ajoutez un champ de recherche avec v-model
  2. Créez un computed qui filtre vos données par nom
  3. Utilisez le computed dans votre v-for
  4. Gérez le cas "aucun résultat"

Tests ​

  • Taper "pika" affiche uniquement les Pokémon dont le nom contient "pika"
  • Effacer le champ réaffiche tous les Pokémon
  • La recherche est insensible à la casse ("PIKA" = "pika" = "Pika")
  • Si aucun Pokémon ne correspond, le message "Aucun Pokémon ne correspond" s'affiche
  • Le bouton ✕ (clearable) vide le champ et réaffiche tous les Pokémon
  • 0 erreurs dans la console

Solution ​

src/pages/index.vue
vue
<template>
  <v-container>
    <h1 class="text-h3 text-center my-6">
      Pokédex
      <span class="text-subtitle-1">({{ pokemonStore.totalPokemons }})</span>
    </h1>

    <!-- Barre de recherche -->
    <v-row class="mb-4">
      <!--
      Champ de recherche par nom
        * v-model lie la valeur au ref searchQuery (binding bidirectionnel)
        * clearable ajoute un bouton pour vider le champ
        * prepend-inner-icon ajoute une icône à l'intérieur du 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>
    </v-row>

    <!--
    Message si aucun résultat
      * v-if vérifie que la liste filtrée est vide
    -->
    <v-alert
      v-if="filteredBySearch.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 filteredBySearch (et non pokemonStore.pokemons)
      * Ainsi la grille se met à jour automatiquement quand on tape dans le champ
    -->
    <v-row v-else>
      <v-col
        v-for="pokemon in filteredBySearch"
        :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()

// Variable réactive pour la recherche
// ref() est auto-importé grâce à unplugin-auto-import
const searchQuery = ref('')

/**
 * Computed : filtre les Pokémon par nom
 * Se recalcule automatiquement quand searchQuery change
 * Ne modifie jamais les données du store (pokemonStore.pokemons reste intact)
 */
const filteredBySearch = computed(() => {
  // Si le champ est vide, retourner tous les Pokémon
  if (!searchQuery.value) return pokemonStore.pokemons

  // Convertir la recherche en minuscules pour une comparaison insensible à la casse
  const query = searchQuery.value.toLowerCase()

  // Filtrer : ne garder que les Pokémon dont le nom contient la recherche
  return pokemonStore.pokemons.filter(pokemon =>
    pokemon.name.toLowerCase().includes(query),
  )
})
</script>

Commit

bash
git add -A
git commit -m "feat: recherche Pokémon par nom avec computed"

Documentation pour les cours de développement web