Angular 2++ | NgForm: Form.Dirty is Always Dirty

4.1k Views Asked by At

Determine if NgForm Looks Exactly As It Did Before Any User-Input


It seems that form.dirty doesn't redact its value after it has been changed, and form.touched seems to always be false no matter what: dirty is touched, and touched is tetched.

template.html
<form #form="ngForm" (ngSubmit)="handleSubmission($event, {}, form)">
    ...
    <input
        #input
        type="text"
        [name]="item.title"
        [(ngModel)]="item.estimate"
        (ngModelChange)="handleEstimateChange(item, item.estimate, input, form)"
    />
    ...
</form>
component.ts
export class LeComponent {
    @Input('data') public data: any;
    public handleEstimateChange: Function;

    constructor(private $: Sandbox) {
        this.handleEstimateChange = $.debounce(this.handleEstimate.bind(this), (1000*0.2));
    }

    handleEstimate(item: any, estimate: number, input: HTMLInputElement, form: NgForm) {
        if (!estimate) delete item.esitmate;
        (this, item, estimate, input, form);
        // Why does form.dirty never change back to pristine again???
    }

}

In the TypeScript, I'm debouncing the ngModelChange handler to give Angular a chance to change the form.dirty value before I check it. This is because ngModelChange gets triggered before the NgForm object has been modified.

If !estimate, because estimate === "", then set it back to its original value of undefined. In this case, the form should look exactly like it did before any user-input had occurred.

However, when I put a breakpoint on the line right above the comment and I output form.dirty to the console, the NgForm never changes dirty back to false.

Is it possible to determine if the form looks exactly like it did before any user-input?

Obviously, I can write my own dirty logic, but wouldn't that mean that NgForm is kind of useless? There's got to be something I'm missing, right? How could dirty not mean dirty?

I've taken a look at some other SO questions -- the first one being similar but definitely not the question I am asking. They are asking if this is intentional -- I don't care; I'd like to know how to accomplish the goal above.

Close, but no cigar:

angular2 formcontrol stays dirty even if set to original value

Block routing if form is dirty [ Angular 2 ]

Angular 2 getting only the dirty values in a controlgroup

How do I programmatically set an Angular 2 form control to dirty?

Angular 2.x/4.x & bootstrap: patchValue does not alter dirty flag. Possible bug?

2

There are 2 best solutions below

0
On

What Was Most Useful


template.html
<form #form="ngForm" (ngSubmit)="handleSubmission($event, {}, form)">
    ...
    <input
        #input
        type="text"
        [name]="item.title"
        [attr.name]="item.title"
        [(ngModel)]="item.estimate"
        (ngModelChange)="handleEstimateChange(item, item.estimate, input, form)"
    />
    ...
</form>
component.ts
export class LeComponent {
    @Input('data') public section: any;
    public handleEstimateChange: Function;

    private resetFormControl = (input: HTMLInputElement, form: NgForm) => {
        var name = input.name, control = form.controls[name];
        control.reset();
        // control.markAsPristine();
        // control.setValue(undefined);
        // control.updateValueAndValidity();
    };

    constructor(private $: Sandbox) {
        this.handleEstimateChange = $.debounce(this.handleEstimate.bind(this), (1000*0.2));
    }

    handleEstimate(item: any, estimate: number, input: HTMLInputElement, form: NgForm) {
        if (!estimate) this.resetFormControl(input, form);
        (this, item, estimate, input, form);
        // Why does form.dirty never change back to pristine again???
    }

}

Note

  • [attr.name]="..." (template.html)
  • resetFormControl

Basically, simply deleteing the value was not enough because it was still present on the FormControl object (form.controls). To clear it properly, invoke control.reset() for the individual control -- this in-turn invokes .markAsPristine() which communicates to the parent NgForm. Also, input.name was empty as it was only represented by ng-reflect-name unless [attr.name] elucidated the same value -- [name] is really just there because its required by Angular.

Now, anytime an <input /> value changes -- and its falsey -- we reset the input ensuring that if all are falsey, Angular will automatically handle the NgForm's dirty-state correctly.

0
On

With template-driven forms and a very flat data model, I implemented it like this:

private currentProduct: IProduct;
private originalProduct: IProduct;

get isDirty(): boolean {
    return JSON.stringify(this.originalProduct) !== JSON.stringify(this.currentProduct);
}

get product(): IProduct {
    return this.currentProduct;
}
set product(value: IProduct) {
    this.currentProduct = value;
    // Clone the object to retain a copy
    this.originalProduct = Object.assign({}, value);
}

But this only works for very simple cases.

As I mentioned in the comments, using reactive forms gives you more flexibility in managing your data model separate from your user entries.