0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Angular strict modeでフォームを型安全に扱う書き方|AI実務ノート 編集部

Last updated at Posted at 2026-01-03

1. 結論(この記事で得られること)

Angular strict mode環境でReactive Formsを型安全に扱う実装パターンを習得できます。

具体的には以下が身につきます:

  • 「FormGroup」の正しい型定義とFormControlの型推論
  • 「NonNullableFormBuilder」を使った安全なフォーム構築
  • 「getRawValue()」と「value」の違いと使い分け
  • 実務で使えるバリデーション型安全パターン
  • AIを活用した型エラー解決の最短ルート
  • Jasmine/Karmaでのフォームテスト戦略

私自身、Angular 14でstrict modeに移行した際、既存のフォームコードが軒並み型エラーで赤くなって途方に暮れた経験があります。この記事では、その時に確立した型安全なパターンを実コード付きで解説します。

2. 前提(環境・読者層)

対象読者

  • Angular 14以降でstrict mode(「strictTemplates: true」)を有効にしている方
  • Reactive Formsの基本は分かるが、型安全性に課題を感じている方
  • 「as」や「!」などの型アサーションを減らしたい方
{
  "@angular/core": "^17.0.0",
  "@angular/forms": "^17.0.0",
  "typescript": "^5.2.0"
}

tsconfig.jsonで以下が有効:

{
  "strict": true,
  "strictNullChecks": true,
  "strictTemplates": true
}

3. Before:よくあるつまずきポイント

パターン1:型が「any」になってしまう

// ❌ 古い書き方:型情報がない
export class UserFormComponent {
  userForm = new FormGroup({
    name: new FormControl(''),
    email: new FormControl(''),
    age: new FormControl(0)
  });
 
  onSubmit() {
    // ここで型がany!タイポも検出されない
    const data = this.userForm.value;
    console.log(data.nam); // 間違っているのに通る
  }
}

このパターン、私も最初にハマりました。「value」の型が「Partial<{name, email, age}>」になり、全プロパティがundefined許容になってしまうんです。

パターン2:null/undefinedの扱いでエラー

// ❌ strictNullChecksでエラー
const nameControl = this.userForm.get('name');
nameControl.setValue('test'); // Object is possibly 'null'

パターン3:ネストしたFormGroupの型が壊れる

// ❌ 型推論が効かない
userForm = new FormGroup({
  profile: new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl('')
  })
});
 
// profile.valueの型がanyに...
const profile = this.userForm.get('profile').value;

4. After:基本的な解決パターン

解決策1:FormGroupによる厳密な型定義

import { FormControl, FormGroup, Validators } from '@angular/forms';
 
// ✅ フォームの型を明示的に定義
interface UserFormModel {
  name: FormControl<string>;
  email: FormControl<string>;
  age: FormControl<number>;
}
 
export class UserFormComponent {
  userForm: FormGroup<UserFormModel> = new FormGroup({
    name: new FormControl('', { nonNullable: true, validators: Validators.required }),
    email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
    age: new FormControl(0, { nonNullable: true })
  });
 
  onSubmit() {
    // ✅ 型安全!getRawValue()でnon-nullableな値を取得
    const data = this.userForm.getRawValue();
    console.log(data.name); // string型
    console.log(data.nam);  // コンパイルエラー!
  }
}

重要なポイント:

  • 「FormControl」で明示的に型を指定
  • 「nonNullable: true」でnull/undefinedを排除
  • 「getRawValue()」を使うとdisabled状態も含む完全な値を取得

解決策2:NonNullableFormBuilderの活用

import { NonNullableFormBuilder } from '@angular/forms';
import { inject } from '@angular/core';
 
export class UserFormComponent {
  private fb = inject(NonNullableFormBuilder);
 
  // ✅ 全コントロールが自動的にnonNullableになる
  userForm = this.fb.group({
    name: ['', Validators.required],
    email: ['', [Validators.required, Validators.email]],
    age: [0, [Validators.min(0), Validators.max(120)]]
  });
 
  onSubmit() {
    if (this.userForm.invalid) return;
 
    // ✅ 型推論が効く
    const formData = this.userForm.getRawValue();
    // formData: { name: string; email: string; age: number; }
  }
}

これが実務では一番スッキリします。FormBuilderのショートハンド記法も使えて、コード量が減ります。

解決策3:ネストした構造の型安全化

interface AddressForm {
  street: FormControl<string>;
  city: FormControl<string>;
  zipCode: FormControl<string>;
}
 
interface UserProfileForm {
  firstName: FormControl<string>;
  lastName: FormControl<string>;
  address: FormGroup<AddressForm>;
}
 
export class ProfileComponent {
  private fb = inject(NonNullableFormBuilder);
 
  profileForm = this.fb.group<UserProfileForm>({
    firstName: this.fb.control(''),
    lastName: this.fb.control(''),
    address: this.fb.group<AddressForm>({
      street: this.fb.control(''),
      city: this.fb.control(''),
      zipCode: this.fb.control('')
    })
  });
 
  // ✅ ネストした値も型安全
  onSubmit() {
    const profile = this.profileForm.getRawValue();
    console.log(profile.address.city); // string
  }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?