0

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?

Cody
  • 9,785
  • 4
  • 61
  • 46
  • 1
    Example to clarify: if a form starts clean with an input value of 'something', and then the user edits it to be 'something else', and then hits undo (for instance) to bring the value back to 'something', are you expecting dirty to become true when the input is changed and then false when the value is put back to its original? – Conor Mancone Sep 05 '17 at 18:04
  • 1
    As the other sites referenced have said, "dirty" means edited ... even if you edit it back to the same value. The "dirty" processing does *not* track your form's original values so it does not know if the user put the same value back. Angular has *no* built in mechanism that keeps the state of your original data. If you need that functionality, you'll need to implement it yourself ... or use something like this: https://github.com/ngrx/store – DeborahK Sep 05 '17 at 18:26
  • 1
    Another option is to use reactive forms instead of template-driven forms. Then your data model is *not* changed by any changes that the user makes. Then you can compare the form values to your original data model to determine if the forms looks exactly like it did before editing. – DeborahK Sep 05 '17 at 18:31
  • @ConorMancone, thanks for the inquiry. That's precisely the goal.
    @DeborahK, I'm aware that Angular has a different notion of what `dirty` means, I happen to disagree to say that _edited_ is actually _touched_. I also will be steering clear of Reactive Forms as the word on Google Avenue states they'll be deprecated -- but thanks for the suggestion.
    – Cody Sep 05 '17 at 22:20
  • @Cody Do you have the reference/link for that? I'm on the Angular doc team and have not heard anything like that about Reactive Forms. – DeborahK Sep 05 '17 at 23:04
  • @Cody I just checked with the developer of both forms modules and **NEITHER** are going away. Not sure where you heard that one is being deprecated but it is not true. Could you provide the link to where you saw that? – DeborahK Sep 05 '17 at 23:45
  • @DeborahK, that's great news. I didn't read it, though, a friend/colleague mentioned a fellow colleague at Google (I believe) stated this. I'll see if I can inquire from my colleague to see if such a statement is `pristine` or just plain old `dirty`. – Cody Sep 06 '17 at 05:29
  • I'd appreciate that. Thanks! – DeborahK Sep 06 '17 at 05:34

2 Answers2

2

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.

DeborahK
  • 57,520
  • 12
  • 104
  • 129
0

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.

Cody
  • 9,785
  • 4
  • 61
  • 46