0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Theo Dõi Thay Đổi Trong Form Angular Một Cách Thông Minh

Đăng vào 7 tháng trước

• 7 phút đọc

Giới Thiệu

Nếu bạn đã là một lập trình viên Angular, chắc chắn bạn đã từng gặp phải những thử thách với các form. Chúng xuất hiện khắp nơi – từ màn hình đăng nhập, quy trình thanh toán, cho đến những wizard onboarding phức tạp khiến bạn nghi ngờ về lựa chọn nghề nghiệp của mình.

Mặc dù form trong Angular rất mạnh mẽ, nhưng đôi khi bạn chỉ muốn biết chính xác điều gì đã thay đổi mà không phải lặn ngụp trong một biển các subscriptions và các đối tượng form khổng lồ. Ví dụ, nếu người dùng vừa cập nhật tên đường của họ, tại sao bạn phải quan tâm đến việc mảng sở thích của họ vẫn còn nguyên vẹn?

Chính vì lý do này, tôi đã phát triển một tiện ích nhỏ giúp theo dõi những thay đổi một cách hiệu quả.

Tại Sao Cách Thông Thường Cảm Thấy Quá Nặng Nề

Chắc chắn bạn có thể subscribe vào formControl.valueChanges. Dễ dàng… cho đến khi bạn có một form với một FormGroup lồng nhau cho địa chỉ, một FormArray cho sở thích, và biết đâu còn nhiều thứ khác.

Vấn đề là gì? Một thay đổi ở bất kỳ đâu trong form khổng lồ đó sẽ gửi toàn bộ giá trị form đến bạn. Boom. Mỗi lần. Tất cả.

Nó giống như việc hỏi ai đó “Bạn đã di chuyển bàn một chút chưa?” và họ trả lời bằng cách gửi cho bạn bản vẽ của toàn bộ tòa nhà văn phòng. Không hữu ích.

Xuất Hiện Anh Hùng: Tiện Ích So Sánh Thông Minh

Phần thú vị ở đây là: Thay vì bị chìm trong các giá trị form, tôi đã tạo ra một tiện ích đơn giản thực hiện hai điều kỳ diệu:

  • Chiến thắng về hiệu suất: Nó chờ người dùng ngừng gõ trước khi kiểm tra các thay đổi. (Bởi vì không ai muốn so sánh các đối tượng JSON trên mỗi lần gõ phím).
  • Sự rõ ràng tuyệt đối: Nó cung cấp cho bạn một đối tượng “diff” sạch sẽ, chỉ hiển thị những gì thực sự đã thay đổi. Hãy nghĩ về nó như việc có một người bạn chỉ nói cho bạn những tin tức thú vị, chứ không phải toàn bộ lịch sử của thị trấn.

Công Thức Ma Thuật (Mã Nguồn)

Dưới đây là phần cốt lõi của tiện ích của chúng ta:

typescript Copy
/**
 * Tính toán đệ quy một đối tượng "diff" giữa hai giá trị.
 * Trả về null nếu các giá trị giống nhau.
 * Đối với các đối tượng, nó trả về một đối tượng mới chỉ với các thuộc tính đã thay đổi.
 * Đối với các mảng, nó trả về một mảng mới với một diff cho mỗi phần tử.
 */
function getDiff(original: any, current: any): any {
  if (original === current) {
    return null;
  }

  // Xử lý các đối tượng lồng nhau (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;
  }

  // Xử lý các mảng (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;
  }

  // Xử lý các kiểu dữ liệu nguyên thủy
  return current;
}

Và đây là hàm bọc giúp kết nối mọi thứ lại với nhau:

typescript Copy
export function trackFormChanges(control: AbstractControl, initialValue: any): Subscription {
  return control.valueChanges
    .pipe(debounceTime(300))
    .subscribe(currentValue => {
      const diff = getDiff(initialValue, currentValue);
      console.log("Thay đổi được phát hiện:", diff);
    });
}

Phân Tích Mã Nguồn

  • getDiff(original, current): Đây là trái tim của tiện ích. Nó là một hàm đệ quy so sánh giá trị form ban đầu (cơ sở) với giá trị hiện tại.
  • Nếu các giá trị giống nhau, nó trả về null.
  • Nếu chúng là các đối tượng (FormGroups), nó lặp qua các khóa và gọi chính nó trên mỗi thuộc tính để tìm các thay đổi lồng nhau. Nó chỉ thêm các thuộc tính vào đối tượng diff nếu có thay đổi.
  • Nếu chúng là các mảng (FormArrays), nó lặp qua các phần tử và gọi chính nó để tìm các thay đổi.
  • Đối với các giá trị nguyên thủy (chuỗi, số), nó đơn giản trả về giá trị mới.
  • trackFormChanges(control, initialValue): Đây là hàm công khai mà bạn sẽ sử dụng trong component của mình.
  • Nó nhận vào control form mà bạn muốn theo dõi và giá trị ban đầu của nó.
  • Nó subscribe vào observable valueChanges của control gốc.
  • Toán tử debounceTime(300) chờ 300 mili giây không hoạt động trước khi phát ra một giá trị. Điều này rất quan trọng cho hiệu suất, vì nó ngăn hàm diff chạy trên mỗi lần gõ phím.
  • Khi một giá trị được phát ra, nó gọi getDiff và ghi lại đối tượng diff kết quả.

Tại Sao Tôi Yêu Thích Cách Này

Lần đầu tiên tôi thử nó, tôi đang xây dựng một form thông tin người dùng với các nhóm lồng nhau. Thông thường, tôi sẽ phải kiểm tra trạng thái dirty trên mỗi trường như một thám tử với quá nhiều cà phê. Nhưng với cách này? Một đối tượng diff rõ ràng như ban ngày.

Bỗng nhiên, việc kích hoạt nút “Lưu” chỉ khi có thay đổi trở nên dễ dàng hơn rất nhiều:

html Copy
<button [disabled]="!hasChanges">Lưu</button>

Thật ấn tượng. 👌

Một Ví Dụ Thực Tế: Form Thông Tin Người Dùng

Dưới đây là cách nó hoạt động:

typescript Copy
@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('Lưu các thay đổi:', getDiff(this.userForm, this.userForm.value));
    }
  }
}

Nó đơn giản, sạch sẽ, và không khiến bạn nghi ngờ về những gì đang diễn ra bên trong.

Hãy để lại nhận xét cho các gợi ý về mã, hoặc bất kỳ ý tưởng nào khác (ví dụ như biến nó thành một toán tử rxjs tùy chỉnh có thể không? 🎁).

Tôi rất muốn nghe bất kỳ trải nghiệm cá nhân nào về vấn đề này.

Nếu bạn cảm thấy bài viết này đã giúp ích cho bạn một chút, hãy hỗ trợ bằng cách chia sẻ nó.

Kết Luận

Các form có thể rất phức tạp, nhưng việc theo dõi các thay đổi không cần phải như vậy. Với một hàm diff đệ quy thông minh và một chút phép thuật debounce, bạn sẽ có một cách đáng tin cậy, có thể kiểm tra và giúp bạn biết chính xác điều gì đã thay đổi – không hơn, không kém.

Tôi đã sử dụng mẹo này trong nhiều dự án, và thực sự, tôi không muốn quay lại. Nếu bạn mệt mỏi với việc vật lộn với các trạng thái dirty và touched, hãy thử nghiệm với nó.

Ai biết được – bạn có thể sẽ lại yêu thích các form Angular một lần nữa. (Được rồi, có thể đó là một chút quá đà. 😅)

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào