Zpět na domů

Signální formuláře Angular 21: architektura a typizace

Článek podrobně rozebírá signální formuláře v Angular 21: architektonické primitivy FieldTree a FieldState, výhody typizace, správa stavu prvků (hidden/disabled/readonly), vestavěný debounce a validace mezi poli prostřednictvím validateTree.

Angular 21: signální formuláře bez abstrakcí
Advertisement 728x90

Signalové formuláře v Angularu 21: jak Signal API mění práci s formuláři na architektonické úrovni

Angular 21 představil signalové formuláře — nejen novou utilitu, ale zásadní posun v paradigmatu řízení stavu formulářů. Nejde o evoluci reaktivních formulářů, ale o jejich přepracování skrze prizmu Signal API: přímou synchronizaci modelu a uživatelského rozhraní, striktní typizaci bez ztráty kontextu, deklarativní řízení stavu prvků a integrovanou podporu debounce a křížové validace. Funkcionalita je zatím experimentální, ale její architektonická řešení již určují směr vývoje pro enterprise aplikace s vysokými nároky na předvídatelnost a udržitelnost.

Architektonický základ: FieldTree a FieldState místo FormGroup/FormControl

Signalové formuláře opouštějí abstrakci AbstractControl ve prospěch dvou striktně typizovaných primitivů:

  • FieldTree<T> — proxy objekt představující strom formuláře jako signály. Každý uzel je Signal<FieldState<T>>, přístup k hodnotě probíhá voláním ().
  • FieldState<T> — stav konkrétního pole: hodnota, stav (valid, touched, dirty), chyby a metody řízení (set, reset, markAsTouched).

Toto eliminuje nutnost ručně synchronizovat vstupní data (@Input) a interní stav formuláře. U reaktivních formulářů bylo třeba použít valueChanges + patchValue, což přinášelo riziko nesouladu a komplikovalo správu životního cyklu. U signalových formulářů se model stává zdrojem pravdy přímo:

Google AdInline article slot
export interface LoginFormModel {
  email: string;
  password: string;
}

@Component({
  imports: [Field],
  template: `
    <form>
      Email: <input [field]="loginForm.email">
      Password: <input [field]="loginForm.password">
    </form>
  `
})
export class LoginForm {
  login = model.required<LoginFormModel>();
  loginForm = form(this.login); // Propojí signál modelu se stromem formuláře
}

Zde je this.login WritableSignal<LoginFormModel>, a loginForm.email() vrátí FieldState<string>. Změna login.set({...}) okamžitě aktualizuje všechny propojené FieldState, a naopak změna hodnoty prostřednictvím loginForm.email().set(...) aktualizuje login().

Typizace bez ztrát: od AbstractControl k přesným typům polí

Klíčovou výhodou je zachování struktury typu na všech úrovních. U reaktivních formulářů get('nested.field') vrátí AbstractControl | null, čímž se ztrácí informace o vnořené struktuře. U signalových formulářů má cesta path.additionalInformation.firstName striktní typ FieldState<string>, a path.additionalInformation()FieldState<{ firstName: string; lastName: string; }>. To umožňuje bezpečné používání effect, computed a dalších primitiv Signal API bez převodů typů a kontrol na undefined.

Vezměme si příklad s grupou additionalInformation:

Google AdInline article slot
effect(() => {
  const info = this.loginForm.additionalInformation();
  console.log(info.firstName().value()); // string — kompilátor ví přesně
  console.log(info.lastName().touched()); // boolean
});

Taková typizace vylučuje klasické chyby: pokus o volání .value u null, chyby při práci s vnořenými FormGroup, neviditelné any v řetězcích get().get().value.

Řízení stavu prvků: hidden, disabled, readonly

Signalové formuláře nabízejí tři vestavěné mechanismy řízení viditelnosti a interaktivnosti, každý s vlastní semantikou:

  • hidden(path, condition?) — kompletně vyřadí pole z DOM (prostřednictvím @if) i ze stromu validace. Nemá vliv na touched/dirty, protože element v formuláři neexistuje.
  • disabled(path, condition?) — činí pole nedostupným pro editaci a vyřazuje ho z validace i ze stavů touched/dirty.
  • readonly(path, condition?) — blokuje editaci, ale zůstává v validačním cyklu a umožňuje sledovat touched/dirty.

Podmínky se zadávají prostřednictvím funkce, která dostává kontext { valueOf }. Například:

Google AdInline article slot
form<{ email: string; password: string }>(
  { email: '', password: '' },
  (path) => {
    disabled(path.password, ({ valueOf }) => !valueOf(path.email));
  }
);

To je ekvivalent password.disabled = !email.value, ale provádí se reaktivně a zaručuje soulad.

Debounce a validace: vestavěná řešení místo RxJS

Pro kontrolu frekvence aktualizace dat je implementována funkce debounce, která přijímá buď timeout v milisekundách, nebo uživatelský Debouncer:

debounce(path, (
  { valueOf },
  abortSignal
) => {
  return new Promise<void>((resolve) => {
    const timeout = setTimeout(() => resolve(), valueOf(path.email).length * 100);
    abortSignal.addEventListener('abort', () => clearTimeout(timeout));
  });
});

Validace je rozdělena na synchronní (required, minLength, pattern) a asynchronní (validateAsync, validateHttp). Specifikum je podpora validateTree, která umožňuje vytvářet křížová pravidla. Například kontrola shody hesel:

validateTree(path, (ctx) => {
  const { password, confirmPassword } = ctx.value();
  if (password !== confirmPassword) {
    return {
      field: ctx.fieldTree.confirmPassword,
      kind: 'passwordMismatch',
      message: 'Hesla se neshodují'
    };
  }
});

Zde ctx.fieldTree dává přístup ke všem FieldState uvnitř stromu, a ctx.value() — k aktuální hodnotě celého formuláře.

Co je důležité

  • Signalové formuláře nejsou náhradou za reaktivní, ale jejich architektonické přepracování pod Signal API: model a formulář jsou nyní jediným zdrojem pravdy.
  • Typizace se udržuje na všech úrovních vnoření, eliminuje AbstractControl a nutnost ruční kontroly instanceof.
  • hidden, disabled, readonly mají jasnou semantiku a automaticky řídí validaci i stavy touched/dirty.
  • debounce a validateTree jsou integrovány do jádra, čímž se odstraňuje závislost na RxJS pro základní scénáře.
  • Experimentální status znamená, že API se může změnit; použití v production vyžaduje jasné pochopení rizik a plánu migrace.

Angular 21 signalové formuláře jsou krokem k předvídatelnějšímu, typově bezpečnějšímu a méně složitému řízení formulářů. Nezjednodušují jednoduché případy, ale radikálně snižují složitost složitých: vnořených formulářů, podmíněných polí, křížové validace a dynamické synchronizace. Pro middle/senior vývojáře to není nástroj „pro rychlý start“, ale architektonický primitiv, který stojí za hluboké studium — až na úroveň implementace FieldTree a chování effect v kontextu formuláře.

— Editorial Team

Advertisement 728x90

Číst dál