通过查询组件的模板来获取FormControl实例

Ang*_*hef 10 angular2-forms angular

我有一个自定义FormFieldComponent,用于封装表单字段的HTML和错误显示逻辑:

@Component({
  selector: 'field',
  template: `
    <div class="form-group">
      <label class="control-label">{{label}}</label>
      <ng-content></ng-content>  <!-- Will display the field -->
      <!-- Here, put error display logic -->
    </div>
  `
})
export class FormFieldComponent {
  @Input() label: string;  // Field label
  @Input() theControl: FormControl;  // Current form control, required to display errors
}
Run Code Online (Sandbox Code Playgroud)

FormFieldComponent,我需要一个FormControl实例来显示错误.

我的表单看起来像这样:

<form [formGroup]="myForm">
  ...
  <field label="Title" [theControl]="myForm.get('title')">
    <input type="text" formControlName="title">
  </field>
  ...
</form>
Run Code Online (Sandbox Code Playgroud)

但我对上面的代码并不完全满意.如您所见,我在两个位置指定字段的键:在[theControl]输入属性和formControlName指令中.

如果我能写的话,代码会更简洁:

<field label="Title">
  <input type="text" formControlName="title">
</field>
Run Code Online (Sandbox Code Playgroud)

注意[theControl]输入属性是如何消失的.本FieldComponent应该能够拿到FormControl实例包含,但如何保持?

我已经尝试使用@ContentChildren装饰器查询组件的FormControl指令模板,但它不起作用:

export class FormFieldComponent {
  @ContentChildren(FormControlDirective) theControl: any;
}
Run Code Online (Sandbox Code Playgroud)

另一种选择是将字段的密钥作为输入传递给FormFieldComponent然后让组件使用该密钥:

  • 以编程方式将formControlName指令应用于它包含的字段.
  • 获取其父级<form>,访问相应的FormGroup实例,并从中提取FormControl实例.

n00*_*dl3 8

简短的回答:你不能

你不能.(好吧,也许你可以,但它会是hacky!)

答案很长:你不能,但......

FormControl是不可注射的.指令是可注射的,但是,你将不得不处理formControlName,ngModel,formControl,等,他们将无法从包装组件,但它的儿童使用...

对于你的情况,你可以试试,@ContentChildren(FormControlName) theControl: any;因为FormControlDirective你的代码没有暗示,但你FormControl无论如何都无法访问(属性_control是内部的,所以它将是一个黑客)...

因此,您应该坚持从处理该组件的组件中管理您的错误FormGroup.

但是,如果你想显示一个自定义输入(不会显示错误信息,但能够显示此输入处于错误状态(主机元素将获取ng-valid,ng-invalid类,所以这只是一个风格问题),你可以通过实施来做到这一点ControlValueAccessor.

控件和本机元素之间的桥梁.

ControlValueAccessor抽象化将新值写入表示输入控件的DOM元素的操作.

这意味着实现了这个接口指令/组件可以使用ngModel,formControl等...

例如: <my-component [(ngModel)]="foo"></my-component>

它不是你问题的确切复制品,但是这个实现为我解决了同样的问题:

export const INPUT_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => InputComponent),
  multi: true
};

@Component({
  selector: "field",
  template: `<!--put anything you want in your template-->
            <label>{{label}}</label>
            <input #input (input)="onChange($event.target.value)" (blur)="onTouched()" type="text">`,
  styles: [],
  providers: [INPUT_VALUE_ACCESSOR]
})
export class InputComponent implements ControlValueAccessor {
  @ViewChild("input")
  input: ElementRef;
  @Input()
  label:string;
  onChange = (_: any) => { };
  onTouched = () => { };

  constructor(private _renderer: Renderer) { }

  writeValue(value: any): void {
    const normalizedValue = value == null ? "" : value;
    this._renderer.setElementProperty(this.input.nativeElement, "value", normalizedValue);
  }

  registerOnChange(fn: (_: any) => void): void {
      this.onChange = fn;
  }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }

  setDisabledState(isDisabled: boolean): void {
    this._renderer.setElementProperty(this.input.nativeElement, "disabled", isDisabled);
  }
}
Run Code Online (Sandbox Code Playgroud)

那你就可以:

<field label="Title" formControlName="title"></field>
Run Code Online (Sandbox Code Playgroud)


Ste*_*ush 6

您可以通过以下方式获得 Form Control Name 实例:

@Component({
  selector: 'field',
  templateUrl: './field.component.html',
  styleUrls: ['./field.component.scss']
})
export class FieldComponent implements AfterContentInit {
  @Input()
  public label: string;

  @ContentChild(FormControlName)
  public controlName: FormControlName;

  public ngAfterContentInit(): void {
    console.log(this.controlName.control);
  }
}
Run Code Online (Sandbox Code Playgroud)