如何将验证器和 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()">