Problem z .dirty i .piękne booleans, jest to, że gdy się zmienią, nie wrócą, nawet jeśli cofniesz wszystkie wprowadzone zmiany. Udało mi się znaleźć sposób na rozwiązanie tego problemu, tworząc klasę, która monitoruje zmiany w całym formularzu i sprawdzi zmienione wartości z oryginalnymi wartościami formularza. W ten sposób, jeśli zmiany użytkownika zostaną cofnięte, formularz może powrócić do pierwotnego poziomu lub opcjonalnie emitować wartość logiczną na obserwowalnym (ReplaySubject), który można dostarczyć i zasubskrybować.
Zastosowanie będzie mniej więcej tak:
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);
}
Alternatywnie, zamiast przestawiania wartości logicznych .pristine/.dirty klasa może być skonfigurowany do wysyłania logiczną, przy każdej zmianie formy od nienaruszonej modyfikowane i nawzajem. Prawdziwa wartość boolowska oznacza, że forma wróciła do stanu nienaruszonego, podczas gdy fałszywa wartość boolowska oznacza, że formularz nie jest już nienaruszony.
Oto przykład, w jaki sposób chcesz go używać:
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);
}
Wreszcie klasa, która wykonuje całą pracę:
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;
}
}
UWAGA: Ostrożnie z wartościami początkowymi
Klasa używa JSON.stringify()
, aby szybko porównać cały obiekt klasy FormGrup. Należy jednak zachować ostrożność podczas inicjowania wartości kontrolnych.
Na przykład w przypadku pól wyboru należy ustawić wartość wiążącą je na wartość logiczną. Jeśli użyjesz innych typów, takich jak "sprawdzone", "0", "1" itp., Porównanie nie będzie działać poprawnie.
<input type="checkbox" ... [(ngModel)]="variable"> <!-- variable must be a boolean -->
To samo tyczy się <select>
, trzeba związać swoją wartość na ciąg znaków, a nie liczba:
<select ... [(ngModel)]="variable"> <!-- variable must be a string -->
Dla stałych kontroli wprowadzania tekstu, również użyć ciągu:
<input type="text" ... [(ngModel)]="variable"> <!-- variable must be a string -->
Oto przykład, dlaczego w przeciwnym razie nie będzie działać. Załóżmy, że masz pole tekstowe i zainicjujesz je za pomocą liczby całkowitej. Stringify pierwotnej wartości byłoby coś takiego:
{pole1: 34, pole2: „niektóre pola tekstowego”}
Jednakże, jeśli aktualizacje użytkownika pole1 na inną wartość i wraca do 34, nowa stringify będą:
{pole: „34”, pole2: „niektóre pola tekstowego”}
Jak widać, mimo że forma tak naprawdę nie zmieniło, porównanie ciąg między oryginałem a nowa wartość spowoduje fałszywe, ze względu na notowania wokół liczby 34.
Możliwy duplikat [Jak obejrzeć zmiany formy w Angular 2?] (Http://stackoverflow.com/questions/34615425/how-to-watch-for-form-changes-in-angular-2) – blo0p3r