Valla
Valla

Reputation: 2442

How to check for changes in form in Angular 2 using

I have a form with few data fields and two buttons.I want to enable the buttons only if the user makes some changes to the form. I have tried using:

this.form.valueChanges.subscribe(data => console.log('form changes', data));

But the changes are detected initially when the form loads also. Is there any other way to check for any changes in the form. I want it to be called only when user makes changes to the field and not when the form loads. Following is my html and typescript code:

profile.html:

<section>
    <div>
        <form [formGroup]="form">
            <fieldset>
                <div class="panel-group m-l-1 m-r-1 accordion vertical-scroll" id="">
                    <div class="form-group required no-gutter">
                        <label for="firstname"> First Name:</label>
                        <div class="col-md-7 col-lg-6">
                            <input type="text" class="form-control" id="firstname" placeholder="" name="firstname" title="firstname" formControlName="firstname" size="128" aria-required="true" maxlength="35">
                        </div>
                    </div>
                </div>

            </fieldset>
            <div>
                <button class="btn btn-primary" type="button" (click)="save()">Save</button>
                <button class="btn btn-primary" type="button" (click)="cancel()">Cancel</button>
            </div>
        </form>
    </div>
</section>

profile.component.ts:

export class ProfileComponent implements OnInit, AfterViewInit, OnChanges {
    public form: FormGroup;

    constructor(private formBuilder: FormBuilder, private app: Application) {

    }

    loadForm(): void {
        this.form = this.formBuilder.group({
            firstname: [this.app.firstName, Validators.required]
        });
        this.form.valueChanges.subscribe(data => console.log('form changes', data));

    }

    save(): void {

    }

    cancel(): void {

    };

    ngOnInit() {
        this.loadForm();
    }

    ngAfterViewInit() {
        this.loadForm();
    }
}

Upvotes: 37

Views: 134482

Answers (11)

bug5layer
bug5layer

Reputation: 239

There is an answer

https://stackoverflow.com/a/63488981/11186896

You can use markAsPristine method to set pristine status true, when the data changes back to defaultValue like this:

ngOnInit() {
    const defaultValue = this.registrationForm.value;
    this.registrationForm.valueChanges
      .pipe(debounceTime(200))
      .subscribe(value => {
        if (JSON.stringify(defaultValue) == JSON.stringify(value)) {
          this.registrationForm.markAsPristine();
        }
      });
  }

Upvotes: 0

tejas n
tejas n

Reputation: 728

You can pass { emitEvent: false } as options for the below reactive form methods to prevent them from triggering the valueChanges event

this.form.patchValue(value, { emitEvent: false })
this.form.setValue(value, { emitEvent: false })
this.form.controls.email.updateValueAndValidity({ emitEvent: false })
this.form.disable({ emitEvent: false })

yes disable triggers the valueChanges event

PS: above this.form is a reactive form

Read this excellent post, it'll answer all your questions and even give some great insights on reactive forms:

https://netbasal.com/angular-reactive-forms-tips-and-tricks-bb0c85400b58

Upvotes: 3

edu
edu

Reputation: 443

You can compare your object against the result of the form when submitting

let changes = false;
for ( let [ key, value ] of Object.entries( this.form.value ) ) {
    const form = this.form.value;
    const record = this.record;
    if ( form[ key ] != record[ key ] ) {
        changes = true;
        break;
    }
}
if ( !changes ) {
    // No changes
} else {
    this.record = this.form.value;
    this.UpdateRecord();
}

Upvotes: 1

Sae
Sae

Reputation: 505

I use some trick for my code, i think this not the best solution but somehow it's work for me.

profile.component.ts:

tempForm: any
ngOnInit() {
  this.loadForm();
  this.tempForm = this.form.value
}

save(): void {
  if (this.tempForm === this.form.value) {
    // Do Save
  } else {
    // Value is Same as initial
  }
}

hope this solve your question, or maybe just give some inspiration.

Upvotes: 4

alexalejandroem
alexalejandroem

Reputation: 1162

I manage to work arround this by having a variable modified:

<button ion-button icon-only clear type="submit" [disabled]="!modified || !editForm.valid">
    <ion-icon name="checkmark"></ion-icon>
</button>

And then on the inputs you set the modified variable on the ionChange event:

<ion-input type="text" (ionChange)="modified=true"></ion-input> 

Upvotes: 3

Shahrukh khan
Shahrukh khan

Reputation: 196

first of all use "NgForm".
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)">
Then on the "onSubmit()" function do this -

onSubmit(myForm: NgForm): void {
  let formControls = myForm.controls;
  if(formControls.firstName.dirty) {
    console.log("It's dirty");
  }
  else {
    console.log("not dirty");
  }
} 

It will definitely work. You can print the whole "myForm" and see for yourselves what all options are available to use.

Upvotes: 8

kontiki
kontiki

Reputation: 40509

The problem with the .dirty and .pristine booleans, is that once they change, they do not go back, even if you undo all the changes you introduced. I managed to find a way of solving this, by creating a class that monitors changes in the entire form, and will check the changed values with the original form values. This way, if the user changes are undone, the form can go back to pristine, or optionally emit a boolean on an observable (ReplaySubject) you can provide and subscribe to.

The use will be something like this:

private _formIntactChecker:FormIntactChecker;

