Template-driven Form 與 LocalStorage 整合
實現表單值的暫存功能,讓使用者重整頁面後仍能保留先前的輸入。
基本原理
監聽表單值變化,將值存入 LocalStorage
頁面載入時,從 LocalStorage 讀取值並設置回表單
考慮表單初始化的時機問題
實作步驟
1. 使用 viewChild 獲取表單參考
首先,使用 viewChild
獲取範本中的表單參考:
private form = viewChild.required<NgForm>('form');
private destroyRef = inject(DestroyRef);
這裡我們找到模板中 #form="ngForm"
這個範本變數,並標註它是 NgForm 類型。
2. 監聽表單值變化並存入 LocalStorage
在 constructor 中,我們使用 afterNextRender
鉤子確保表單已完全初始化:
constructor() {
afterNextRender(() => {
// 監聽表單值變化
const subscription = this.form()
.valueChanges?.pipe(debounceTime(500))
.subscribe({
next: (value) =>
window.localStorage.setItem(
'saved-login-form',
JSON.stringify({ email: value.email })
),
});
// 記得取消訂閱
this.destroyRef.onDestroy(() => {
subscription?.unsubscribe();
});
});
}
使用 afterNextRender
是因為這個表單是用 Template-driven 方法,表單控制項是在範本渲染後才被建立,所以必須等到範本完全初始化後才能操作表單。

我們使用 valueChanges
Observable 來監聽表單值的變化,並加入 debounceTime(500)
防抖機制,讓使用者停止輸入 500ms 後才將值存入 LocalStorage。

3. 載入時從 LocalStorage 恢復表單值
同樣在 afterNextRender
鉤子中,我們從 LocalStorage 讀取之前儲存的值:
afterNextRender(() => {
const savedForm = window.localStorage.getItem('saved-login-form');
if (savedForm) {
try {
const loadedFormData = JSON.parse(savedForm);
const savedEmail = loadedFormData.email;
// 使用 setTimeout 確保表單控制項已完全初始化
setTimeout(() => {
this.form().controls['email'].setValue(savedEmail);
}, 50);
} catch (error) {
console.error('Error parsing saved form data:', error);
}
}
// 接著是監聽表單值變化的程式碼...
});
為什麼需要使用 setTimeout
?這是因為即使在 afterNextRender
中,表單控制項可能仍未完全註冊到表單,直接使用 this.form().controls['email']
可能會導致錯誤:
ERROR RuntimeError: NG01000: There are no form controls registered with this group yet.
If you're using ngModel, you may want to check next tick (e.g. use setTimeout).

透過 setTimeout
將操作推遲到下一個事件循環,我們可以確保表單控制項已完全註冊。
4. 設置表單值的不同方法
有兩種方式可以設置表單值:
設置整個表單的值:
this.form().setValue({
email: savedEmail,
password: '', // 必須提供所有欄位
});
使用 setValue()
時必須提供表單中所有控制項的值,否則會出錯。
設置單個控制項的值:
this.form().controls['email'].setValue(savedEmail);
只設置特定控制項的值,其他控制項不受影響。
部分更新表單值:
this.form().form.patchValue({
email: savedEmail
// 不需要提供 password
});
使用 patchValue()
可以只更新指定的欄位,其他欄位保持不變。
完整範例
HTML 模板:
<form #form="ngForm" (ngSubmit)="onSubmit(form)">
<h2>Login</h2>
<div class="control-row">
<div class="control no-margin">
<label for="email">Email</label>
<input
id="email"
type="email"
name="email"
ngModel
required
email
#emailCtrl="ngModel"
/>
</div>
<div class="control no-margin">
<label for="password">Password</label>
<input
id="password"
type="password"
name="password"
ngModel
required
minlength="6"
#passwordCtrl="ngModel"
/>
</div>
<button class="button">Login</button>
</div>
@if (emailCtrl.touched && emailCtrl.dirty && emailCtrl.invalid) {
<p class="control-error">Invalid email address entered.</p>
}
@if (passwordCtrl.touched && passwordCtrl.dirty && passwordCtrl.invalid) {
<p class="control-error">
Invalid password entered - must be at least 6 characters long.
</p>
}
</form>
元件程式碼:
import {
afterNextRender,
Component,
DestroyRef,
inject,
viewChild,
} from '@angular/core';
import { FormsModule, NgForm } from '@angular/forms';
import { debounceTime } from 'rxjs';
@Component({
selector: 'app-login',
standalone: true,
imports: [FormsModule],
templateUrl: './login.component.html',
styleUrl: './login.component.css',
})
export class LoginComponent {
private form = viewChild.required<NgForm>('form');
private destroyRef = inject(DestroyRef);
constructor() {
afterNextRender(() => {
// 從 LocalStorage 讀取先前儲存的表單值
const savedForm = window.localStorage.getItem('saved-login-form');
if (savedForm) {
try {
const loadedFormData = JSON.parse(savedForm);
const savedEmail = loadedFormData.email;
setTimeout(() => {
// 設置表單控制項的值
this.form().controls['email'].setValue(savedEmail);
}, 50);
} catch (error) {
console.error('Error parsing saved form data:', error);
}
}
// 監聽表單值變化並儲存到 LocalStorage
const subscription = this.form()
.valueChanges?.pipe(debounceTime(500))
.subscribe({
next: (value) =>
window.localStorage.setItem(
'saved-login-form',
JSON.stringify({ email: value.email })
),
});
// 元件銷毀時取消訂閱
this.destroyRef.onDestroy(() => {
subscription?.unsubscribe();
});
});
}
onSubmit(formData: NgForm) {
if (formData.form.invalid) {
return;
}
const enteredEmail = formData.form.value.email;
const enteredPassword = formData.form.value.password;
console.log(enteredEmail, enteredPassword);
formData.form.reset();
}
}
注意事項
時機問題:在表單完全初始化前設置值會導致錯誤,所以需要使用
afterNextRender
和setTimeout
錯誤處理:對 JSON 解析等操作應加入錯誤處理
防抖處理:使用
debounceTime
減少不必要的 LocalStorage 寫入操作取消訂閱:使用
DestroyRef.onDestroy
確保訂閱被正確取消,避免記憶體洩漏表單重置:在表單提交後使用
formData.form.reset()
重置表單,這也會觸發valueChanges
,可能會將空值寫入 LocalStorage
Last updated