javascript - Hur implementerar man korrekt nästlade formulär med Validator och Control Value Accessor?
I min applikation har jag behov av en återanvändbar nästlad formkomponent, såsom Address. Jag vill att min AddressComponent ska hantera sin egen FormGroup, så att jag inte behöver skicka den från utsidan. På Angular-konferensen (video, presentation) rekommenderade Kara Erikson, en medlem av Angular Core-teamet, att implementera ControlValueAccessor för de nästlade formulären, vilket gör den nästlade formen i praktiken bara till en FormControl.
Jag kom också på att jag behöver implementera Validator, så att giltigheten av mitt inbäddade formulär kan ses av huvudformuläret.
I slutändan skapade jag SubForm-klassen som det nästlade formuläret behöver ärva från:
export abstract class SubForm implements ControlValueAccessor, Validator {
form: FormGroup;
public onTouched(): void {
}
public writeValue(value: any): void {
if (value) {
this.form.patchValue(value, {emitEvent: false});
this.onTouched();
}
}
public registerOnChange(fn: (x: any) => void): void {
this.form.valueChanges.subscribe(fn);
}
public registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.form.disable()
: this.form.enable();
}
validate(c: AbstractControl): ValidationErrors | null {
return this.form.valid ? null : {subformerror: 'Problems in subform!'};
}
registerOnValidatorChange(fn: () => void): void {
this.form.statusChanges.subscribe(fn);
}
}
Om du vill att din komponent ska användas som ett inbäddat formulär behöver du göra följande:
@Component({
selector: 'app-address',
templateUrl: './address.component.html',
styleUrls: ['./address.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => AddressComponent),
multi: true
}
],
})
export class AddressComponent extends SubForm {
constructor(private fb: FormBuilder) {
super();
this.form = this.fb.group({
street: this.fb.control('', Validators.required),
city: this.fb.control('', Validators.required)
});
}
}
Allt fungerar bra såvida jag inte kontrollerar giltighetsstatusen för mitt underformulär från mallen för mitt huvudformulär. I detta fall uppstår ExpressionChangedAfterItHasBeenCheckedError, se ngIf-satsen (stackblitz-kod):
<form action=""
[formGroup]="form"
class="main-form">
<h4>Upper form</h4>
<label>First name</label>
<input type="text"
formControlName="firstName">
<div *ngIf="form.controls['address'].valid">Hi</div>
<app-address formControlName="address"></app-address>
<p>Form:</p>
<pre>{{form.value|json}}</pre>
<p>Validity</p>
<pre>{{form.valid|json}}</pre>
</form>
Svar #1
Använd ChangeDetectorRef
Kontrollerar denna vy och dess barn. Använd tillsammans med detach för att implementera lokala kontroller av förändringsdetektion.
Detta är en försiktighetsåtgärd som har införts för att förhindra inkonsekvenser mellan modellens data och användargränssnittet så att felaktiga eller gamla data inte visas för en användare på sidan
Ref: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
Ref:https://angular.io/api/core/ChangeDetectorRef
import { Component, OnInit,ChangeDetectorRef } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
@Component({
selector: 'app-upper',
templateUrl: './upper.component.html',
styleUrls: ['./upper.component.css']
})
export class UpperComponent implements OnInit {
form: FormGroup;
constructor(private fb: FormBuilder,private cdr:ChangeDetectorRef) {
this.form = this.fb.group({
firstName: this.fb.control('', Validators.required),
address: this.fb.control('')
});
}
ngOnInit() {
this.cdr.detectChanges();
}
}
Ditt gafflade exempel: https://stackblitz.com/edit/github-3q4znr
Svar #2
WriteValue kommer att utlösas i samma digest-cykel som den normala förändringsdetekteringslivscykelns hook.
För att åtgärda det utan att använda changeDetectionRef kan du definiera ditt giltighetsstatusfält och ändra det reaktivt.
public firstNameValid = false; this.form.controls.firstName.statusChanges.subscribe(
status => this.firstNameValid = status === 'VALID'
);
<div *ngIf="firstNameValid">Hi</div>