AngularAtlaséd. 22.2

Chap. 09Formulaires47 / 75

Signal Forms — avancé

Validation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.

v22 ~0 min de lecture Documentation officielle

Après les bases de Signal Forms, voici les patterns que vous utilisez dans les vrais formulaires : listes dynamiques, validation asynchrone, champs conditionnels, schémas réutilisables et validation inter-champs. Tout s’exprime dans le schéma, pas dans le template : le template reste simple.

Listes dynamiques : applyEach

interface Invoice {
  client: string;
  lines: { description: string; qty: number; price: number }[];
}

readonly model = signal<Invoice>({ client: '', lines: [{ description: '', qty: 1, price: 0 }] });

readonly f = form(this.model, (p) => {
  required(p.client);
  applyEach(p.lines, (line) => {          // les mêmes règles pour chaque élément
    required(line.description);
    min(line.qty, 1);
  });
});

// On ajoute / retire des éléments en modifiant le MODEL : l’arbre de champs se met à jour tout seul
addLine() {
  this.model.update((m) => ({ ...m, lines: [...m.lines, { description: '', qty: 1, price: 0 }] }));
}
LiveUne facture à lignes dynamiques
DescriptionQtéPrixTotal
3.500 RON
Total de la facture : 3.500 RONvalide
Ajoutez des lignes, videz une description ou mettez la quantité à 0 : chaque ligne est validée par la même règle applyEach.

Validation asynchrone : validateHttp / validateAsync

import { validateHttp, validateAsync } from '@angular/forms/signals';

form(this.model, (p) => {
  required(p.username);
  minLength(p.username, 3);

  // validateHttp : une requête HTTP (via HttpClient → passe par les interceptors)
  validateHttp(p.username, {
    request: ({ value }) => `/api/username-available?username=${value()}`,
    debounce: 400,                       // v22
    onSuccess: (res: { available: boolean }) =>
      res.available ? undefined : { kind: 'taken', message: 'Nom d’utilisateur déjà pris' },
    onError: () => ({ kind: 'network', message: 'Vérification impossible' }),
  });

  // validateAsync : n’importe quelle resource (p. ex. un SDK, IndexedDB)
  validateAsync(p.iban, {
    params: ({ value }) => value(),
    factory: (iban) => resource({ params: iban, loader: ({ params }) => this.bank.check(params) }),
    onSuccess: (ok) => (ok ? undefined : { kind: 'iban', message: 'IBAN invalide' }),
    onError: () => undefined,
  });
});

// Dans le template : f.username().pending() vaut true pendant la vérification
// v22 : f.username().reloadValidation() : relance manuellement la validation asynchrone
LiveLe nom d’utilisateur est-il disponible ? (validateHttp + debounce)
pending: false · valid: false
Saisissez « admin » ou « angular » → pris. Observez l’état pending pendant la vérification.

Champs conditionnels : hidden, disabled, readonly

form(this.model, (p) => {
  // hidden : le champ n’est ni affiché NI validé
  hidden(p.company, ({ valueOf }) => valueOf(p.type) !== 'company');
  required(p.company);

  // disabled : affiché, non modifiable, non validé (avec une raison facultative)
  disabled(p.coupon, ({ valueOf }) => !valueOf(p.hasCoupon));
  disabled(p.email, () => this.user.isLocked() && 'Compte verrouillé'); // une string = la raison

  // readonly : affiché, non modifiable, mais sa valeur est envoyée
  readonly(p.id);

  // required conditionnel
  required(p.phone, { when: ({ valueOf }) => valueOf(p.contactBy) === 'phone' });
});

// template : @if (!f.company().hidden()) { <input [formField]="f.company" /> }

Schémas réutilisables : schema, apply, applyWhen

// Un schéma réutilisable : vous le définissez une fois et l’appliquez partout
export const addressSchema = schema<Address>((a) => {
  required(a.street);
  required(a.city);
  pattern(a.zip, /^\d{6}$/, { message: 'Code postal : 6 chiffres' });
});

