Reactive Forms 與 LocalStorage 整合

Reactive Forms 與 Template-driven Forms 在與 LocalStorage 整合時有一些關鍵差異。

時機差異

在使用 Reactive Forms 時,不需要等到 afterNextRender 才與表單互動,可以直接在 ngOnInit 生命週期鉤子中進行:

ngOnInit(): void {
  // 從 localStorage 讀取並設置表單值
  const savedForm = window.localStorage.getItem('saved-login-form');
  if (savedForm) {
    try {
      const loadedForm = JSON.parse(savedForm);
      this.loginForm.patchValue({
        email: loadedForm.email,
      });
    } catch (error) {
      console.error('Error parsing saved form data', error);
    }
  }
  
  // 監聽表單變化並保存到 localStorage
  const subscription = this.loginForm.valueChanges
    .pipe(debounceTime(500))
    .subscribe({
      next: (value) => {
        window.localStorage.setItem(
          'saved-login-form',
          JSON.stringify({ email: value.email })
        );
      },
    });
    
  // 組件銷毀時取消訂閱
  this.destroyRef.onDestroy(() => {
    subscription.unsubscribe();
  });
}

這是因為 Reactive Forms 不依賴範本初始化,表單模型完全在 TypeScript 中定義和控制。

使用 patchValue 更新表單

patchValue 方法允許我們只更新表單的部分控制項,而不需要提供所有控制項的值:

this.loginForm.patchValue({
  email: loadedForm.email,
  // 不需要包含 password 或其他控制項
});

相比之下,setValue 方法要求提供所有控制項的值,否則會報錯。

更優雅的方法:初始化時設定預設值

由於 Reactive Forms 的特性,我們可以採用更優雅的方式,直接在 FormControl 初始化時設定從 localStorage 讀取的值:

// 在元件類別外部執行,只會在首次載入模組時執行一次
let initialEmailValue = '';
const savedForm = window.localStorage.getItem('saved-login-form');
if (savedForm) {
  try {
    const loadedForm = JSON.parse(savedForm);
    initialEmailValue = loadedForm.email;
  } catch (error) {
    console.error('Error parsing saved form data', error);
  }
}

@Component({...})
export class LoginComponent {
  // 直接在建立 FormControl 時使用初始值
  loginForm = new FormGroup({
    email: new FormControl(initialEmailValue, {
      validators: [Validators.required, Validators.email],
      asyncValidators: [emailIsUnique],
    }),
    password: new FormControl('', [...])
  });
  
  // 仍然需要監聽變化來保存到 localStorage
  ngOnInit(): void {
    const subscription = this.loginForm.valueChanges
      .pipe(debounceTime(500))
      .subscribe({...});
      
    this.destroyRef.onDestroy(() => {
      subscription.unsubscribe();
    });
  }
}

這種方法的好處是不需要在初始化後再次更新表單值,避免了不必要的狀態變化。

兩種方法比較

  1. ngOnInit 中使用 patchValue:

    • 優點:集中在生命週期鉤子中處理,邏輯清晰

    • 缺點:表單初始化後又立即更新,可能導致狀態變化

  2. FormControl 初始化時設置:

    • 優點:更加優雅,避免不必要的狀態變化

    • 缺點:邏輯分散在元件外部,可能對某些開發者來說不直觀

Last updated