如何将验证器和 CSS 样式传播到自定义 Angular 表单组件内的输入控件?
How to propagate validators and CSS styles to input control inside a custom Angular form component?
我有一个嵌套表单组件,其中 <input>
字段设置为与 formControlName="nested"
一起使用。验证器在父 FormControl
上设置,如下所示:
form = this.fb.group({
value: ['', [Validators.required, Validators.minLength(3)]],
nested: ['', [Validators.required, Validators.minLength(3)]],
});
我想将状态从父级 FormControl
传播到嵌套 <input>
,因此它的反应方式与常规非嵌套 <input>
、 即 只需触摸它并单击提交(control.markAsTouched()
),状态设置为无效并且设置 CSS 样式 ng-invalid
。
我设法获得了部分胜利,使用以下订阅父控件状态变化的代码,但"touching"嵌套输入会将其恢复为 VALID 并且单击提交不会设置 ng-invalid
样式。
@ViewChild('tbNgModel', {static: false}) tbNgModel: NgModel;
private isAlive = true;
constructor(@Optional() @Self() public ngControl: NgControl, private cdr: ChangeDetectorRef) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
ngAfterViewInit(): void {
this.ngControl.statusChanges.pipe(
takeWhile(() => this.isAlive)
).subscribe(status => {
console.log('Status changed: ', status, 'Errors: ', this.ngControl.errors);
this.tbNgModel.control.setErrors(this.ngControl.errors);
this.cdr.detectChanges();
});
this.ngControl.control.updateValueAndValidity(); // to force emit initial value
}
Stackblitz reproduction of my problem
如何才能真正将状态传播到嵌套控件,同时仅在父控件上设置验证器 FormControl
?
最终解决方案
根据@Eliseo 的回答和,这是我最终实现的(它对我有用,但可能还有更好的方法)
component.ts
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
...
getValidationCss() {
if (!this.ngControl) return {};
return {
'ng-invalid': this.ngControl.invalid,
'is-invalid': this.ngControl.invalid && this.ngControl.touched,
'ng-valid': this.ngControl.valid,
'ng-touched': this.ngControl.touched,
'ng-untouched': this.ngControl.untouched,
'ng-pristine': this.ngControl.pristine,
'ng-dirty': this.ngControl.dirty,
};
}
component.html
...
<input #tb class="form-control" [ngClass]="getValidationCss()" ...>
...
Dstj,事情一定要简单一些。查看 stackblitz
就在我们的输入中,我们可以使用 [ngClass]
<input [ngClass]="{'ng-touched':ngControl.control.touched,'ng-invalid':ngControl.control.invalid}"
type="text" class="form-control" [(ngModel)]="value"
(ngModelChange)="propagateChange($event)"
(blur)="touched()"
>
其中ngControl是在构造函数中注入的ngControl
constructor(@Self() public ngControl: NgControl) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
看到是ngControlvalid/invalidtouched/untouched.....
已更新 添加(模糊)标记为已触摸
Updated2 使用 ngDoCheck
其他解决方案是使用ngDoCheck,
我们的组件
value: string;
customClass=null;
onChange:boolean=false;
constructor(@Self() public ngControl: NgControl,private el:ElementRef) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
ngDoCheck()
{
if (!this.onChange)
{
this.onChange=true;
//it's necesary a setTimeOut to give Angular a chance
setTimeout(()=>{
this.customClass=this.el.nativeElement.getAttribute('class');
this.onChange=false;
})
}
}
change(value:any)
{
this.propagateChange(value);
this.touched(null)
}
.html
<input #tbNgModel="ngModel" [ngClass]="customClass" type="text" class="form-control"
[(ngModel)]="value"
(ngModelChange)="change($event)"
(blur)="touched()">
我有一个嵌套表单组件,其中 <input>
字段设置为与 formControlName="nested"
一起使用。验证器在父 FormControl
上设置,如下所示:
form = this.fb.group({
value: ['', [Validators.required, Validators.minLength(3)]],
nested: ['', [Validators.required, Validators.minLength(3)]],
});
我想将状态从父级 FormControl
传播到嵌套 <input>
,因此它的反应方式与常规非嵌套 <input>
、 即 只需触摸它并单击提交(control.markAsTouched()
),状态设置为无效并且设置 CSS 样式 ng-invalid
。
我设法获得了部分胜利ng-invalid
样式。
@ViewChild('tbNgModel', {static: false}) tbNgModel: NgModel;
private isAlive = true;
constructor(@Optional() @Self() public ngControl: NgControl, private cdr: ChangeDetectorRef) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
ngAfterViewInit(): void {
this.ngControl.statusChanges.pipe(
takeWhile(() => this.isAlive)
).subscribe(status => {
console.log('Status changed: ', status, 'Errors: ', this.ngControl.errors);
this.tbNgModel.control.setErrors(this.ngControl.errors);
this.cdr.detectChanges();
});
this.ngControl.control.updateValueAndValidity(); // to force emit initial value
}
Stackblitz reproduction of my problem
如何才能真正将状态传播到嵌套控件,同时仅在父控件上设置验证器 FormControl
?
最终解决方案
根据@Eliseo 的回答和
component.ts
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
...
getValidationCss() {
if (!this.ngControl) return {};
return {
'ng-invalid': this.ngControl.invalid,
'is-invalid': this.ngControl.invalid && this.ngControl.touched,
'ng-valid': this.ngControl.valid,
'ng-touched': this.ngControl.touched,
'ng-untouched': this.ngControl.untouched,
'ng-pristine': this.ngControl.pristine,
'ng-dirty': this.ngControl.dirty,
};
}
component.html
...
<input #tb class="form-control" [ngClass]="getValidationCss()" ...>
...
Dstj,事情一定要简单一些。查看 stackblitz
就在我们的输入中,我们可以使用 [ngClass]
<input [ngClass]="{'ng-touched':ngControl.control.touched,'ng-invalid':ngControl.control.invalid}"
type="text" class="form-control" [(ngModel)]="value"
(ngModelChange)="propagateChange($event)"
(blur)="touched()"
>
其中ngControl是在构造函数中注入的ngControl
constructor(@Self() public ngControl: NgControl) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
看到是ngControlvalid/invalidtouched/untouched.....
已更新 添加(模糊)标记为已触摸
Updated2 使用 ngDoCheck
其他解决方案是使用ngDoCheck,
我们的组件
value: string;
customClass=null;
onChange:boolean=false;
constructor(@Self() public ngControl: NgControl,private el:ElementRef) {
if (this.ngControl != null) {
this.ngControl.valueAccessor = this;
}
}
ngDoCheck()
{
if (!this.onChange)
{
this.onChange=true;
//it's necesary a setTimeOut to give Angular a chance
setTimeout(()=>{
this.customClass=this.el.nativeElement.getAttribute('class');
this.onChange=false;
})
}
}
change(value:any)
{
this.propagateChange(value);
this.touched(null)
}
.html
<input #tbNgModel="ngModel" [ngClass]="customClass" type="text" class="form-control"
[(ngModel)]="value"
(ngModelChange)="change($event)"
(blur)="touched()">