form(this.model, (p) => {
  apply(p.billing, addressSchema);                                          // toujours
  applyWhen(p.shipping, ({ valueOf }) => !valueOf(p.sameAsBilling), addressSchema); // conditionnel
  hidden(p.shipping, ({ valueOf }) => valueOf(p.sameAsBilling));
});
LiveCheckout : particulier / entreprise, deux adresses, code promo
Adresse de facturation
disabled: true
invalid
Choisissez « Entreprise » → l’identifiant fiscal et la raison sociale apparaissent et sont validés. Décochez « même adresse » → l’adresse de livraison apparaît avec les mêmes règles. Cochez le code promo → le champ devient actif et obligatoire.

Validation inter-champs

// Validation d’un champ qui dépend d’un autre
validate(p.endDate, ({ value, valueOf }) =>
  value() < valueOf(p.startDate) ? { kind: 'range', message: 'La date de fin précède le début' } : undefined,
);

// validateTree : une règle au niveau du groupe qui peut signaler des erreurs sur plusieurs champs
validateTree(p, ({ value, fieldTreeOf }) => {
  const { min, max } = value();
  return min > max
    ? [
        { kind: 'range', message: 'Le minimum est supérieur', fieldTree: fieldTreeOf(p.min) },
        { kind: 'range', message: 'Le maximum est inférieur', fieldTree: fieldTreeOf(p.max) },
      ]
    : undefined;
});

Schémas Zod / Valibot

// Vous avez déjà des schémas Zod / Valibot / ArkType ? Utilisez-les directement (Standard Schema)
import { validateStandardSchema } from '@angular/forms/signals';
import { z } from 'zod';

const UserSchema = z.object({ name: z.string().min(2), email: z.email() });

form(this.model, (p) => {
  validateStandardSchema(p, UserSchema); // les erreurs atterrissent sur les champs correspondants
});

Pièges fréquents

  • De la logique dans le template (@if (model().type === 'company') autour des champs) sans hidden() dans le schéma → le champ masqué est quand même validé et le formulaire reste invalide. Mettez la règle dans le schéma et lisez hidden().
  • Validation asynchrone sans debounce → une requête à chaque frappe.
  • Listes modifiées par mutation (model().lines.push()) → l’arbre ne se met pas à jour. Utilisez model.update avec un nouveau tableau.
  • Erreurs sur les groupes : une erreur sur p (la racine) n’apparaît pas à côté d’un input. Ciblez le champ avec fieldTree (ou affichez errorSummary()).

Chap. 09 · 0 sur 3 terminées

Leçon terminée ?

Marquez-la : elle apparaîtra sur votre carte, dans le sommaire et sur la page d’accueil.

Se marque toute seule une fois la leçon lue en entier.

  1. Signal Forms — les bases
  2. Signal Forms — avancé
  3. Reactive Forms typés
Voir toute votre carte

