如果你是 Angular 開發者,並且已經投入了超過五分鐘的時間,那麼你可能已經對表單感到頭痛。它們無所不在——登入介面、結帳流程,以及大量的入門嚮導,這些都讓你懷疑人生。
嘿,別誤會我的意思——Angular 表單功能很強大。但說實話:有時候你只是想知道到底發生了什麼變化,而不想費力地翻閱一大堆樣板訂閱和巨大的表單物件。例如,如果用戶剛剛更新了街道名稱,你為什麼要關心他們的愛好陣列是否仍然完好無損?
這正是我所渴望的,而且——劇透警告——我使用一個簡潔的小工具來解決這個問題,它可以像專業人士一樣追蹤變化。
當然,你隨時可以訂閱 formControl.valueChanges。很簡單…直到你的表單裡嵌套了 FormGroup 來儲存地址,FormArray 來儲存興趣愛好,還有其他什麼不知道的東西。
問題來了?在這個巨大的表單中,任何一點修改都會讓整個表單的值爆出來。砰!每次都是這樣。全部都是。
這就像問別人“你的辦公桌是不是挪了點兒?”,結果他們卻把整棟辦公大樓的藍圖寄給你。一點用都沒有。
最酷的部分來了。我沒有被表單值淹沒,而是編寫了一個簡單的實用程序,它可以做兩件神奇的事情:
效能優勢:它會等待使用者暫停輸入,然後才檢查變更。 (因為沒人想在每次擊鍵時都對 JSON 物件進行 diff 比較。)
水晶般清晰度:它為您提供一個乾淨、整潔的「差異」物件,僅顯示實際發生的變化。
想像一下,就像有個朋友只會告訴你有趣的八卦,而不是整個城鎮的歷史。
以下是我們的實用程式的核心:
/**
* Recursively computes a "diff" object between two values.
* Returns null if the values are identical.
* For objects, it returns a new object with only the changed properties.
* For arrays, it returns a new array with a diff for each element.
*/
function getDiff(original: any, current: any): any {
if (original === current) {
return null;
}
// Handle nested objects (FormGroups)
if (original !== null && typeof original === 'object' && !Array.isArray(original) &&
current !== null && typeof current === 'object' && !Array.isArray(current)) {
const diff: any = {};
let hasChanges = false;
for (const key in current) {
if (Object.prototype.hasOwnProperty.call(current, key)) {
const itemDiff = getDiff(original[key], current[key]);
if (itemDiff !== null) {
diff[key] = itemDiff;
hasChanges = true;
}
}
}
return hasChanges ? diff : null;
}
// Handle arrays (FormArrays)
if (Array.isArray(original) && Array.isArray(current)) {
const diff: any[] = [];
let hasChanges = false;
const maxLength = Math.max(original.length, current.length);
for (let i = 0; i < maxLength; i++) {
const itemDiff = getDiff(original[i], current[i]);
diff[i] = itemDiff;
if (itemDiff !== null) {
hasChanges = true;
}
}
return hasChanges ? diff : null;
}
// Handle primitive types
return current;
}
以下是將所有內容連結在一起的包裝器:
export function trackFormChanges(control: AbstractControl, initialValue: any): Subscription {
return control.valueChanges
.pipe(debounceTime(300))
.subscribe(currentValue => {
const diff = getDiff(initialValue, currentValue);
console.log("Changes detected:", diff);
});
}
getDiff(original, current):這是這個實用程式的核心。它是一個遞歸函數,用於比較原始表單值(我們的基準值)與當前值。
如果值相同,則傳回 null。
如果它們是物件(FormGroups),它會遍歷鍵並在每個屬性上呼叫自身來尋找嵌套的變更。只有當發現變更時,它才會將屬性新增到 diff 物件中。
如果它們是陣列(FormArrays),它會遍歷元素並呼叫自身來尋找變更。
對於原始值(字串、數字),它只是傳回新值。
trackFormChanges(control, initialValue):這是您將在元件中使用的公共函數。
它採用您想要追蹤的表單控制項及其初始值。
它訂閱根控制項的 valueChanges 可觀察物件。
debounceTime(300) 運算子會等待 300 毫秒的無活動狀態後再發出值。這對於效能至關重要,因為它可以防止 diff 函數在每次按鍵時都執行。
一旦發出一個值,它就會呼叫 getDiff 並記錄產生的 diff 物件。
我第一次嘗試的時候,是在建立一個包含巢狀群組的使用者個人資料表單。通常情況下,我就像喝多了咖啡的偵探一樣,仔細檢查每個欄位的「髒」狀態。但有了它?一個簡潔的 diff 物件,一目了然。
突然間,只有當某些事情真正改變時才啟用「儲存」按鈕,從一個令人頭痛的問題變成了一句話的事情:
<button [disabled]="!hasChanges">Save</button>
廚師之吻。 👌
實際操作起來是這樣的:
@Component({...})
export class UserProfileComponent implements OnInit, OnDestroy {
userForm: FormGroup;
private formSubscription: Subscription;
public hasChanges = false;
constructor(private fb: FormBuilder) {
this.userForm = this.fb.group({
name: ['John Doe', Validators.required],
address: this.fb.group({
street: ['123 Angular Ave'],
city: ['Codeville']
})
});
}
ngOnInit() {
const initialValue = this.userForm;
this.formSubscription = this.userForm.valueChanges
.pipe(debounceTime(300))
.subscribe(() => {
const diff = getDiff(initialValue, this.userForm.value);
this.hasChanges = diff !== null;
});
}
ngOnDestroy() {
if (this.formSubscription) {
this.formSubscription.unsubscribe();
}
}
onSave() {
if (this.hasChanges) {
console.log('Saving changes:', getDiff(this.userForm, this.userForm.value));
}
}
}
它簡單、乾淨,不會讓您猜測背後到底發生了什麼。
歡迎對程式碼建議或任何更好的想法發表評論(例如,使其成為自訂 rxjs 運算子??🎁)。
我很想聽聽有關此事的個人經驗。
如果您覺得這篇文章對您有一點點幫助,請分享支持。
表單可能很雜亂,但追蹤變更卻並非如此。透過智慧遞歸差異分析和一些去抖動技巧,您可以獲得一種可靠、可測試且省心的方法,準確了解更改內容——不多不少。
我已經在各個專案中用過這個技巧了,說實話,我實在不想再用了。如果你厭倦了處理 dirty 和 touched 狀態,不妨試試這個。
誰知道呢——說不定你又會愛上 Angular Forms 了。 (好吧,可能有點誇張了。😅)
原文出處:https://dev.to/xhanimanolistrungu/tracking-changes-in-angular-forms-without-losing-your-mind-8b8