constructor(private _fb: FormBuilder) { 

    this._form = _fb.group({
        ...
     });

    // from now on, you can trust the .dirty and .pristine to reset
    // if the user undoes his changes.
    this._formIntactChecker = new FormIntactChecker(this._form);

}

Alternatively, instead of resetting the .pristine/.dirty booleans, the class can be configured to emit a boolean whenever the form changes from intact to modified and viceversa. A true boolean means, the form went back to being intact, while a false boolean means the form is no longer intact.

Here's an example on how you would use it:

private _formIntactChecker:FormIntactChecker;

constructor(private _fb: FormBuilder) { 

     this._form = _fb.group({
        ...
     });

     var rs = new ReplaySubject()

     rs.subscribe((isIntact: boolean) => {
        if (isIntact) {
            // do something if form went back to intact
        } else {
            // do something if form went dirty
        }
     })

     // When using the class with a ReplaySubject, the .pristine/.dirty
     // will not change their behaviour, even if the user undoes his changes,
     // but we can do whatever we want in the subject's subscription.
     this._formChecker = new FormIntactChecker(this._form, rs);

}

Finally, the class that does all the work:

import { FormGroup } from '@angular/forms';
import { ReplaySubject } from 'rxjs';

export class FormIntactChecker {

    private _originalValue:any;
    private _lastNotify:boolean;

    constructor(private _form: FormGroup, private _replaySubject?:ReplaySubject<boolean>) {

        // When the form loads, changes are made for each control separately
        // and it is hard to determine when it has actually finished initializing,
        // To solve it, we keep updating the original value, until the form goes
        // dirty. When it does, we no longer update the original value.

        this._form.statusChanges.subscribe(change => {
            if(!this._form.dirty) {
                this._originalValue = JSON.stringify(this._form.value);
            }
        })

        // Every time the form changes, we compare it with the original value.
        // If it is different, we emit a value to the Subject (if one was provided)
        // If it is the same, we emit a value to the Subject (if one was provided), or
        // we mark the form as pristine again.

        this._form.valueChanges.subscribe(changedValue => {

            if(this._form.dirty) {
                var current_value = JSON.stringify(this._form.value);

                if (this._originalValue != current_value) {
                    if(this._replaySubject && (this._lastNotify == null || this._lastNotify == true)) {
                        this._replaySubject.next(false);
                        this._lastNotify = false;
                    }
                } else {
                    if(this._replaySubject)
                        this._replaySubject.next(true);
                    else
                        this._form.markAsPristine();

                    this._lastNotify = true;
                }
            }
        })
    }

    // This method can be call to make the current values of the
    // form, the new "orginal" values. This method is useful when
    // you save the contents of the form but keep it on screen. From
    // now on, the new values are to be considered the original values
    markIntact() {
        this._originalValue = JSON.stringify(this._form.value);

        if(this._replaySubject)
            this._replaySubject.next(true);
        else
            this._form.markAsPristine();

        this._lastNotify = true;
    }
}

IMPORTANT: Careful with initial values

The class uses JSON.stringify() to quickly compare the whole formGroup value object. However, be careful when you initialize control values.

For example, for checkboxes, you must set the value binding it to a boolean. If you use other types, such as "checked", "0", "1", etc., the comparison will fail to work properly.

<input type="checkbox" ... [(ngModel)]="variable"> <!-- variable must be a boolean -->

The same goes to <select>, you must bind its value to a string, not a number:

<select ... [(ngModel)]="variable"> <!-- variable must be a string -->

For regular text input controls, also use a string:

<input type="text" ... [(ngModel)]="variable"> <!-- variable must be a string -->

Here is an example why otherwise it won't work. Suppose you have a text field, and you initialize it with an integer. The stringify of the original value would be something like this:

{ field1: 34, field2: "some text field" }

However, if the user updates field1 to a different value and goes back to 34, the new stringify will be:

{ field: "34", field2: "some text field" }

As you can see, although the form did not really changed, the string comparison between the original and the new value will result false, due to the quotes around the number 34.

Upvotes: 20

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657198

I guess you can just ignore the first change

this.form.valueChanges
.skip(1)
.subscribe(data => console.log('form changes', data));

Hint: import the skip operator

Upvotes: 3

Shailesh  kala
Shailesh kala

Reputation: 1852

you can check for the changes in perticular formcontrol like this:

this.profileForm.controls['phone'].valueChanges.subscribe(
                data => console.log('form changes', data)

                );

Upvotes: 1

dvanrensburg
dvanrensburg

Reputation: 1441

Try the following to see if the form has changed:

ngOnChanges() {
    if (!!this.form && this.form.dirty) {
        console.log("The form is dirty!");
    }
    else {
        console.log("No changes yet!");
    }      
}  

Upvotes: 3

silentsod
silentsod

Reputation: 8335

You can use the .dirty (or .pristine) values to determine if a user has used the UI to change the control value:

<button class="btn btn-primary" type="button" (click)="save()" [disabled]="!form.dirty" >Save</button>
<button class="btn btn-primary" type="button" [disabled]="!form.dirty"(click)="cancel()">Cancel</button>

https://angular.io/docs/ts/latest/api/forms/index/AbstractControl-class.html#!#dirty-anchor

dirty : boolean A control is dirty if the user has changed the value in the UI.

Note that programmatic changes to a control's value will not mark it dirty.

touched : boolean A control is marked touched once the user has triggered a blur event on it.

Upvotes: 32

Related Questions