Les 75 pages · tapez pour filtrer

  • 01Nouveautés d’Angular 21 & 22Tous les changements importants de la v21 et de la v22, en bref, avec un lien vers la page qui explique chacun.Départ
  • 01Votre première application, pas à pasUne liste de lecture construite d’un composant vide jusqu’à une application terminée — signals, @for, inputs et outputs, un service et un formulaire, pas à pas.Départ
  • 01Décisions rapides« Où mettre ça ? », « Signal ou Observable ? », « Shared ou feature ? » — des réponses courtes aux questions de tous les jours.Départ
  • 01Questions d’entretien Angular60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.Départ
  • 02Standalone et bootstrapComment une application démarre sans NgModule : bootstrapApplication, app.config.ts, imports dans le composant.Fondamentaux modernes
  • 02Data binding et événementsInterpolation, bindings de propriétés et d’attributs, événements avec $event et filtres de touches, et comment [( )] est construit — ce qui entre dans le DOM et ce qui en revient.Fondamentaux modernes
  • 02Control Flow : @if, @for, @switchLa syntaxe de blocs qui remplace *ngIf/*ngFor/ngSwitch, avec track, @empty et vérifications d’exhaustivité.Fondamentaux modernes
  • 02@let et les nouvelles expressions de templateVariables locales avec @let, fonctions fléchées, spread, regex, instanceof, commentaires dans les balises.Fondamentaux modernes
  • 02@defer — Lazy Loading dans les templatesChargez les composants lourds uniquement quand c’est nécessaire : triggers, prefetch, placeholder, loading, error.Fondamentaux modernes
  • 02Bindings de classe et de stylePourquoi [class.x] et [style.x] ont remplacé ngClass et ngStyle (avec migration automatique depuis la v21).Fondamentaux modernes
  • 02Styles des composants et encapsulation:host, les trois modes d’encapsulation, les thèmes avec des variables CSS, et pourquoi ::ng-deep est un dernier recours.Fondamentaux modernes
  • 02Pipes : quand et commentPipes pures vs impures, pipes personnalisées, et comment choisir entre une pipe, computed() et une méthode.Fondamentaux modernes
  • 03signal() et computed()Les primitives réactives : état modifiable, état dérivé, égalité, untracked et règles d’immutabilité.Signals et réactivité
  • 03effect() — quand OUI, quand NONEffets de bord synchronisés avec les Signals : localStorage, logs, API impératives. Et leurs pièges.Signals et réactivité
  • 03linkedSignal() — état dérivé modifiableUn signal qui se réinitialise quand sa source change, mais que vous pouvez aussi modifier à la main.Signals et réactivité
  • 03resource, rxResource, httpResourceDes données asynchrones sous forme de Signals : status, value, error, reload, annulation automatique. Stable depuis la v22.Signals et réactivité
  • 03debounced() — signals différésLa version « debounce » d’un signal, sans RxJS. Parfait pour la recherche.Signals et réactivité
  • 03Listes : rechercher, filtrer, trier, paginerUne chaîne de signals computed qui ne recalcule que ce qui a changé, avec les filtres dans l’URL pour qu’un lien rouvre la même vue.Signals et réactivité
  • 03Les Observables depuis zéroCe qu’est un Observable, subscribe et unsubscribe, les opérateurs que vous utilisez chaque semaine, froid ou chaud — et quand un signal suffit.Signals et réactivité
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — et quand RxJS reste le bon choix.Signals et réactivité
  • 04input() et output()Les inputs comme Signals (required, transform, alias) et les outputs typés. Les données descendent, les événements remontent.Composants : communication
  • 04model() — binding bidirectionnelLe « banana-in-a-box » [( )] avec une seule API. Quand l’utiliser, et quand input + output est préférable.Composants : communication
  • 04viewChild et contentChildLes queries comme Signals : viewChild, viewChildren, contentChild, contentChildren, l’option read.Composants : communication
  • 04Lifecycle et DestroyRefQuels hooks comptent encore à l’ère des Signals, afterNextRender, DestroyRef et ngOnChanges typé.Composants : communication
  • 04Projection de contenung-content avec select, plusieurs slots, contenu par défaut et le piège de la projection conditionnelle.Composants : communication
  • 04ng-template et ngTemplateOutletLes templates comme valeurs : les render props en Angular, contexte typé et personnalisation des composants.Composants : communication
  • 05Smart vs DumbContainer vs présentation : qui détient l’état, qui se contente d’afficher, et comment bien les séparer.Conception des composants
  • 05Composants génériques <T>Un DataTable<T> ou un Select<T> correctement typé, avec des templates pour les cellules.Conception des composants
  • 05Composants dynamiquesNgComponentOutlet, createComponent avec inputBinding/outputBinding, et un tableau de bord construit à partir d’une configuration.Conception des composants
  • 05Directives et composition d’hôteLes directives d’attribut, les métadonnées host: {} et la Directive Composition API (hostDirectives).Conception des composants
  • 05Contrôles de formulaire personnalisésFormValueControl (Signal Forms) vs ControlValueAccessor : une notation par étoiles, dans les deux variantes.Conception des composants
  • 05Change DetectionOnPush est la valeur par défaut depuis la v22, Zone.js a disparu depuis la v21. Ce qui déclenche le rendu et comment éviter les mauvaises surprises.Conception des composants
  • 06inject() et @Service()Le nouveau décorateur @Service() de la v22, inject() vs le constructeur, et le contexte d’injection.Dependency Injection
  • 06Providers et hiérarchie des injecteursRoot vs route vs composant, useClass/useValue/useFactory/useExisting, et les modificateurs optional/self/skipSelf.Dependency Injection
  • 06InjectionToken et provideX()Configuration typée, fonctions provideFeature(), makeEnvironmentProviders et initializers.Dependency Injection
  • 06injectAsync() — services en Lazy LoadingChargez un service lourd uniquement quand vous en avez besoin, avec prefetch à l’idle.Dependency Injection
  • 07Structure des dossierscore / shared / features, ce que contient chacun, et à quoi ressemble l’intérieur d’une feature.Architecture d’application
  • 07Shared vs featureQuand déplacer un composant dans shared et (surtout) quand NE PAS le faire. Une checklist concrète.Architecture d’application
  • 07Interfaces et modèlesInterface en ligne ou fichier séparé ? DTO vs modèle de domaine, type vs interface, où les ranger.Architecture d’application
  • 07State Management avec les SignalsStores simples à base de Signals, facades, état local vs global, et quand NgRx devient nécessaire.Architecture d’application
  • 07Guide de style et nommageLes conventions officielles actuelles : noms de fichiers sans suffixes, protected, readonly, ordre des membres.Architecture d’application
  • 07Barrels, alias et frontièresindex.ts, alias de chemins, API publique d’une feature et règles de dépendances vérifiables automatiquement.Architecture d’application
  • 07Erreurs : @boundary et ErrorHandlerLe nouveau bloc @boundary de la v22.2, l’ErrorHandler global, les erreurs HTTP et les erreurs de resource.Architecture d’application
  • 08Routing moderne et Lazy LoadingloadComponent, routes enfants, paramètres comme inputs et héritage des paramètres (le nouveau comportement par défaut de la v22).Routing
  • 08Guards et resolverscanActivate, canMatch, canDeactivate et resolve sous forme de fonctions, avec redirections via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — les basesDes formulaires bâtis sur les Signals : form(), [formField], validation par schéma. Stable depuis la v22.Formulaires
  • 09Signal Forms — avancéValidation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.Formulaires
  • 09Reactive Forms typésFormGroup typé, nonNullable, FormArray — et leur cohabitation avec les Signal Forms.Formulaires
  • 10HttpClient, interceptors et couche d’APIHttpClient sans configuration (v21), Fetch par défaut (v22), interceptors fonctionnels et une couche d’API avec mapping des DTO.HTTP et données
  • 11Performance — une checklistCe qui compte vraiment : OnPush + Signals, track, @defer, routes en Lazy Loading, images, bundles et Hydration.Qualité et performance
  • 11Tests avec VitestVitest est le runner par défaut depuis la v21. TestBed, composants avec Signals, services, HTTP et harnesses.Qualité et performance
  • 11DevTools : regarder dans votre applicationChrome DevTools et Angular DevTools pas à pas : ce que montre chaque panneau, avec des exercices qui fonctionnent sur ce site.Qualité et performance
  • 11Angular AriaDes composants headless et accessibles que vous stylez comme vous voulez : tabs, accordion, menu, listbox...Qualité et performance
  • 12SSR, Prerendering et modes de renduQuand rendre sur le serveur, quand au build et quand uniquement dans le navigateur : RenderMode par route, app.routes.server.ts et les coûts réels.SSR et Hydration
  • 12Hydration : event replay et incrémentaleComment le navigateur reprend le HTML du serveur sans le re-rendre : Hydration complète, event replay, @defer (hydrate on …) et erreurs de mismatch.SSR et Hydration
  • 12Code compatible serveurwindow n’existe pas sur le serveur : PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, le cache HTTP et PendingTasks.SSR et Hydration
  • 13Micro frontendsQuand découper une application en plusieurs déploiements vaut le coup, et quand c’est juste de la complexité : Native Federation, Module Federation, iframes, Web Components.Architecture à grande échelle
  • 13Web Components avec Angular ElementscreateCustomElement : un composant Angular utilisé comme balise HTML n’importe où, plus la communication entre applications via des événements et BroadcastChannel.Architecture à grande échelle
  • 13Monorepo, bibliothèques et frontièresUn workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.Architecture à grande échelle
  • 13Architecture hexagonale (ports et adapters)Un domaine sans Angular, des ports sous forme de classes abstraites et des adapters interchangeables : HTTP, localStorage ou en mémoire, sans toucher à l’UI.Architecture à grande échelle
  • 14Les Signals sous le capotLe graphe réactif : producteurs et consommateurs, push-pull, pourquoi computed est lazy et sans glitch, le problème du diamant et quand les effects s’exécutent.Réactivité avancée
  • 14RxJS avancé : opérateurs d’ordre supérieurswitchMap, mergeMap, concatMap et exhaustMap sur un diagramme interactif, plus retry avec backoff, shareReplay et comment éviter les fuites.Réactivité avancée
  • 14NgRx SignalStoreState Management à grande échelle sur les Signals : withState, withComputed, withMethods, rxMethod, entités et hooks — et quand un store simple suffit.Réactivité avancée
  • 14Machines à états avec les signalsFini les états impossibles : une machine typée sur des signals, avec transitions, gardes et un diagramme d’états en direct.Réactivité avancée
  • 15WebSockets : un temps réel qui résiste au réseauUn socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.Temps réel et hors ligne
  • 15Mises à jour optimistes et rollbackUne UI instantanée qui reste honnête : snapshot et rollback, réponses dans le désordre, clés d’idempotence.Temps réel et hors ligne
  • 15Offline-first : Service Worker et cacheLe Service Worker d’Angular : stratégies de cache, annonce des nouvelles versions et file d’attente pour les modifications faites hors ligne.Temps réel et hors ligne
  • 16Web WorkersDes calculs lourds sans bloquer l’UI : un vrai worker dans la page, l’échange de messages et quand ça n’en vaut pas la peine.UI avancée et performance
  • 16Listes géantes : virtual scroll100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).UI avancée et performance
  • 16Animations modernes et View Transitionsanimate.enter et animate.leave à la place de @angular/animations (déprécié), transitions CSS et View Transitions à la navigation.UI avancée et performance
  • 16Fuites mémoire : les trouver et les corrigerAbonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.UI avancée et performance
  • 17Sécurité : XSS, sanitisation et CSPCe qu’Angular fait automatiquement contre le XSS, quand bypassSecurityTrust devient dangereux, plus la CSP avec nonce, Trusted Types et XSRF.Sécurité et internationalisation
  • 17Authentification : tokens et refreshUn interceptor qui ajoute le token, le renouvelle une seule fois sur une 401 et rejoue les requêtes en attente — plus où conserver le token.Sécurité et internationalisation
  • 17OAuth 2.1 et OpenID Connect avec PKCELe flux authorization code avec PKCE, pas à pas : verifier et challenge, state et nonce, l’ID token, et pourquoi un BFF est plus sûr.Sécurité et internationalisation
  • 17Internationalisation (i18n)$localize, l’attribut i18n, ICU plural/select, un build par langue vs des traductions à l’exécution, et le formatage selon la locale.Sécurité et internationalisation