typescript - Hur man kontrollerar ändringar i ett formulär i Angular 2 med
Jag har ett formulär med några datafält och två knappar. Jag vill aktivera knapparna endast om användaren gör några ändringar i formuläret. Jag har försökt använda:
this.form.valueChanges.subscribe(data => console.log('form changes', data));
Men ändringarna upptäcks också initialt när formuläret laddas. Finns det något annat sätt att kontrollera om det har skett några ändringar i formuläret? Jag vill att det ska kallas endast när användaren gör ändringar i fältet och inte när formuläret laddas. Följande är min HTML- och TypeScript-kod:
profil.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();
}
}
Svar #1
Du kan använda .dirty (eller .pristine) värden för att avgöra om en användare har använt användargränssnittet för att ändra kontrollvärdet:
<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 En kontroll är smutsig om användaren har ändrat värdet i användargränssnittet.
Observera att programmatiska ändringar av ett kontrollvärde inte kommer att markera det som smutsigt.
touched : boolean En kontroll markeras som touched när användaren har utlösts ett blur-evenemang på den.
Svar #2
Problemet med .dirty och .pristine booleanerna är att när de väl ändras, går de inte tillbaka, även om du ångrar alla ändringar du gjort. Jag lyckades hitta ett sätt att lösa detta genom att skapa en klass som övervakar ändringar i hela formuläret och kontrollerar de ändrade värdena mot de ursprungliga formulärvärdena. På detta sätt, om användarens ändringar ångras, kan formuläret gå tillbaka till pristine, eller alternativt skicka ut en boolean på en observable (ReplaySubject) som du kan tillhandahålla och prenumerera på.
Användningen kommer att vara något sådant här:
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);
}
Alternativt, istället för att återställa .pristine/.dirty-booleans, kan klassen konfigureras för att avge ett booleanvärde varje gång formuläret ändras från intakt till modifierat och vice versa. Ett sant booleanvärde betyder att formuläret gick tillbaka till att vara intakt, medan ett falskt booleanvärde betyder att formuläret inte längre är intakt.
Här är ett exempel på hur du skulle använda det:
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);
}
Slutligen, klassen som gör allt arbetet:
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;
}
}
VIKTIGT: Var försiktig med startvärden
Klassen använder JSON.stringify() för att snabbt jämföra hela formGroup värdeobjektet. Var dock försiktig när du initialiserar kontrollvärden.
Till exempel, för kryssrutor måste du ställa in värdet genom att binda det till en boolean. Om du använder andra typer, såsom "checked", "0", "1", etc., kommer jämförelsen inte att fungera korrekt.
<input type="checkbox" ... [(ngModel)]="variable"> <!-- variable must be a boolean -->
Samma gäller för <select>, du måste binda dess värde till en sträng, inte ett nummer:
<select ... [(ngModel)]="variable"> <!-- variable must be a string -->
För vanliga textinmatningskontroller, använd också en sträng:
<input type="text" ... [(ngModel)]="variable"> <!-- variable must be a string -->
Här är ett exempel varför det annars inte skulle fungera. Antag att du har ett textfält, och du initialiserar det med ett heltal. Omvandlingen till sträng av det ursprungliga värdet skulle vara något i stil med detta:
{ field1: 34, field2: "något textfält" }
Men om användaren uppdaterar fält1 till ett annat värde och går tillbaka till 34, blir den nya strängifieringen:
{ fält: "34", fält2: "något textfält" }
Som du kan se, även om formen egentligen inte förändrades, kommer strängjämförelsen mellan det ursprungliga och det nya värdet att resultera i falskt, på grund av citattecknen runt siffran 34.
