首页 > 解决方案 > Angular自定义组件在提交时显示无效的表单元素

问题描述

我有一个包含几个自定义表单组件的反应表单。我创建了这个组件以在多种表单中使用,并且我从 “ControlValueAccessor”接口继承,它运行良好,我查看每个自定义组件的值并查看表单和组件是否有效。但是我提交表单后无法显示验证错误消息。

例如,我有一个自动完成组件来显示办公室位置,通常当触摸此组件并且未选择位置时,您会看到有关用户必须选择位置的错误;但它不适用于表单提交。

这是组件的html:

<div  [formGroup]="cityForm">
  <mat-form-field  appearance="outline">
   <mat-label i18n >Office locations</mat-label>
   <input matInput  i18n-aria-label
     aria-label="cities"
     [matAutocomplete]="auto"
     [formControl]="officeLocationControl">
     <mat-error *ngIf="officeLocationControl.hasError('required')" i18n >
      Please select an <strong>Ofice location</strong>
    </mat-error>
   <mat-autocomplete #auto="matAutocomplete">
    <mat-option *ngFor="let city of filteredCities | async" [value]="city.name">
     <span>{{city.name}}</span> 
    </mat-option>
 </mat-autocomplete>
</mat-form-field>
</div>
这是组件 ts 文件:

import { CityService } from './../../services/city.service';
import { ChangeDetectionStrategy, Component, forwardRef, OnDestroy, OnInit } from '@angular/core';
import { City } from 'src/app/shared/models/city';
import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import { Observable, Subscription } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

export interface CityFormValues{
  officeLocation : string
}

@Component({
  selector: 'app-city',
  templateUrl: './city.component.html',
  styleUrls: ['./city.component.css'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CityComponent),
      multi: true
     },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => CityComponent),
      multi: true
    }
  ],
   changeDetection: ChangeDetectionStrategy.OnPush
})
export class CityComponent implements ControlValueAccessor, OnInit, OnDestroy {
  cityForm: FormGroup;
  cities : City[];
  selectedCity: string;
  officeLocationControl = new FormControl(true,Validators.required);
  filteredCities : Observable<City[]>;

  subscriptions: Subscription[] = [];

  get value(): CityFormValues {
     return  this.cityForm.value ;
  }

  set value(value: CityFormValues) {
    this.cityForm.setValue(value);
    this.onChange(value);
    this.onTouched();
  }

  constructor(private cityService: CityService, private formBuilder: FormBuilder) {
    this.cityForm = this.formBuilder.group({
      officeLocation: this.officeLocationControl
    });
    this.subscriptions.push(
      this.cityForm.valueChanges.subscribe(value => {
        this.onChange(value);
        this.onTouched();
      })
    );
   }

  ngOnInit(): void {
     this.cityService.getAllCities().subscribe(p => { this.cities = p;
     this.filteredCities = this.officeLocationControl.valueChanges.pipe(
       startWith(''),
       map(value => this._filter(value))
     )
    }
    );
  }

  ngOnDestroy() {
    this.subscriptions.forEach(s => s.unsubscribe());
  }

  onChange: any = () => {};
  onTouched: any = () => {};

  registerOnChange(fn) {
    this.onChange = fn;
  }

  writeValue(value) {
    if (value) {
      this.value = value;
    }

    if (value === null) {
      this.cityForm.reset();
    }
  }

  registerOnTouched(fn) {
    this.onTouched = fn;
  }

  validate(_: FormControl) {
    return this.cityForm.valid ? null : { profile: { valid: false } };
  }

  private _filter(value: string): City[] {
    const filterValue = value.toLowerCase();

    return this.cities.filter(city => city.name.toLowerCase().includes(filterValue));
  }

}

这是父表单控件html:

<form style="width: 100%;" [formGroup]="clientProfileForm" (ngSubmit)="submit()">
<div class="wrapper">
    <div  class="one" fxLayout="row" fxLayoutAlign="start start" >
        <mat-card style="height: auto; width: 100%; margin: 1%; ">
        <div class="component-wrapper">
          <app-city formControlName="officeLocation"></app-city>
        </div>
      </mat-card>
    </div>
    <mat-divider class="three" ></mat-divider>
    <div class="three"  fxLayout="row" fxLayoutAlign="end center">
      <button type="submit" style="width: 10%;" mat-stroked-button i18n="@@clientProfileFormNextButton"         color="primary">Next</button>
    </div>
  </div>
</form>
<p *ngIf="showValidationError">
  Form is {{clientProfileForm.valid ? 'Valid' : 'Invalid'}}
</p>

和 ts 文件:

import { ClientService } from './../../services/client.service';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';

@Component({
  selector: 'app-client-form',
  templateUrl: './client-form.component.html',
  styleUrls: ['./client-form.component.css']
})
export class ClientFormComponent implements OnInit {
  clientProfileForm: FormGroup;
  showValidationError = false;
  
  constructor(private formbuilder: FormBuilder, private clientService: ClientService) {
      this.clientProfileForm = this.formbuilder.group({
        officeLocation:[]
      });
   }

  ngOnInit(): void {
  }
  submit() {
    if(this.clientProfileForm.valid){
        this.clientService.postClient(this.clientProfileForm.value);
    }else{
        this.showValidationError = true;
        this.clientProfileForm.get("officeLocation").updateValueAndValidity();
        this.clientProfileForm.get("officeLocation").markAsTouched();
    }
  }
}

我想如果我将组件设置为已触摸(我相信这不是一个好的解决方案)它可能会起作用,但它也不起作用。

 this.clientProfileForm.get("officeLocation").updateValueAndValidity();
    this.clientProfileForm.get("officeLocation").markAsTouched();

当我单击控件并且不选择城市时,效果很好。 在此处输入图像描述

当我单击下一步按钮提交表单时,未显示错误消息:

在此处输入图像描述

请问有什么想法吗?

标签: angulartypescriptangular-materialangular-formsangular-custom-validators

解决方案


推荐阅读