如果你是 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


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝10   💬6   ❤️11
448
🥈
我愛JS
📝1   💬6   ❤️4
93
🥉
AppleLily
📝1   💬4   ❤️1
46
#4
💬2  
6
#5
💬1  
5
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次