【问题标题】:Get a hold of a FormControl instance by querying a component's template通过查询组件的模板获取 FormControl 实例
【发布时间】:2017-04-02 04:07:41
【问题描述】:

我有一个自定义的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
}

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

然后我的表单如下所示:

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

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

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

<field label="Title">
  <input type="text" formControlName="title">
</field>

注意[theControl] 输入属性是如何消失的。 FieldComponent 应该能够获取它包含的 FormControl 实例,但是如何?

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

export class FormFieldComponent {
  @ContentChildren(FormControlDirective) theControl: any;
}

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

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

【问题讨论】:

  • 我不明白你在做什么。我的应用中有许多反应式表单,从未使用过 [control],我只使用 formControlName。
  • @John: [control] 不是 Angular 指令,它只是我给输入属性的名称。我会将[control] 重命名为[theControl] 以使其更明显。那么,您知道组件如何获取出现在其模板中的 FormControl 实例吗?
  • 你不能在
    标签中添加#formInstance 吗?
  • 我的问题是,只要您可以通过实现ControlValueAccessor 创建自己的自定义输入,为什么要这样做?
  • 很高兴看到解决方案 n00dl3

标签: angular angular2-forms


【解决方案1】:

简短回答:你不能

你就是做不到。 (好吧,也许你可以,但它会很老套!)

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

FormControl 不可注射。指令是可注入的,但是,您必须处理 formControlNamengModelformControl 等,并且它们不能从包装组件访问,但它的子组件...

对于您的情况,您可以尝试使用@ContentChildren(FormControlName) theControl: any;,因为您的代码中没有隐含FormControlDirective,但无论如何您都无法访问FormControl(属性_control 是内部的,所以它会做个黑客)...

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

BUT 如果您想显示自定义输入(不会按原样显示错误消息,但能够显示此输入处于错误状态(主机元素将获得 @987654332 @、ng-invalid 类,所以这只是风格问题),您可以通过实现 ControlValueAccessor 来做到这一点。

控件和原生元素之间的桥梁。

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

这意味着实现此接口的指令/组件可以与ngModelformControl等一起使用...

例如:&lt;my-component [(ngModel)]="foo"&gt;&lt;/my-component&gt;

这不是您的问题的确切再现,但这个实现为我解决了同样的问题:

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);
  }
}

那么你就可以了:

<field label="Title" formControlName="title"></field>

【讨论】:

  • @n00dl3:感谢您提供非常详细和清晰的答案。我很好奇:除了&lt;input type="text"&gt;,具体是&lt;select&gt;&lt;textarea&gt;...字段,你是如何处理字段的?
  • 你可以通过输入类型创建一个组件。如果你想为每个字段类型调用一个组件,你可以在模板中创建一个带有type@Input()ngSwitch的包装器(它也应该实现ControlValueAccessor并通过@ViewChild()调用children方法参考)。对于选择(无线电相同),它有点复杂,因为您需要创建一个my-select 和一个my-option 组件,将my-select 注入其子项中。 (待续)...
  • 您可以查看angular repository 中的代码,尤其是*_value_accessor.ts 文件。但是您不会在这些文件中获得无线电/选择的帮助,因为它们正在使用内部属性和方法(但是我在之前的评论中告诉您的效果很好,如果您需要专门的帮助(注入父母等...)我担心这超出了这个问题的范围)...现在...啤酒时间!!!!!!
  • @AngularFrance 需要更多帮助?
【解决方案2】:

您可以通过以下方式获取表单控件名称实例:

@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);
  }
}

【讨论】:

  • 这应该是公认的答案。这对我很有用。
【解决方案3】:

不确定事情是否发生了变化,但我找到了一种看似受支持的方式来做到这一点here

基本上,您可以使用 @ContentChild 装饰器,并根据您的表单控件或将其链接到模板控件的方式,使用正确的指令作为装饰器的选择器。

例如,如果我要投射一个使用案例的输入,即 [formControl],那么您与该 GitHub 票证的作者处于同一条船上。对我来说,我使用的是“formGroupName”指令,尽管令人困惑,但该指令的正确用法是@ContentChild(FormControlName) control;

HTH!

【讨论】:

    猜你喜欢
    • 2018-01-27
    • 2016-08-04
    • 2018-06-07
    • 2023-03-30
    • 2014-06-30
    • 1970-01-01
    • 2015-03-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多