Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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();
    }
}
like image 681
Valla Avatar asked Dec 05 '16 20:12

Valla


People also ask

What is the use of valueChanges method on a FormControl?

The ValueChanges is an event raised by the Angular forms whenever the value of the FormControl, FormGroup or FormArray changes. It returns an observable so that you can subscribe to it. The observable gets the latest value of the control. It allows us to track changes made to the value in real-time and respond to it.

Which class is applied on a form control if its value is changed in Angular?

ng-touched will be applied if the condition is true and ng-untouched will be applied if false. This pair of classes defines the state of the control whether its value has been changed or not. ng-dirty will be applied if the condition is true and ng-pristine will be applied if false.


4 Answers

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.

like image 150
silentsod Avatar answered Oct 19 '22 13:10

silentsod


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.

like image 30
kontiki Avatar answered Oct 19 '22 11:10

kontiki


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.

like image 8
Shahrukh khan Avatar answered Oct 19 '22 11:10

Shahrukh khan


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.

like image 4
Sae Avatar answered Oct 19 '22 13:10

Sae