Angular 角度8:使用formControlName和ControlValueAccessor选择列表下拉列表
我正在尝试创建一个材质下拉包装(mat select dropdown),它将与formControlName一起使用。如果有人在他们的图书室里,他们可以发布他们的Stackblitz吗?您可以自由地从头开始,创建自己的答案,无论什么都可以满足需求 要求: 1) 需要使用formControlName。我们有一个带有formBuilder/的父组件表单,它的验证器正试图引用这个子包装器。作为典型场景,父组件formbuilder还有许多其他表单字段 2) 如果数据不符合父FormBuilder验证程序的要求,则需要显示错误红色“无效” 3) a)不仅需要使用formControlName/patchValue(patchValue应使用整个类);b) 如果有人将数据放入@Input()SelectedValueId Id号,也可以选择此选项。我可以和这两个人一起工作 正在尝试使其工作,但尚未成功, 有人有代码来修复这个问题吗 需要工作的stackblitz,为了成功的回答 在本例中,Id是地址Id的来源Angular 角度8:使用formControlName和ControlValueAccessor选择列表下拉列表,angular,typescript,angular-material,angular8,angular-reactive-forms,Angular,Typescript,Angular Material,Angular8,Angular Reactive Forms,我正在尝试创建一个材质下拉包装(mat select dropdown),它将与formControlName一起使用。如果有人在他们的图书室里,他们可以发布他们的Stackblitz吗?您可以自由地从头开始,创建自己的答案,无论什么都可以满足需求 要求: 1) 需要使用formControlName。我们有一个带有formBuilder/的父组件表单,它的验证器正试图引用这个子包装器。作为典型场景,父组件formbuilder还有许多其他表单字段 2) 如果数据不符合父FormBuilder验
export class SourceOfAddressDto implements ISourceOfAddressDto {
sourceOfAddressId: number | undefined; // should work with this Id
sourceOfAddressCode: string | undefined;
sourceOfAddressDescription: string | undefined;
类型脚本:
@Component({
selector: 'app-address-source-dropdown',
templateUrl: './address-source-dropdown.component.html',
styleUrls: ['./address-source-dropdown.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressSourceDropdownComponent),
multi: true
}
]
})
export class AddressSourceDropdownComponent implements OnInit, OnChanges {
dataList: any[] = [];
@Input() Label = 'Address Source';
@Input() sourceOfAddressDefaultItem: SourceOfAddressDto = SourceOfAddressDefault;
@Input() selectedSourceOfAddress: any;
@Input() TxtValue = 'sourceOfAddressId';
@Input() TxtField = 'sourceOfAddressDescription';
@Input() Disabled: boolean;
@Input() valuesToExclude: number[] = [];
@Input() Hint = '';
@Input() styles: string;
@Input() defaultSourceOfAddressCode: any;
@Output() addressSourceChange = new EventEmitter<any>();
private _selectedValueId: number;
@Input() set selectedValueId(value: number) {
this._selectedValueId = value;
let outputData: any;
if (this.selectedValueId == this.sourceOfAddressDefaultItem[this.TxtValue]) {
outputData = null;
} else {
outputData = this.dataList.find(x => x[this.TxtValue] == this.selectedValueId);
}
this.onChange(outputData);
}
get selectedValueId(): any {
return this._selectedValueId;
}
@Input() errors: any = null;
disabled: boolean;
control: FormControl;
writeValue(value: any) {
this.selectedValueId = value ? value : '';
}
onChange = (_: any) => { };
onTouched: any = () => { };
registerOnChange(fn: any) { this.onChange = fn; }
registerOnTouched(fn: any) { this.onTouched = fn; }
setDisabledState(isDisabled) { this.disabled = isDisabled; }
constructor(
public injector: Injector,
private AddressService: AddressServiceProxy,
) { }
ngOnInit() {
this.loadDataList();
}
ngOnChanges() { }
loadDataList() {
this.AddressService.getSourceOfAddressAll().subscribe(res => {
this.dataList = res.body.filter(q => q.sourceOfAddressId !== -1);
});
}
}
<div class="dropdown-cont">
<mat-form-field appearance="outline">
<mat-label>{{Label}}</mat-label>
<mat-select
disableOptionCentering
[disabled]="Disabled"
[ngStyle]="styles"
(ngModelChange)="selectedValueId=$event"
required>
<mat-option [value]="sourceOfAddressDefaultItem[TxtValue]">{{sourceOfAddressDefaultItem[TxtField]}}</mat-option>
<mat-option *ngFor="let item of dataList" [value]="item[TxtValue]">
{{item[TxtField]}}
</mat-option>
</mat-select>
<mat-hint>{{Hint}}</mat-hint>
</mat-form-field>
</div>
@组件({
选择器:“应用程序地址源下拉列表”,
templateUrl:'./地址源下拉列表.component.html',
styleUrls:['./地址源下拉列表.component.scss'],
供应商:[
{
提供:NG_值访问器,
useExisting:forwardRef(()=>AddressSourceDropdownComponent),
多:真的
}
]
})
导出类AddressSourceDropdownComponent实现OnInit、OnChanges{
数据列表:任意[]=[];
@Input()标签='地址源';
@Input()sourceOfAddressDefaultItem:SourceOfAddressDto=SourceOfAddressDefault;
@Input()selectedSourceOfAddress:any;
@Input()TxtValue='sourceOfAddressId';
@Input()TxtField='sourceOfAddressDescription';
@Input()已禁用:布尔值;
@Input()值排除:数字[]=[];
@输入()提示=“”;
@Input()样式:字符串;
@Input()defaultSourceOfAddressCode:任意;
@Output()addressSourceChange=新的EventEmitter();
private _selectedValueId:number;
@Input()设置selectedValueId(值:数字){
这是。\ u selectedValueId=value;
让outputData:任意;
如果(this.selectedValueId==this.sourceOfAddressDefaultItem[this.TxtValue]){
outputData=null;
}否则{
outputData=this.dataList.find(x=>x[this.TxtValue]==this.selectedValueId);
}
此.onChange(输出数据);
}
获取selectedValueId():任意{
返回此项。\u selectedValueId;
}
@Input()错误:any=null;
禁用:布尔值;
控制:FormControl;
writeValue(值:任意){
this.selectedValueId=value?值:“”;
}
onChange=(uquo:any)=>{};
onTouched:any=()=>{};
registerOnChange(fn:any){this.onChange=fn;}
registerOnTouched(fn:any){this.onTouched=fn;}
setDisabledState(isDisabled){this.disabled=isDisabled;}
建造师(
公共注入器:注入器,
专用AddressService:AddressServiceProxy,
) { }
恩戈尼尼特(){
这是loadDataList();
}
ngOnChanges(){}
loadDataList(){
此.AddressService.GetSourceOfAddressSall().subscribe(res=>{
this.dataList=res.body.filter(q=>q.sourceofaddress!=-1);
});
}
}
HTML:
@Component({
selector: 'app-address-source-dropdown',
templateUrl: './address-source-dropdown.component.html',
styleUrls: ['./address-source-dropdown.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AddressSourceDropdownComponent),
multi: true
}
]
})
export class AddressSourceDropdownComponent implements OnInit, OnChanges {
dataList: any[] = [];
@Input() Label = 'Address Source';
@Input() sourceOfAddressDefaultItem: SourceOfAddressDto = SourceOfAddressDefault;
@Input() selectedSourceOfAddress: any;
@Input() TxtValue = 'sourceOfAddressId';
@Input() TxtField = 'sourceOfAddressDescription';
@Input() Disabled: boolean;
@Input() valuesToExclude: number[] = [];
@Input() Hint = '';
@Input() styles: string;
@Input() defaultSourceOfAddressCode: any;
@Output() addressSourceChange = new EventEmitter<any>();
private _selectedValueId: number;
@Input() set selectedValueId(value: number) {
this._selectedValueId = value;
let outputData: any;
if (this.selectedValueId == this.sourceOfAddressDefaultItem[this.TxtValue]) {
outputData = null;
} else {
outputData = this.dataList.find(x => x[this.TxtValue] == this.selectedValueId);
}
this.onChange(outputData);
}
get selectedValueId(): any {
return this._selectedValueId;
}
@Input() errors: any = null;
disabled: boolean;
control: FormControl;
writeValue(value: any) {
this.selectedValueId = value ? value : '';
}
onChange = (_: any) => { };
onTouched: any = () => { };
registerOnChange(fn: any) { this.onChange = fn; }
registerOnTouched(fn: any) { this.onTouched = fn; }
setDisabledState(isDisabled) { this.disabled = isDisabled; }
constructor(
public injector: Injector,
private AddressService: AddressServiceProxy,
) { }
ngOnInit() {
this.loadDataList();
}
ngOnChanges() { }
loadDataList() {
this.AddressService.getSourceOfAddressAll().subscribe(res => {
this.dataList = res.body.filter(q => q.sourceOfAddressId !== -1);
});
}
}
<div class="dropdown-cont">
<mat-form-field appearance="outline">
<mat-label>{{Label}}</mat-label>
<mat-select
disableOptionCentering
[disabled]="Disabled"
[ngStyle]="styles"
(ngModelChange)="selectedValueId=$event"
required>
<mat-option [value]="sourceOfAddressDefaultItem[TxtValue]">{{sourceOfAddressDefaultItem[TxtField]}}</mat-option>
<mat-option *ngFor="let item of dataList" [value]="item[TxtValue]">
{{item[TxtField]}}
</mat-option>
</mat-select>
<mat-hint>{{Hint}}</mat-hint>
</mat-form-field>
</div>
{{Label}}
{{sourceOfAddressDefaultItem[TxtField]}
{{item[TxtField]}
{{Hint}}
- 即使API有时滞后,也希望可以使用默认值,并且默认值首先插入@Input SelectedValueId中
ControlValueAccessor
接口,让angular知道您想要使用反应式表单
export class AddressSourceDropdownComponent implements OnInit, OnChanges, ControlValueAccessor { ...
其他人提到,您需要显式实现
ControlValueAccessor
接口。虽然这绝对是一个好的实践,但在TypeScript中并不需要它,因为满足接口的任何东西都会隐式实现它,这是您使用writeValue
、registerOnChange
和registerOnTouched
方法(以及setDisabledState
执行的,但该方法是可选的)
因此,最大的问题是实现的细节。Angular依赖于此接口的实现来执行formControlName
(以及formControl
)的神奇双向绑定,其中父组件可以侦听并设置子组件上的值
您的writeValue
和register
很好。您的注册表更改
无效。在这里,您可以对组件进行本地更改并“注册”它们,这意味着您可以将通常的Angular valueChanges事件函数挂接到您自己的自定义valueChanges中
使用表单控件实现该功能的典型方法:
control = new FormControl('');
registerOnChange(fn: (value: string) => void) {
this.control.valueChanges
.subscribe(fn);
}
因此,一旦您执行了类似的操作,您将从父组件得到上下变化
现在,为了满足您的所有请求,您需要更多的自定义代码。不久前,我碰巧实现了一些非常类似的东西,它工作得很好,我在一个新的环境中重新创建了它
希望这足够让你走了 要使控件在mat form字段中工作,需要执行MatFormFieldControl接口的额外步骤。官方材料文档提供了一个很好的演练和代码示例,说明了如何在此处执行此操作:。请注意,该示例没有实现ControlValueAccessor,您仍然需要这样做。我最近为实现ckeditor的角度控件而做了这一步。您需要将此代码用于ControlValueAccessor这将与FormControl、FormControlName以及ngModel和