通过查询组件的模板获取 FormControl 实例
Get a hold of a FormControl instance by querying a component's template
我有一个自定义 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
指令应用于它包含的字段。
- 获取其父级
<form>
,访问相应的 FormGroup 实例,并从中提取 FormControl 实例。
简答:你不能
你就是做不到。 (好吧,也许你可以,但这会很麻烦!)
长答案:你不能,但是...
FormControl
不可注射。指令是可注入的,但是,您将不得不处理 formControlName
、ngModel
、formControl
等,它们不能从包装组件访问,但它的子组件...
对于您的情况,您可以尝试使用 @ContentChildren(FormControlName) theControl: any;
,因为您的代码中没有隐含 FormControlDirective
,但无论如何您都无法访问 FormControl
(属性 _control
是内部的,所以这将是一个 hack)...
所以你应该坚持从处理 FormGroup
.
的组件中管理你的错误
BUT 如果您想显示自定义输入(不会按原样显示错误消息,但能够显示此输入处于错误状态(宿主元素将得到 ng-valid
、ng-invalid
类,所以这只是样式问题),您可以通过实现 ControlValueAccessor
.
A bridge between a control and a native element.
A ControlValueAccessor abstracts the operations of writing a new value to a DOM element representing an input control.
这意味着 directives/components 实现此接口可以与 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);
}
}
那么你可以:
<field label="Title" formControlName="title"></field>
您可以通过以下方式获取 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);
}
}
不确定事情是否发生了变化,但我找到了一种看似受支持的方法来做到这一点here
基本上,您可以使用 @ContentChild
装饰器,并根据您的表单控件或您将其链接到模板控件的方式,使用正确的指令作为装饰器的选择器。
例如,如果我正在投影一个使用案例的输入,[formControl],您与该 GitHub 票证的作者在同一条船上。对我来说,我使用的是 'formGroupName' 指令,虽然令人困惑,但该指令的正确用法是 @ContentChild(FormControlName) control;
HTH!
我有一个自定义 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
指令应用于它包含的字段。 - 获取其父级
<form>
,访问相应的 FormGroup 实例,并从中提取 FormControl 实例。
简答:你不能
你就是做不到。 (好吧,也许你可以,但这会很麻烦!)
长答案:你不能,但是...
FormControl
不可注射。指令是可注入的,但是,您将不得不处理 formControlName
、ngModel
、formControl
等,它们不能从包装组件访问,但它的子组件...
对于您的情况,您可以尝试使用 @ContentChildren(FormControlName) theControl: any;
,因为您的代码中没有隐含 FormControlDirective
,但无论如何您都无法访问 FormControl
(属性 _control
是内部的,所以这将是一个 hack)...
所以你应该坚持从处理 FormGroup
.
BUT 如果您想显示自定义输入(不会按原样显示错误消息,但能够显示此输入处于错误状态(宿主元素将得到 ng-valid
、ng-invalid
类,所以这只是样式问题),您可以通过实现 ControlValueAccessor
.
A bridge between a control and a native element.
A ControlValueAccessor abstracts the operations of writing a new value to a DOM element representing an input control.
这意味着 directives/components 实现此接口可以与 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);
}
}
那么你可以:
<field label="Title" formControlName="title"></field>
您可以通过以下方式获取 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);
}
}
不确定事情是否发生了变化,但我找到了一种看似受支持的方法来做到这一点here
基本上,您可以使用 @ContentChild
装饰器,并根据您的表单控件或您将其链接到模板控件的方式,使用正确的指令作为装饰器的选择器。
例如,如果我正在投影一个使用案例的输入,[formControl],您与该 GitHub 票证的作者在同一条船上。对我来说,我使用的是 'formGroupName' 指令,虽然令人困惑,但该指令的正确用法是 @ContentChild(FormControlName) control;
HTH!