首页 > 解决方案 > Angular6 - 尝试在我的表单中实现嵌套的自定义表单字段时出现问题和错误行为(使用 Angular Material)

问题描述

我正在使用角度材料创建一个表单,在该表单中我使用 *ngFor 指令循环以显示许多不同类型的输入。我想做的是将 matInput 嵌套在另一个组件中并在我的表单中重用它。我想我快要成功了,但我在这个控制台错误上被困了超过 3 天:

formControlName 必须与父 formGroup 指令一起使用。您需要添加一个 formGroup 指令并将一个现有的 FormGroup 实例传递给它(您可以在您的类中创建一个)。

例子:

<div [formGroup]="myGroup">
  <input formControlName="firstName">
</div>

In your class:

this.myGroup = new FormGroup({
   firstName: new FormControl()
});

这个错误对我来说毫无意义,因为我已经在我的表单中实现了一个 [formGroup] 指令,如下所示。所以我想知道如何解决这个错误。

基本上,到目前为止,我所做的是遵循材料官方网站上的指南:https ://material.angular.io/guide/creating-a-custom-form-field-control#trying-it-out 这是一些代码向您展示我一直在实施的内容:

input.component.html

<input matInput
  placeholder="placeholder"
  formControlName="formControlName"
/>

输入组件.ts

import { Component, Input, HostBinding, Self, Optional, ElementRef, Output } from '@angular/core';
import { MatFormFieldControl } from '@angular/material';
import { FormControlName, ControlValueAccessor, NgControl, FormBuilder, FormGroup } from '@angular/forms';
import { Subject } from 'rxjs';
import { FocusMonitor } from '@angular/cdk/a11y';
import { coerceBooleanProperty } from '@angular/cdk/coercion';


class MyInput {
  constructor(public ftdName: string, public ftdConformite: string, public ftdEmetteur: string,
              public ftdNation: string, public ftdNonConformite: string, public ftdGed: string, 
              public ftdConstatExterne: string) { }
}

@Component({
  selector: 'app-input',
  templateUrl: './input.component.html',
  styleUrls: ['./input.component.css'],
  providers: [
    {
      provide : MatFormFieldControl, useExisting: InputComponent
    }]
})

export class InputComponent implements ControlValueAccessor, MatFormFieldControl<MyInput> {

  static nextId = 0;
  public _placeholder: string;
  public _disabled = false;
  public _required = false;
  public stateChanges = new Subject<void> ();
  public focused = false;
  public errorState = false;
  public listForm: FormGroup;

  onChange: any = () => {}
  onTouch: any = () => {}

  @HostBinding('class.floating')
  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }

  get empty() {
    let n = this.listForm.value;
    return !n.ftdName && !n.ftdConformite && !n.ftdNonConformite && !n.ftdConstatExterne && !n.ftdEmetteur && !n.ftdGed && !n.ftdNation;
  } 

  @HostBinding('attr.aria-describedby') describedBy = '';
  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  @HostBinding() id = `example-myinput-${InputComponent.nextId++}`

  @Input() 
  formControlName: FormControlName;

  @Input() 
  get placeholder() {
    return this._placeholder
  }
  set placeholder(plh) {
    this._placeholder = plh;
    this.stateChanges.next();
  }

  @Input()
  get required() {
    return this._required;
  }
  set required(req) {
    this._required = coerceBooleanProperty(req);
    this.stateChanges.next();
  }

  @Input()
  get disabled(): boolean {
     return this._disabled;
  }
  set disabled(value: boolean) {
    this._disabled = coerceBooleanProperty(value);
    this._disabled ? this.listForm.disable() : this.listForm.enable();
    this.stateChanges.next();
  }

  constructor(@Optional() @Self() public ngControl : NgControl,
  fb: FormBuilder, private fm: FocusMonitor, private elRef: ElementRef<HTMLElement>) { 

    this.listForm = fb.group({
      'ftdName' : '',
      'ftdConformite' : '',
      'ftdEmetteur' : '',
      'ftdNation' : '',
      'ftdNonConformite' : '',
      'ftdGed' : '', 
      'ftdConstatExterne' : ''
    })

    fm.monitor(elRef.nativeElement, true)
    .subscribe(origin => {
      this.focused = !!origin;
      this.stateChanges.next();
    });

    if(this.ngControl != null) {
      this.ngControl.valueAccessor = this
    }
  }

  onContainerClick(event: MouseEvent) {
    if ((event.target as Element).tagName.toLowerCase() != 'input') {
      this.elRef.nativeElement.querySelector('input').focus();
    }
  }

  set value(input : MyInput | null) {
    this.stateChanges.next();
  }

  // this method sets the value programmatically
  writeValue(value:any) {
    this.value = value
  }
  // upon UI element value changes, this method gets triggered
  registerOnChange(fn: any){
    this.onChange = fn
  }

  // upon touching the element, this method gets triggered
  registerOnTouched(fn: any){
    this.onTouch = fn
  }

  ngOnDestroy() {
    this.stateChanges.complete();
    this.fm.stopMonitoring(this.elRef.nativeElement);
  }
}

form.component.html(我在其中调用我的应用程序输入,我简化了代码以仅显示与我的问题有关的部分。)

<div class="matContainer">
  <form class="EventForm" [formGroup]="listForm" (ngSubmit)="onSubmitList()">
    <ng-container *ngFor="let group of jsonArrays.groups; let last = last">
      <ng-container *ngFor="let input of group.inputs">

        <mat-form-field *ngIf="input.type === 'matInput'" class="colonne{{ 
         input.colonne }}">
          <app-input 
            placeholder="{{ input.placeholder }}"
            formControlName="{{ input.formControlName }}"
          ></app-input>
        </mat-form-field>

      </ng-container>
    </ng-container>
  </form>
</div>

结果,只有当我单击第一个输入(始终显示)时,我才能看到通常显示的输入,或者如果我调整页面大小,我对这种行为一无所知。但是,无论如何,控制台中的错误消息仍然显示。如果您有任何问题,或者我可以澄清的事情,请不要犹豫,我会很高兴。

标签: angulartypescriptangular-materialangular6

解决方案


推荐阅读