web-dev-qa-db-fra.com

Comment vérifier les changements de forme dans Angular 2 en utilisant

J'ai un formulaire avec peu de champs de données et deux boutons. Je souhaite activer les boutons uniquement si l'utilisateur apporte des modifications au formulaire. J'ai essayé d'utiliser:

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

Mais les modifications sont détectées initialement lorsque le formulaire est également chargé. Existe-t-il un autre moyen de vérifier si des modifications ont été apportées au formulaire? Je veux qu'il soit appelé uniquement lorsque l'utilisateur apporte des modifications au champ et non lorsque le formulaire est chargé. Voici mon code HTML et TypeScript:

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();
    }
}
16
Valla

Vous pouvez utiliser les valeurs .dirty (ou .pristine) pour déterminer si un utilisateur a utilisé l'interface utilisateur pour modifier la valeur de contrôle:

<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 Un contrôle est sale si l'utilisateur a modifié la valeur dans l'interface utilisateur.

Notez que les modifications apportées par programme à la valeur d'un contrôle ne le marqueront pas sale.

touched: boolean Un contrôle est marqué comme touché une fois que l'utilisateur a déclenché un événement de flou sur elle.

14
silentsod

Le problème avec les booléens .dirty et .pristine, c'est qu'une fois qu'ils ont changé, ils ne reviennent pas en arrière, même si vous annulez tous les changements que vous avez apportés. J'ai réussi à trouver un moyen de résoudre ce problème en créant une classe qui surveille les modifications dans l'ensemble du formulaire et vérifie les valeurs modifiées avec les valeurs de formulaire d'origine. Ainsi, si les modifications apportées par l'utilisateur sont annulées, le formulaire peut redevenir vierge ou éventuellement émettre un booléen sur un observable (ReplaySubject) que vous pouvez fournir et auquel vous pouvez vous abonner.

L'utilisation sera quelque chose comme ça:

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);

}

Alternativement, au lieu de réinitialiser les booléens .pristine/.dirty, la classe peut être configurée pour émettre un booléen chaque fois que le formulaire passe de intact à modifié et vice-versa. Un vrai booléen signifie que la forme est redevenue intacte, alors qu'un faux booléen signifie que la forme n'est plus intacte.

Voici un exemple d'utilisation:

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);

}

Enfin, la classe qui fait tout le travail:

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: Attention aux valeurs initiales

La classe utilise JSON.stringify() pour comparer rapidement l’ensemble de l’objet valeur formGroup. Cependant, soyez prudent lorsque vous initialisez les valeurs de contrôle.

Par exemple, pour les cases à cocher, vous devez définir la valeur la liant à un booléen. Si vous utilisez d'autres types, tels que "coché", "0", "1", etc., la comparaison ne fonctionnera pas correctement.

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

Il en va de même pour <select>, vous devez lier sa valeur à une chaîne, pas à un nombre:

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

Pour les contrôles de saisie de texte normaux, utilisez également une chaîne:

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

Voici un exemple pourquoi sinon cela ne fonctionnera pas. Supposons que vous ayez un champ de texte et que vous l'initialisiez avec un entier. Le stringify de la valeur originale ressemblerait à ceci:

{champ1: 34, champ2: "un champ de texte"}

Toutefois, si l'utilisateur met à jour field1 avec une valeur différente et revient à 34, la nouvelle chaîne sera:

{champ: "34", champ2: "un champ de texte"}

Comme vous pouvez le constater, bien que le formulaire n’ait pas vraiment changé, la comparaison des chaînes entre la valeur originale et la nouvelle valeur sera fausse, à cause des guillemets autour du nombre 34.

10
kontiki

Essayez ce qui suit pour voir si le formulaire a changé:

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

Je parviens à travailler autour de cela en faisant modifier une variable: 

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

Et ensuite, sur les entrées, vous définissez la variable modifiée sur l'événement ionChange:

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

Je suppose que vous pouvez simplement ignorer le premier changement

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

Astuce: importer l'opérateur skip

1
Günter Zöchbauer

vous pouvez vérifier les changements dans le contrôle de formulaire particulier comme ceci:

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

                );
0
Shailesh kala