用验证器包装 angular 反应式表单组件
Wrapping angular reactive form component with validator
使用 angular 7 和 Bootstrap 4,我想将 bootstrap 4 输入包装在自定义组件中,以减少模板中的样板文件。
我希望最终的主要组件如下所示:
<form [formGroup]="myForm" (submit)="submit(myForm.value)">
<app-form-control label="Lastname" placeholder="Lastname" formControlName="lastName"></app-form-control>
<app-form-control label="Firstname" placeholder="Firstname" formControlName="firstName"></app-form-control>
<button class="pull-right" type="submit">
SUBMIT
</button>
<button (click)="reset()">
RESET
</button>
</form>
我的 formGroup 是这样创建的:
public createFormGroup() {
return this.fb.group({
firstName: [null, Validators.required],
lastName: [null, Validators.required],
});
}
app-form-control 的模板应该是这样的:
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}" [formControlName]="formControlName" autocomplete="nope"/>
</div>
</div>
但我不知道如何编写组件(在 TypeScript 中)。如何将外部 formControlName 属性绑定到内部输入字段?如何进行验证?
你可以通过实现ControlValueAccessor
来实现。让我们通过构建 TextBoxComponent
.
来逐步完成流程
步骤 1: 为文本字段创建 NG_VALUE_ACCESSOR
作为 TEXTBOX_VALUE_ACCESSOR
。
const TEXTBOX_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextBoxComponent),
multi: true,
};
第 2 步: 将 ControlValueAccessor
实施到我们的组件 TextBoxComponent
。
export class TextBoxComponent implements ControlValueAccessor{
...
...
}
步骤 3: 定义 ControlValueAccessor
的未实现方法。 TextBoxComponent
的详细代码如下。
@Component({
selector: "text-box",
template: `
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}" [(ngModel)]="inputValue" />
</div>
</div>
`,
providers: [TEXTBOX_VALUE_ACCESSOR],
})
export class TextBoxComponent implements ControlValueAccessor {
private _inputValue: any = '';
private _onTouchedCallback: () => {};
private _onChangeCallback: (_:any) => {};
@Input("label") label: string = "Your Label";
@Input("placeholder") placeholder: string = "Your Placeholder";
get inputValue(): any {
return this._inputValue;
}
set inputValue(value: any) {
if (value !== this._inputValue) {
this._inputValue = value;
this._onChangeCallback(value);
}
this._onTouchedCallback();
}
//From ControlValueAccessor interface
writeValue(value: any) {
this._inputValue = value;
}
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this._onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this._onTouchedCallback = fn;
}
}
使用方法:
<form [formGroup]="formGroup">
<text-box formControlName="textboxControl" label="My Label" placeholder="My Placeholder"></text-box>
<pre>{{formGroup.value | json}}</pre>
</form>
完整的代码在stackblitz。
"key" 正在使用 viewProvider。您使用 @Input 集为 formControl 赋值,请参阅 stackblitz。 "magic" 是指 if equal 在 "children" 中引用 formControl 或在父
中引用 form.get('input1')
@Component({
selector: 'app-form-control',
template: `
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}"
[formControl]="formControl" autocomplete="nope"/>
</div>
</div>
<!--you can control the properties of formControl-->
{{formControl.valid}}{{formControl.touched}}}
`,
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]})
export class HelloComponent {
formControl: FormControl;
constructor(private parentF: FormGroupDirective) { }
@Input()
set controlName(value) {
this.formControl = this.parentF.form.get(value) as FormControl
}
@Input() label: string;
@Input() placeholder: string;
}
并这样调用组件:
<form [formGroup]="myForm" (submit)="submit(myForm.value)">
<app-form-control label="Lastname" placeholder="Lastname" controlName="lastName"></app-form-control>
</form>
更新 好吧,(一年后)考虑到 stackblitz 是错误的。当您(单击)按钮创建一个新表单时:
this.form=this.createForm({note:'lll'})
这 "break" 组件和表单之间的关系,因为该关系是关于旧表单的 - 只有更改 @Input "nameControl" 才会更改。所以正确的做法是使用 setValue 为表单赋予新值。
使用 angular 7 和 Bootstrap 4,我想将 bootstrap 4 输入包装在自定义组件中,以减少模板中的样板文件。
我希望最终的主要组件如下所示:
<form [formGroup]="myForm" (submit)="submit(myForm.value)">
<app-form-control label="Lastname" placeholder="Lastname" formControlName="lastName"></app-form-control>
<app-form-control label="Firstname" placeholder="Firstname" formControlName="firstName"></app-form-control>
<button class="pull-right" type="submit">
SUBMIT
</button>
<button (click)="reset()">
RESET
</button>
</form>
我的 formGroup 是这样创建的:
public createFormGroup() {
return this.fb.group({
firstName: [null, Validators.required],
lastName: [null, Validators.required],
});
}
app-form-control 的模板应该是这样的:
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}" [formControlName]="formControlName" autocomplete="nope"/>
</div>
</div>
但我不知道如何编写组件(在 TypeScript 中)。如何将外部 formControlName 属性绑定到内部输入字段?如何进行验证?
你可以通过实现ControlValueAccessor
来实现。让我们通过构建 TextBoxComponent
.
步骤 1: 为文本字段创建 NG_VALUE_ACCESSOR
作为 TEXTBOX_VALUE_ACCESSOR
。
const TEXTBOX_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TextBoxComponent),
multi: true,
};
第 2 步: 将 ControlValueAccessor
实施到我们的组件 TextBoxComponent
。
export class TextBoxComponent implements ControlValueAccessor{
...
...
}
步骤 3: 定义 ControlValueAccessor
的未实现方法。 TextBoxComponent
的详细代码如下。
@Component({
selector: "text-box",
template: `
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}" [(ngModel)]="inputValue" />
</div>
</div>
`,
providers: [TEXTBOX_VALUE_ACCESSOR],
})
export class TextBoxComponent implements ControlValueAccessor {
private _inputValue: any = '';
private _onTouchedCallback: () => {};
private _onChangeCallback: (_:any) => {};
@Input("label") label: string = "Your Label";
@Input("placeholder") placeholder: string = "Your Placeholder";
get inputValue(): any {
return this._inputValue;
}
set inputValue(value: any) {
if (value !== this._inputValue) {
this._inputValue = value;
this._onChangeCallback(value);
}
this._onTouchedCallback();
}
//From ControlValueAccessor interface
writeValue(value: any) {
this._inputValue = value;
}
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this._onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this._onTouchedCallback = fn;
}
}
使用方法:
<form [formGroup]="formGroup">
<text-box formControlName="textboxControl" label="My Label" placeholder="My Placeholder"></text-box>
<pre>{{formGroup.value | json}}</pre>
</form>
完整的代码在stackblitz。
"key" 正在使用 viewProvider。您使用 @Input 集为 formControl 赋值,请参阅 stackblitz。 "magic" 是指 if equal 在 "children" 中引用 formControl 或在父
中引用 form.get('input1')@Component({
selector: 'app-form-control',
template: `
<div class="form-group row">
<label class="col-2 col-form-label">{{label}}</label>
<div class="col-10">
<input class="form-control" placeholder="{{placeholder}}"
[formControl]="formControl" autocomplete="nope"/>
</div>
</div>
<!--you can control the properties of formControl-->
{{formControl.valid}}{{formControl.touched}}}
`,
viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]})
export class HelloComponent {
formControl: FormControl;
constructor(private parentF: FormGroupDirective) { }
@Input()
set controlName(value) {
this.formControl = this.parentF.form.get(value) as FormControl
}
@Input() label: string;
@Input() placeholder: string;
}
并这样调用组件:
<form [formGroup]="myForm" (submit)="submit(myForm.value)">
<app-form-control label="Lastname" placeholder="Lastname" controlName="lastName"></app-form-control>
</form>
更新 好吧,(一年后)考虑到 stackblitz 是错误的。当您(单击)按钮创建一个新表单时:
this.form=this.createForm({note:'lll'})
这 "break" 组件和表单之间的关系,因为该关系是关于旧表单的 - 只有更改 @Input "nameControl" 才会更改。所以正确的做法是使用 setValue 为表单赋予新值。