Skip to content

IliaIdakiev/form-control-change-tracker

Repository files navigation

Angular Form Control Change Tracker

npm version npm downloads License Angular CI

🚀 Live Demo

Try it on StackBlitz - Interactive demo with reactive and template-driven forms (Angular 19)

Version Compatibility

Angular Version Library Version Status
v19+ ^1.0.0 🟢 Stable
v15 - v19 0.0.4 🟡 Legacy Support

Custom Comparison Strategy

The library uses a SimpleStrategy by default, which performs a fast reference check for primitives and a key-order independent deep comparison for objects. To use a different strategy (e.g., deep-diff):

  1. Install deep-diff (or your preferred library):

    npm install deep-diff
    npm install @types/deep-diff --save-dev
  2. Create the Strategy:

    import { Injectable } from "@angular/core";
    import { ComparisonStrategy } from "form-control-change-tracker";
    import { diff } from "deep-diff";
    
    @Injectable()
    export class DeepDiffStrategy implements ComparisonStrategy {
      isEqual(a: any, b: any): boolean {
        // Returns true if no differences found
        return !diff(a, b);
      }
    }
  3. Provide it in your Module:

    import { HG_COMPARISON_STRATEGY } from "form-control-change-tracker";
    import { DeepDiffStrategy } from "./strategies/deep-diff-strategy";
    
    @NgModule({
      // ...
      providers: [
        {
          provide: HG_COMPARISON_STRATEGY,
          useClass: DeepDiffStrategy,
        },
      ],
    })
    export class AppModule {}

Very often when developers need to know if there were any changes inside the a form in order to present a unsaved changes confirmation dialog when navigating away or in order to disable the save button when there is nothing new to save. The FormControlChangeTrackerModule provides two things:

  • The ChangeTrackerDirective (hgChangeTracker) that can be set on the individual form controls in order to track if any changes are made

  • And the @hasChanges() decorator that is applied over the ChangeTrackerDirective directives in order to provide you a boolean value indicating if there are any changes or not.

Usage

1. New Container API (Recommended)

The new API allows you to track changes directly in your template without needing complex decorators.

Angular 19+ (Standalone Components)

import { Component } from "@angular/core";
import { ReactiveFormsModule, FormBuilder, FormGroup } from "@angular/forms";
import { FormControlChangeTrackerModule } from "form-control-change-tracker";

@Component({
  selector: "app-my-form",
  standalone: true,
  imports: [ReactiveFormsModule, FormControlChangeTrackerModule],
  template: `
    <form [formGroup]="form" (ngSubmit)="submit()" hgChangeTrackerContainer #tracker="hgChangeTrackerContainer">
      <div class="form-group">
        <label>First Name</label>
        <!-- Auto-captures initial value on init -->
        <input formControlName="firstName" hgChangeTracker />
      </div>

      <div class="form-group">
        <label>Last Name</label>
        <input formControlName="lastName" hgChangeTracker />
      </div>

      <!-- Check for changes anywhere in the form -->
      <button [disabled]="!tracker.hasChanges">Submit</button>

      <!-- Reset initial/default values to current values -->
      <button type="button" [disabled]="!tracker.hasChanges" (click)="tracker.resync()">Update Defaults</button>
    </form>
  `,
})
export class MyFormComponent {
  form: FormGroup;

  constructor(private fb: FormBuilder) {
    this.form = this.fb.group({
      firstName: [""],
      lastName: [""],
    });
  }

  submit() {
    console.log("Form submitted:", this.form.value);
  }
}

Legacy NgModule (Angular 15-18)

// app.module.ts
imports: [
  // ...
  FormControlChangeTrackerModule,
];

template

<form [formGroup]="form" (ngSubmit)="submit()" hgChangeTrackerContainer #tracker="hgChangeTrackerContainer">
  <div class="form-group">
    <label>First Name</label>
    <!-- Auto-captures initial value on init -->
    <input formControlName="firstName" hgChangeTracker />
  </div>

  <div class="form-group">
    <label>Last Name</label>
    <input formControlName="lastName" hgChangeTracker />
  </div>

  <!-- Check for changes anywhere in the form -->
  <button [disabled]="!tracker.hasChanges">Submit</button>

  <!-- Reset initial/default values to current values -->
  <button [disabled]="!tracker.hasChanges" (click)="tracker.resync()">Update Defaults</button>
</form>

2. Configuration Options

Debounce Time Adjust the debounce time for change detection (default: 20ms).

<input hgChangeTracker [debounceTime]="300" />

Multi-Initial Values Allow multiple values to be considered "valid" (unchanged).

<input hgChangeTracker [multiInitialValue]="true" [initialValue]="['A', 'B']" />

Auto-Sync Control whether the directive automatically captures the initial value.

<!-- Disable auto sync if you want full manual control -->
<input hgChangeTracker [autoInitialValueSync]="false" [initialValue]="startValue" />

3. Legacy Decorator API

The library serves backward compatibility for the @hasChanges() decorator usage.

@Component({...})
export class MyComponent {
  @ViewChildren(ChangeTrackerDirective) @hasChanges() hasFormChanges: boolean;
}

Features

  • Deep Comparison: Uses deep-diff strategies to correctly track object changes.
  • Reactive: Built on RxJS for efficient change detection.
  • Debounced: Prevents UI thrashing on high-frequency inputs.
  • Container Support: Easily aggregate change status for an entire form.

Releases

No releases published

Packages

 
 
 

Contributors