Account Settings
概要
{ profile: {...}, settings: {...} } のネストオブジェクトをモデルに持つアカウント編集フォーム。ネストモデルに対する form() の構造と、部分スキーマを切り出して特定のパスに適用するパターンを学ぶ。
学習ポイント
- ネストモデルの
form()定義とパス到達 (userForm.profile.firstName) schema()+apply()による部分スキーマの切り出しと適用
フォーム構造
| パス | 型 | バリデーション |
|---|---|---|
profile.firstName |
string |
required, maxLength(50) |
profile.lastName |
string |
required, maxLength(50) |
settings.theme |
'light' | 'dark' | 'auto' |
型レベル制約のみ |
settings.notifications |
boolean |
なし |
実装の要点
ネストモデルとパス到達
モデルがネストされていれば、form() が生成する FieldTree も同じ階層になる。末端まで型補完が効いた状態でパスを辿れる。
readonly userModel = signal({
profile: { firstName: 'Alice', lastName: 'Tanaka' },
settings: { theme: 'light' as 'light' | 'dark' | 'auto', notifications: true },
});
readonly userForm = form(this.userModel);
// モデル形状と FieldTree の階層は 1:1 で対応する:
userForm.profile.firstName; // : FieldTree<string>
userForm.settings.theme; // : FieldTree<'light' | 'dark' | 'auto'>
// 末端の value() で現在値を読む:
userForm.profile.firstName().value(); // : string → 'Alice'
userForm.settings.theme().value(); // : 'light' | 'dark' | 'auto' → 'light'
// 中間ノードの value() はサブツリー全体を返す(同じパス形式で部分木にも到達できる):
userForm.profile().value(); // : { firstName: string; lastName: string }
userForm.settings().value(); // : { theme: ...; notifications: boolean }
userForm().value(); // : UserData (ツリー全体)テンプレート側でも同じパスをそのまま [formField] に渡す。セクションごとに @let で別名化すると、userForm.profile. の繰り返しを避けられる。
<fieldset>
@let profile = userForm.profile;
<input [formField]="profile.firstName" />
<input [formField]="profile.lastName" />
</fieldset>部分スキーマの切り出し: schema() + apply()
schema<T>(fn) は「型 T を持つ任意のパスに適用できる」スキーマを作る。apply(path, schema) でフォーム内の特定パスに後付けで適用する。
// profile の Profile 型に対する再利用可能なスキーマ
const profileSchema = schema<Profile>((p) => {
required(p.firstName, { message: 'First name is required' });
maxLength(p.firstName, 50, { message: 'First name must be 50 characters or fewer' });
required(p.lastName, { message: 'Last name is required' });
maxLength(p.lastName, 50, { message: 'Last name must be 50 characters or fewer' });
});
readonly userForm = form(this.userModel, (s) => {
// profile サブツリーに profileSchema を適用
apply(s.profile, profileSchema);
});同じ Profile 型を持つ別のパスへも profileSchema をそのまま適用できる。