Tom*_*don 18 forms angular-material angular
我正在尝试在Angular(v5)中创建自定义表单控件.自定义控件本质上是一个Angular Material组件的包装器,但还有一些额外的东西.
我已经阅读了有关实现的各种教程ControlValueAccessor,但是我找不到任何可以编写组件来包装现有组件的内容.
理想情况下,我想要一个显示Angular Material组件的自定义组件(带有一些额外的绑定和东西),但是能够从父表单传递验证(例如required)并让Angular Material组件处理它.
例:
外部组件,包含表单并使用自定义组件
<form [formGroup]="myForm">
<div formArrayName="things">
<div *ngFor="let thing of things; let i = index;">
<app-my-custom-control [formControlName]="i"></app-my-custom-control>
</div>
</div>
</form>
Run Code Online (Sandbox Code Playgroud)
自定义组件模板
基本上我的自定义表单组件只包含一个Angular Material下拉列表和自动完成.我可以在不创建自定义组件的情况下执行此操作,但这样做似乎是有意义的,因为处理过滤等的所有代码都可以存在于该组件类中,而不是在容器类中(不需要)关心这个的实施).
<mat-form-field>
<input matInput placeholder="Thing" aria-label="Thing" [matAutocomplete]="thingInput">
<mat-autocomplete #thingInput="matAutocomplete">
<mat-option *ngFor="let option of filteredOptions | async" [value]="option">
{{ option }}
</mat-option>
</mat-autocomplete>
</mat-form-field>
Run Code Online (Sandbox Code Playgroud)
因此,在input更改时,应将该值用作表单值.
我尝试了几种方法,都有自己的陷阱:
绑定到事件keyup和blur事件input,然后通知父更改(即调用Angular传入的函数registerOnChange作为实现的一部分ControlValueAccessor).
这种方式有效,但是从下拉列表中选择一个值时,似乎更改事件不会触发,并且最终会处于不一致状态.
它也不考虑验证(例如,如果它是"必需的",当值为n时; t设置表单控件将正确无效,但Angular Material组件将不会如此显示).
这有点接近了.我在自定义组件类中创建了一个新表单,它只有一个控件.在组件模板中,我将该表单控件传递给Angular Material组件.在类中,我订阅valueChanges了它,然后将更改传播回父级(通过传入的函数registerOnChange).
这种作品,但感觉凌乱,应该有一个更好的方法.
这也意味着应用于我的自定义表单控件(由容器组件)的任何验证都会被忽略,因为我创建了一个缺少原始验证的新"内部表单".
ControlValueAccessor,而只是传递表格正如标题所说......我试图不以"正确"的方式做到这一点,而是添加了一个绑定到父表单.然后,我在自定义组件中创建一个表单控件作为该父表单的一部分.
这适用于处理值更新和扩展验证(但它必须作为组件的一部分创建,而不是父表单),但这只是错误.
处理这个问题的正确方法是什么?感觉我只是在绊倒不同的反模式,但我在文档中找不到任何暗示甚至支持它的东西.
我参加聚会有点晚了,但这是我包装一个可能接受formControlName, formControl, 或ngModel
@Component({
selector: 'app-input',
template: '<input [formControl]="control">',
styleUrls: ['./app-input.component.scss']
})
export class AppInputComponent implements OnInit, ControlValueAccessor {
constructor(@Optional() @Self() public ngControl: NgControl) {
if (this.ngControl != null) {
// Setting the value accessor directly (instead of using the providers) to avoid running into a circular import.
this.ngControl.valueAccessor = this;
}
}
control: FormControl;
// These are just to make Angular happy. Not needed since the control is passed to the child input
writeValue(obj: any): void { }
registerOnChange(fn: (_: any) => void): void { }
registerOnTouched(fn: any): void { }
ngOnInit() {
if (this.ngControl instanceof FormControlName) {
const formGroupDirective = this.ngControl.formDirective as FormGroupDirective;
if (formGroupDirective) {
this.control = formGroupDirective.form.controls[this.ngControl.name] as FormControl;
}
} else if (this.ngControl instanceof FormControlDirective) {
this.control = this.ngControl.control;
} else if (this.ngControl instanceof NgModel) {
this.control = this.ngControl.control;
this.control.valueChanges.subscribe(x => this.ngControl.viewToModelUpdate(this.control.value));
} else if (!this.ngControl) {
this.control = new FormControl();
}
}
}
Run Code Online (Sandbox Code Playgroud)
编辑:
我已经添加了一个助手来完成这个我已经启动的角度实用程序库:s-ng-utils.使用它你可以扩展WrappedFormControlSuperclass和写:
@Component({
selector: 'my-wrapper',
template: '<input [formControl]="formControl">',
providers: [provideValueAccessor(MyWrapper)],
})
export class MyWrapper extends WrappedFormControlSuperclass<string> {
// ...
}
Run Code Online (Sandbox Code Playgroud)
请在此处查看更多文档.
一种解决方案是获取@ViewChild()对应于内部表单组件ControlValueAccessor,并在您自己的组件中委派给它.例如:
@Component({
selector: 'my-wrapper',
template: '<input ngDefaultControl>',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumberInputComponent),
multi: true,
},
],
})
export class MyWrapper implements ControlValueAccessor {
@ViewChild(DefaultValueAccessor) private valueAccessor: DefaultValueAccessor;
writeValue(obj: any) {
this.valueAccessor.writeValue(obj);
}
registerOnChange(fn: any) {
this.valueAccessor.registerOnChange(fn);
}
registerOnTouched(fn: any) {
this.valueAccessor.registerOnTouched(fn);
}
setDisabledState(isDisabled: boolean) {
this.valueAccessor.setDisabledState(isDisabled);
}
}
Run Code Online (Sandbox Code Playgroud)
的ngDefaultControl模板中的上述是手动触发角到其正常连接DefaultValueAccessor到输入端.如果您使用<input ngModel>,这会自动发生,但我们不希望ngModel这里只是值访问器.您需要将DefaultValueAccessor上面的内容更改为材料下拉列表的值访问者 - 我自己并不熟悉Material.
我实际上已经围绕这个问题纠结了一段时间,我想出了一个与 Eric 非常相似(或相同)的很好的解决方案。他忘记说明的是,在视图实际加载之前,您不能使用@ViewChild valueAccessor(请参阅@ViewChild docs)
这是解决方案:(我给你的例子是用 NgModel 包装核心角度选择指令,因为你使用的是自定义 formControl,你需要定位该 formControl 的 valueAccessor 类)
@Component({
selector: 'my-country-select',
templateUrl: './country-select.component.html',
styleUrls: ['./country-select.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: CountrySelectComponent,
multi: true
}]
})
export class CountrySelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges {
@ViewChild(SelectControlValueAccessor) private valueAccessor: SelectControlValueAccessor;
private country: number;
private formControlChanged: any;
private formControlTouched: any;
public ngAfterViewInit(): void {
this.valueAccessor.registerOnChange(this.formControlChanged);
this.valueAccessor.registerOnTouched(this.formControlTouched);
}
public registerOnChange(fn: any): void {
this.formControlChanged = fn;
}
public registerOnTouched(fn: any): void {
this.formControlTouched = fn;
}
public writeValue(newCountryId: number): void {
this.country = newCountryId;
}
public setDisabledState(isDisabled: boolean): void {
this.valueAccessor.setDisabledState(isDisabled);
}
}
Run Code Online (Sandbox Code Playgroud)
and*_*590 -3
NgForm 提供了一种简单的方法来管理表单,而无需在 HTML 表单中注入任何数据。输入数据必须在组件级别注入,而不是在经典 html 标签中注入。
<form #myForm="ngForm" (ngSubmit)="onSubmit(myForm)>...</form>
Run Code Online (Sandbox Code Playgroud)
另一种方法是创建一个表单组件,其中所有数据模型都使用 ngModel 绑定;)
| 归档时间: |
|
| 查看次数: |
4377 次 |
| 最近记录: |