首页 > 解决方案 > 为什么重置自定义 Angular 反应式表单控件会显示不一致的行为?

问题描述

我有一个使用反应形式的 Angular (v10) 项目。有一个自定义的多选组件,实现ControlValueAccessor.

一切正常,在父组件中重置控件的行为很奇怪:

// This doesn't reset the value correctly. With the debugger I found out, that Angular updates
// the value correctly at some point, but after the call, the value is still/again the old one.
this.myMultiselect.reset({value: [], disabled: true});

// Splitting the reset and disabling, it works the expected way.
this.myMultiselect.reset([]);
this.myMultiselect.disable();

有具体原因吗?它是一个错误吗?我们想在任何地方都使用相同的样式(具有值/禁用的对象),但我不明白,为什么它不适用于我们的自定义组件。

其他表单控件与这两种变体都可以正常工作。

多选.component.ts

import {Component, forwardRef, Input, OnDestroy, OnInit} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';
import {takeUntil} from 'rxjs/operators';
import {ReplaySubject, Subject} from 'rxjs';
import {MultiselectOption} from './multiselect-option';

@Component({
    selector: 'eb-multiselect',
    templateUrl: './multiselect.component.html',
    styleUrls: ['./multiselect.component.scss'],
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            multi: true,
            useExisting: forwardRef(() => MultiselectComponent)
        }
    ]
})
export class MultiselectComponent implements OnInit, OnDestroy, ControlValueAccessor {
    @Input() withFilter = false;

    @Input() withSelectButtons = false;

    _options: MultiselectOption[];

    @Input()
    set options(value: MultiselectOption[]) {
        this._options = value;
        this.filteredOptions.next(this._options.slice());
    }

    @Input() labelText: string;

    selectControl = new FormControl();
    searchControl = new FormControl();
    filteredOptions: ReplaySubject<MultiselectOption[]> = new ReplaySubject<MultiselectOption[]>(1);

    protected _onDestroy = new Subject<void>();

    ngOnInit() {
        this.filteredOptions.next(this._options?.slice());

        this.searchControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => {
            this.filterOptions();
        });
    }

    ngOnDestroy() {
        this._onDestroy.next();
        this._onDestroy.complete();
    }

    filterOptions() {
        if (!this._options) {
            return;
        }
        let search = this.searchControl.value;
        if (!search) {
            this.filteredOptions.next(this._options.slice());
            return;
        } else {
            search = search.toLowerCase();
        }
        this.filteredOptions.next(this._options.filter(option => option.name.toLowerCase().indexOf(search) !== -1));
    }

    selectAll() {
        this.selectControl.setValue(this._options.map(o => o.value));
    }

    deselectAll() {
        this.selectControl.setValue([]);
    }

    writeValue(value: any): void {
        this.selectControl.setValue(value);
    }

    registerOnChange(fn: (value: any) => void): void {
        this.selectControl.valueChanges.subscribe(fn);
    }

    registerOnTouched(): void {}

    setDisabledState(isDisabled: boolean): void {
        if (isDisabled) {
            this.selectControl.disable();
        } else {
            this.selectControl.enable();
        }
    }
}

多选.component.html

<mat-form-field>
    <mat-label>{{ labelText }}</mat-label>
    <mat-select [formControl]="selectControl" disableOptionCentering multiple>
        <mat-option *ngIf="withFilter">
            <ngx-mat-select-search [formControl]="searchControl"></ngx-mat-select-search>
        </mat-option>
        <div *ngIf="withSelectButtons" class="select-buttons">
            <button mat-button (click)="selectAll()">Select all</button>
            <button mat-button (click)="deselectAll()">Deselect all</button>
        </div>
        <mat-option *ngFor="let option of filteredOptions | async" [value]="option.value">{{ option.name }}</mat-option>
    </mat-select>
</mat-form-field>

标签: angulartypescriptangular-materialangular-reactive-forms

解决方案


推荐阅读