Angular 8:带有 formControlName 和 ControlValueAccessor 的 Mat-Select 列表下拉菜单

0 typescript angular-material angular angular-reactive-forms angular8

我正在尝试创建一个材质下拉包装器(mat-select dropdown),它将与 formControlName 一起使用。如果某人的库中有 Stackblitz,可以发布他们的 Stackblitz 吗?随意从头开始,并创建自己的答案,只要符合要求即可。

要求:

1)需要与formControlName一起使用。我们有带有 formBuilder 的父组件表单及其验证器,它试图引用这个子包装器。作为典型场景,父组件表单构建器还有许多其他表单字段。

2) 如果数据不满足父 FormBuilder 验证器的要求,则需要显示红色无效错误。

3) a) 不仅需要与 formControlName/patchValue 一起使用(patchValue 应该与整个类一起使用);b) 如果有人将数据放入 @Input() SelectedValueId Id 数字,也可以选择。可以和两个一起工作

试图让它工作,但还没有成功,有人有任何代码来解决这个问题吗?

需要工作 stackblitz,才能获得成功的答案,

在本例中,Id 是 sourceOfAddressId

export class SourceOfAddressDto implements ISourceOfAddressDto {
    sourceOfAddressId: number | undefined;  // should work with this Id
    sourceOfAddressCode: string | undefined;
    sourceOfAddressDescription: string | undefined;
Run Code Online (Sandbox Code Playgroud)

打字稿:

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

}
Run Code Online (Sandbox Code Playgroud)

HTML:

<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>
Run Code Online (Sandbox Code Playgroud)
  • 也希望可以使用默认值,即使 API 有时会滞后,并且默认值首先插入到 @Input SelectedValueId 中

Jes*_*sse 5

其他人提到您需要显式实现该ControlValueAccessor接口。虽然这绝对是一个很好的实践,但在 TypeScript 中不是必需的,因为任何满足接口的东西也会隐式实现它,这是您使用writeValueregisterOnChangeregisterOnTouched方法(以及,setDisabledState但该方法是可选的)执行的操作。

因此,最大的问题是实施的细节。Angular 依赖于该接口的实现来执行神奇的双向绑定formControlNameformControl就此而言),其中父组件既可以监听子组件,也可以在子组件上设置值。

你的writeValue和都registerOnTouched很好。你的registerOnChange不是。在这里,您可以在组件本地进行更改并“注册”它们,这意味着您可以将常用的 Angular valueChanges 事件函数挂接到您自己的自定义 valueChanges 中。

使用表单控件来实现这一点的典型方法:

control = new FormControl('');

registerOnChange(fn: (value: string) => void) {
    this.control.valueChanges
        .subscribe(fn);
}
Run Code Online (Sandbox Code Playgroud)

因此,一旦您执行类似的操作,您就会从父组件中获得向上和向下的更改。

现在,为了满足您的所有请求,您将需要更多自定义代码。不久前我碰巧实现了一些非常相似的东西,并且效果很好,并且我在 stackblitz 中重新创建了

希望这足以让您继续前进。