Étape 12 — Recherche par nom
Objectifs
- Créer une donnée calculée (
computed) qui filtre les Pokémon par nom - Utiliser
v-modelpour 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 :
# 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 installContexte
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.
const filteredList = computed(() => {
return list.value.filter(item => item.name.includes(query.value))
})- Si
querychange →filteredListest recalculé automatiquement - Si
listchange →filteredListest 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 :
| Composant | Rôle | Documentation |
|---|---|---|
<v-text-field> | Champ de saisie avec label, icône, bouton effacer | Text Fields |
Dans le <script setup>, créez la variable réactive :
const searchQuery = ref('')Dans le <template>, ajoutez une <v-row> entre le titre et la grille de cartes :
<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 champprepend-inner-icon: affiche une icône de recherche à l'intérieur du champhide-details: masque l'espace réservé aux messages de validationvariant="outlined": style avec borduredensity="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 :
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— lecomputedcrée un nouveau tableau
3. Utiliser le computed dans le template
Remplacez pokemonStore.pokemons par filteredBySearch dans le v-for de la grille :
<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 :
<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 :
- Ajoutez un champ de recherche avec
v-model - Créez un
computedqui filtre vos données par nom - Utilisez le
computeddans votrev-for - 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
<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
git add -A
git commit -m "feat: recherche Pokémon par nom avec computed"