frontend / vuejs
Formulaires & v-model avancé
Explication
Ce que vous allez apprendre
- Utiliser
v-modelsur tous les types de champs de formulaire (texte, select, checkbox, radio) - Appliquer les modificateurs
.trim,.numberet.lazyselon le besoin - Lier des checkboxes multiples à un même tableau réactif
- Construire une validation dérivée avec
computedet gérer la soumission de façon robuste - Séparer clairement la responsabilité de validation de celle de soumission
Dans quel contexte ?
Un formulaire d'inscription doit valider un email, un mot de passe de longueur minimale et une confirmation identique, afficher les erreurs au fur et à mesure, désactiver le bouton de soumission tant que le formulaire n'est pas valide, et gérer un état de chargement pendant l'appel réseau. C'est un cas extrêmement courant, et Vue fournit tous les outils nécessaires sans bibliothèque externe pour un formulaire de cette complexité.
D'abord, une liaison bidirectionnelle qui s'adapte au type de champ
v-model fonctionne différemment selon le type d'élément HTML : sur un <input type="text">, il lie la valeur textuelle ; sur une checkbox, il lie un booléen (ou un tableau pour des checkboxes multiples partageant le même v-model) ; sur des radios, il lie la valeur de l'option sélectionnée. Cette adaptation automatique évite d'écrire soi-même la logique de synchronisation pour chaque type de champ.
Prérequis
Il faut avoir compris le v-model personnalisé sur un composant (leçon 5) : cette leçon l'applique concrètement aux champs de formulaire natifs.
Ensuite, trois modificateurs qui évitent du code répétitif
.trim retire automatiquement les espaces en début/fin de saisie, .number convertit la valeur en nombre (rappel du piège de typage vu implicitement dès qu'on manipule des formulaires), et .lazy synchronise la valeur seulement à l'événement change plutôt qu'à chaque frappe — utile pour éviter une validation trop agressive pendant la saisie.
| Modificateur | Effet |
|---|---|
.trim | Retire les espaces en début/fin |
.number | Convertit la saisie en nombre |
.lazy | Synchronise au change, pas à chaque frappe |
Il reste un comportement à bien comprendre sur les checkboxes multiples
Plusieurs checkboxes partageant le même v-model="langues" (un tableau réactif) s'ajoutent ou se retirent automatiquement de ce tableau selon leur état coché ou non — sans qu'il faille écrire manuellement la logique de push/splice. C'est un raccourci puissant, mais qui suppose de bien initialiser langues comme un tableau (ref([])) dès le départ.
Piège fréquent
Initialiser une checkbox multiple avec un ref(null) au lieu d'un tableau vide provoque une erreur silencieuse ou un comportement inattendu, Vue s'attendant à pouvoir manipuler un tableau pour cocher/décocher plusieurs valeurs.
Maintenant, construire une validation qui reste synchronisée avec la saisie
Une computed qui recalcule un objet d'erreurs à chaque changement des champs du formulaire reste toujours à jour automatiquement, sans qu'il faille déclencher manuellement une revalidation à chaque frappe. estValide dérive ensuite simplement de l'absence d'erreurs dans cet objet.
Astuce
Sépare toujours la logique de VALIDATION (une computed pure, sans effet de bord) de la logique de SOUMISSION (une fonction async avec gestion d'erreur réseau et état de chargement). Ce découplage rend chaque partie testable indépendamment et évite qu'un bug de validation n'affecte la gestion réseau, ou inversement.
Pour des formulaires vraiment complexes en production, des bibliothèques comme VeeValidate évitent de réinventer la gestion d'erreurs par champ, le focus automatique sur le premier champ invalide, et l'intégration avec des schémas de validation typés. Maintenant que les formulaires n'ont plus de secret, la prochaine leçon explore comment étendre le comportement du DOM lui-même avec des directives personnalisées.
Commandes & code
Formulaires & v-model avancé
v-model unifie la liaison bidirectionnelle sur tous les types de champs, avec des modificateurs utiles.
<script setup>
import { ref, reactive } from 'vue'
const texte = ref('')
const description = ref('')
const paysChoisi = ref('fr')
const accepteConditions = ref(false)
const langues = ref([]) // checkboxes multiples -> tableau
const genre = ref('') // radio -> valeur unique
const formulaire = reactive({
email: '',
age: null,
})
</script>
<template>
<!-- Modificateurs de v-model -->
<input v-model.trim="texte" placeholder="espaces automatiquement retirés" />
<input v-model.number="formulaire.age" type="number" placeholder="converti en Number" />
<input v-model.lazy="description" placeholder="sync au 'change', pas à chaque frappe" />
<select v-model="paysChoisi">
<option value="fr">France</option>
<option value="be">Belgique</option>
<option value="ch">Suisse</option>
</select>
<label><input type="checkbox" v-model="accepteConditions" /> J'accepte les conditions</label>
<!-- checkboxes multiples sur le même tableau réactif -->
<label><input type="checkbox" value="fr" v-model="langues" /> Français</label>
<label><input type="checkbox" value="en" v-model="langues" /> Anglais</label>
<label><input type="checkbox" value="es" v-model="langues" /> Espagnol</label>
<!-- radios -->
<label><input type="radio" value="h" v-model="genre" /> Homme</label>
<label><input type="radio" value="f" v-model="genre" /> Femme</label>
<textarea v-model="formulaire.email" />
</template><!-- Formulaire complet avec validation manuelle et gestion de soumission -->
<script setup>
import { reactive, computed } from 'vue'
const form = reactive({
email: '',
motDePasse: '',
confirmation: '',
})
const erreurs = computed(() => {
const e = {}
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
e.email = 'Adresse email invalide'
}
if (form.motDePasse.length < 8) {
e.motDePasse = 'Minimum 8 caractères'
}
if (form.confirmation !== form.motDePasse) {
e.confirmation = 'Les mots de passe ne correspondent pas'
}
return e
})
const estValide = computed(() => Object.keys(erreurs.value).length === 0)
const enEnvoi = ref(false)
async function soumettre() {
if (!estValide.value) return
enEnvoi.value = true
try {
const reponse = await fetch('/api/inscription', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: form.email, motDePasse: form.motDePasse }),
})
if (!reponse.ok) throw new Error(`Erreur serveur: ${reponse.status}`)
console.log('inscription réussie')
} catch (erreur) {
console.error(erreur)
} finally {
enEnvoi.value = false
}
}
</script>
<template>
<form @submit.prevent="soumettre" novalidate>
<div>
<input v-model.trim="form.email" type="email" :aria-invalid="!!erreurs.email" />
<p v-if="erreurs.email" role="alert">{{ erreurs.email }}</p>
</div>
<div>
<input v-model="form.motDePasse" type="password" />
<p v-if="erreurs.motDePasse" role="alert">{{ erreurs.motDePasse }}</p>
</div>
<div>
<input v-model="form.confirmation" type="password" />
<p v-if="erreurs.confirmation" role="alert">{{ erreurs.confirmation }}</p>
</div>
<button type="submit" :disabled="!estValide || enEnvoi">
{{ enEnvoi ? 'Envoi...' : "S'inscrire" }}
</button>
</form>
</template>Résumé
.trim,.number,.lazysont les trois modificateurs natifs dev-modelsur un input.- Sur des checkboxes multiples,
v-modellie automatiquement à un tableau des valeurs cochées. - Séparer la validation (computed dérivé) de la soumission (fonction async avec gestion d'erreur/loading).
- Pour des formulaires complexes en production, des libs comme VeeValidate ou Zod+VeeValidate évitent de réinventer la validation.
Exercices pratiques
Mission : un bouton d'inscription qui reste actif à tort
Objectif : Diagnostiquer une checkbox mal initialisée et une validation défaillante, puis livrer un formulaire d'inscription conforme.
Contexte
Un formulaire d'inscription possède un champ langues initialisé avec const langues = ref(null) relié à trois checkboxes (français, anglais, espagnol) via le même v-model="langues". Par ailleurs, le bouton "S'inscrire" reste cliquable même quand le mot de passe fait moins de 8 caractères, car la validation actuelle ne vérifie que l'email. Les deux bugs doivent être corrigés avant la mise en production.