جاوااسکریپت - چگونه فرمهای تو در تو را به درستی با Validator و Control Value Accessor پیادهسازی کنیم؟
در برنامه من، من به یک کامپوننت فرم تو در تو قابل استفاده مجدد نیاز دارم، مانند Address. من میخواهم AddressComponent خود با FormGroup خودش کار کند، به طوری که نیازی به ارسال آن از خارج نباشد. در کنفرانس Angular (ویدیو، ارائه)، کارا اریکسون، یکی از اعضای تیم Angular Core توصیه کرد که برای فرمهای تو در تو، ControlValueAccessor را پیادهسازی کنیم، که باعث میشود فرم تو در تو عملاً فقط یک FormControl باشد.
من همچنین متوجه شدم که باید Validator را پیادهسازی کنم، تا صحت فرم تو در توی من توسط فرم اصلی قابل مشاهده باشد.
در نهایت، من کلاس SubForm را ایجاد کردم که فرم تو در تو نیاز دارد از آن ارث ببرد:
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);
}
}
اگر میخواهید مؤلفه شما به عنوان یک فرم تو در تو استفاده شود، باید موارد زیر را انجام دهید:
@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)
});
}
}
همه چیز خوب کار میکند مگر اینکه وضعیت اعتبار زیرفرم خود را از قالب فرم اصلیام بررسی کنم. در این حالت خطای ExpressionChangedAfterItHasBeenCheckedError تولید میشود، به عبارت ngIf نگاه کنید (کد stackblitz) :
<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>
پاسخ شماره ۱
از ChangeDetectorRef استفاده کنید
این نما و فرزندان آن را بررسی میکند. از آن همراه با detach برای پیادهسازی بررسیهای تشخیص تغییر محلی استفاده کنید.
این یک مکانیزم احتیاطی است که برای جلوگیری از ناسازگاری بین دادههای مدل و رابط کاربری قرار داده شده است تا دادههای نادرست یا قدیمی به کاربر در صفحه نمایش داده نشوند
ارجاع: https://blog.angularindepth.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4
ارجاع: 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();
}
}
نمونه فورک شده شما: https://stackblitz.com/edit/github-3q4znr
پاسخ شماره ۲
WriteValue در همان چرخه هضم با هوک چرخهی حیات تشخیص تغییر معمولی فعال خواهد شد.
برای رفع آن بدون استفاده از changeDetectionRef، میتوانید فیلد وضعیت اعتبار خود را تعریف کرده و به صورت واکنشی آن را تغییر دهید.
public firstNameValid = false; this.form.controls.firstName.statusChanges.subscribe(
status => this.firstNameValid = status === 'VALID'
);
<div *ngIf="firstNameValid">Hi</div>