Appearance
Form Factory à partir d’une configuration JSON
Ce tutoriel montre comment convertir une simple configuration JSON en FormGroup dynamique via une factory Angular. Vous apprendrez à traduire des validateurs déclaratifs en règles concrètes, à centraliser la logique et à identifier les points de vigilance quand le nombre de champs explose.
Le lien avec les design patterns
Réflexion
Nous nous appuyons sur les patterns de création pour encapsuler toute la logique de génération d’un formulaire à partir d’une configuration abstraite.
- Factory Method : la méthode
createForm()construit unFormGroupsans exposer la mécanique interne. Le composant consommateur délègue totalement la fabrication à la factory. - Builder Pattern : chaque champ est ajouté étape par étape (valeur, validateurs, options), ce qui permet d’enrichir progressivement la construction (validators asynchrones, groupes imbriqués, etc.).
- Abstract Factory (optionnel) : si vous avez plusieurs familles de formulaires (Reactive Forms, Template-driven, formulaires métier spécialisés), vous pouvez définir une interface
FormFactoryavec des implémentations dédiées.
Patrons de création
Les patterns de création facilitent la maintenance en isolant la complexité de construction et permettent d’ouvrir votre système à l’extension sans modifier les consommateurs.
Imaginez un service urbanisme qui doit diffuser des dizaines de formulaires différents selon la ville, la saison ou le public. Un seul moteur comprend les champs à afficher et construit les formulaires à la volée à partir d’un fichier de configuration. C’est exactement ce que nous allons mettre en place.
Comprendre l’idée de la factory
Réflexion
Une form factory prend une configuration décrivant chaque champ et retourne un FormGroup prêt pour les Reactive Forms. L’enjeu est double :
- centraliser la création des contrôles pour éviter les duplications,
- interpréter dynamiquement les validateurs et valeurs par défaut.
Nous allons structurer la configuration sous forme d’objets JSON simples, puis mappez-les vers des contrôles typés.
Commencez petit
Testez votre form factory avec trois ou quatre champs avant de généraliser. Vous éviterez de déboguer un énorme JSON dès le départ.
Définir la configuration JSON
Réflexion
Nous utilisons des objets avec name, type, value et validators. Ce format est suffisamment riche pour couvrir la plupart des champs simples tout en restant lisible.
ts
export const userFormConfig = [
{ name: 'firstName', type: 'text', validators: ['required'] },
{ name: 'email', type: 'email', validators: ['required', 'email'] },
{ name: 'age', type: 'number' }
];Ajoutez vos règles maison (minLength, pattern, etc.) au fur et à mesure.
Implémenter le service Angular
Réflexion
Nous allons créer un service injectable FormFactoryService qui :
- transforme chaque entrée en tuple
[valeurInitiale, validateurs], - résout les noms de validateurs en véritables
ValidatorFn, - expose une méthode
createFormque vos composants réutilisent.
ts
import { Injectable, inject } from '@angular/core';
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
export interface FormFieldConfig {
name: string;
type: string;
value?: unknown;
validators?: string[];
}
@Injectable({ providedIn: 'root' })
export class FormFactoryService {
private readonly formBuilder = inject(FormBuilder);
/**
* **Objectif** : construire un FormGroup à partir d'une configuration JSON.
* **Conception** : convertir chaque champ en contrôle Angular avec ses validateurs.
* @example
* formFactory.createForm(userFormConfig);
*/
createForm(config: FormFieldConfig[]): FormGroup {
const group: Record<string, unknown[]> = {};
for (const field of config) {
const validators = this.resolveValidators(field.validators);
group[field.name] = [field.value ?? '', validators];
}
return this.formBuilder.group(group);
}
/**
* **Objectif** : transformer des noms de validateurs en ValidatorFn.
* **Conception** : mapper chaque nom sur la fonction correspondante et ignorer les inconnus.
* @example
* formFactory['resolveValidators'](['required', 'email']);
*/
private resolveValidators(names?: string[]): ValidatorFn[] {
if (!names?.length) {
return [];
}
const map: Record<string, ValidatorFn> = {
required: Validators.required,
email: Validators.email,
minLength3: Validators.minLength(3)
};
return names
.map((name) => map[name])
.filter((validator): validator is ValidatorFn => Boolean(validator));
}
}Documentez vos validateurs
Gardez un tableau à jour de vos validateurs personnalisés (minLength3, mustMatch, etc.). Une mauvaise clé JSON se traduira sinon par un formulaire silencieusement incomplet.
Avantages et limites
Réflexion
Avantages
- Centralisation de la logique de création.
- Ajout rapide de nouveaux formulaires via JSON.
- Tests unitaires plus simples : il suffit de vérifier la factory.
Inconvénients
- Gestion d’erreurs moins explicite : des clés mal orthographiées ne déclenchent pas forcément d’alerte.
- Difficile à maintenir si chaque formulaire doit avoir un comportement très spécifique (affichage conditionnel, layout).
- Les contrôles complexes (datepicker custom, upload) nécessitent souvent un champ “type” plus riche ou un mapping spécifique.
Aller plus loin : valeurs dynamiques et options
Réflexion
Votre factory peut aussi accepter :
- des valeurs asynchrones (ex: préremplissage en fonction de l’utilisateur),
- des métadonnées pour l’UI (label, placeholder),
- des validateurs asynchrones (
asyncValidators).
Commencez néanmoins avec la base, puis ajoutez ces cas progressivement.
Exemple complet
Cet exemple final illustre la configuration, la factory et son intégration dans un composant. Ajoutez vos propres validateurs au mapping pour répondre aux besoins de vos formulaires métiers sans dupliquer le code.