admin 管理员组文章数量: 1086019
In my application, I have a need for a reusable nested form ponent, such as Address. I want my AddressComponent
to deal with its own FormGroup, so that I don't need to pass it from the outside.
At Angular conference (video, presentation) Kara Erikson, a member of Angular Core team remended to implement ControlValueAccessor
for the nested forms, making the nested form effectively just a FormControl
.
I also figured out that I need to implement Validator
, so that the validity of my nested form can be seen by the main form.
In the end, I created the SubForm class that the nested form needs to extend:
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);
}
}
If you want your ponent to be used as a nested form, you need to do the following:
@Component({
selector: 'app-address',
templateUrl: './addressponent.html',
styleUrls: ['./addressponent.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)
});
}
}
Everything works well unless I check the validity status of my subform from the template of my main form. In this case ExpressionChangedAfterItHasBeenCheckedError
is produced, see ngIf
statement (stackblitz code) :
<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>
In my application, I have a need for a reusable nested form ponent, such as Address. I want my AddressComponent
to deal with its own FormGroup, so that I don't need to pass it from the outside.
At Angular conference (video, presentation) Kara Erikson, a member of Angular Core team remended to implement ControlValueAccessor
for the nested forms, making the nested form effectively just a FormControl
.
I also figured out that I need to implement Validator
, so that the validity of my nested form can be seen by the main form.
In the end, I created the SubForm class that the nested form needs to extend:
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);
}
}
If you want your ponent to be used as a nested form, you need to do the following:
@Component({
selector: 'app-address',
templateUrl: './address.ponent.html',
styleUrls: ['./address.ponent.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)
});
}
}
Everything works well unless I check the validity status of my subform from the template of my main form. In this case ExpressionChangedAfterItHasBeenCheckedError
is produced, see ngIf
statement (stackblitz code) :
<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>
Share
Improve this question
asked Aug 23, 2018 at 17:20
ganqqwertyganqqwerty
2,0042 gold badges25 silver badges37 bronze badges
3 Answers
Reset to default 5 +50Use ChangeDetectorRef
Checks this view and its children. Use in bination with detach to implement local change detection checks.
This is a cautionary mechanism put in place to prevent inconsistencies between model data and UI so that erroneous or old data are not shown to a user on the page
Ref:https://blog.angularindepth./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.ponent.html',
styleUrls: ['./upper.ponent.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();
}
}
Your Forked Example:https://stackblitz./edit/github-3q4znr
WriteValue will be triggered in the same digest cycle with the normal change detection lyfe cycle hook.
To fix that without using changeDetectionRef you can define your validity status field and change it reactively.
public firstNameValid = false;
this.form.controls.firstName.statusChanges.subscribe(
status => this.firstNameValid = status === 'VALID'
);
<div *ngIf="firstNameValid">Hi</div>
Try to use [hidden] in stand of *ngIf, it will work without ChangeDetectorRef.
Update URL : https://stackblitz./edit/github-3q4znr-ivtrmz?file=src/app/upper/upper.ponent.html
<div [hidden]="!form.controls['address'].valid">Hi</div>
版权声明:本文标题:javascript - How to properly implement nested forms with Validator and Control Value Accessor? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://roclinux.cn/p/1744027443a2520882.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论