首页 > 解决方案 > 在 Angular Material 自定义字段组件中禁用不起作用

问题描述

我有一个使用 Angular Material 创建的自定义滑动切换组件。我遵循了本指南:https ://material.angular.io/guide/creating-a-custom-form-field-control

一切似乎都工作正常,除非我像这样动态禁用自定义组件:

<custom-slide-toggle
  [toggleClass]="'enable_user'"
  [value]="userFormGroup.get('activeUser').value"
  formControlName="activeUser"
  [toggleText]="'enable user'"
  (selectionChange)="statusChange()"
  [isChecked]="userFormGroup.get('activeUser').value"
  [required]="false"
  [disabled]="true"
></custom-slide-toggle>

该组件已禁用,但我收到控制台警告 It looks like you're using the disabled attribute with a reactive form directive. ...

为了解决它,我尝试像这样设置禁用推荐的方式:activeUser: new FormControl([{value:false, disabled: true}])在父组件中,但自定义组件没有被禁用。

我也尝试了同样的事情,但在自定义组件本身中,但对禁用或禁用该字段没有任何影响。

更新:我尝试按照@DKidwell 的建议将 formGroup 绑定添加到我的自定义组件,但我仍然收到相同的警告It looks like you're using the disabled attribute...。我使用 FormBuilder 添加了 formGroup 以更紧密地匹配 Angular Material 示例。

更新 2:我找到的解决方案是创建一个自定义指令,并根据@DKidwell 的回答添加 FormGroup 绑定。我创建的自定义指令基于这篇文章:https ://netbasal.com/disabling-form-controls-when-working-with-reactive-forms-in-angular-549dd7b42110

我像这样实现了自定义指令并删除了 [disabled] 装饰器:

<custom-slide-toggle
  [toggleClass]="'enable_user'"
  [value]="userFormGroup.get('activeUser').value"
  formControlName="activeUser"
  [toggleText]="'enable user'"
  (selectionChange)="statusChange()"
  [isChecked]="userFormGroup.get('activeUser').value"
  [required]="false"
  [disableControl]="isEditable"
></custom-slide-toggle>

这是我的自定义组件打字稿:

@Component({
  selector: 'custom-slide-toggle',
  templateUrl: './custom-slide-toggle.component.html',
  styleUrls: ['./custom-slide-toggle.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR, 
      multi: true, 
      useExisting: forwardRef(() => CustomSlideToggleComponent)
    },
    {
      provide: MatFormFieldControl,
     useExisting: CustomSlideToggleComponent
    }
  ],
  host: {
    '[id]': 'id'
  },
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CustomSlideToggleComponent implements OnInit, OnDestroy, DoCheck, ControlValueAccessor, MatFormFieldControl<boolean> {
  private static nextId = 0;
  private _placeholder: string;
  private _disabled = false;
  private _required = false;
  private _readlonly = false;

  public stateChanges = new Subject<void>();
  public errorState = false;
  public focused = false;
  public ngControl: NgControl;
  public toggleFormGroup: FormGroup;
  
  @HostBinding() public id = `custom-slide-toggle-${CustomSlideToggleComponent.nextId++}`;
  @HostBinding('class.floating')
  get shouldLabelFloat() {
    return this.focused || !this.empty;
  }
  @HostBinding('attr.aria-describedby') describedBy = '';
  setDescribedByIds(ids: string[]) {
    this.describedBy = ids.join(' ');
  }

  @Input() public toolTip: string = '';
  @Input() public isChecked: boolean;
  @Input() public toggleText: string = '';
  @Input() public tabNumber: number = null;
  @Input() public toggleId: string = '';
  @Input() public toggleClass: string;
  @Input()
    public get disabled(): boolean {
      return this._disabled;
    }
    public set disabled(value: boolean) {
      console.log('set disabled trigged');
      this._disabled = coerceBooleanProperty(value);
      this._disabled ? this.toggleFormGroup.disable() : this.toggleFormGroup.enable();
      this.stateChanges.next();
    }
  @Input()
    public get required() {
      return this._required;
    }
    public set required(req: boolean) {
      this._required = coerceBooleanProperty(req);
      this.stateChanges.next();
    }
  @Input()
    public get readonly(): boolean {
      return this._readlonly;
    }
    public set readonly(value: boolean) {
      this._readlonly = coerceBooleanProperty(value);
      this._readlonly ? 
      this.toggleFormGroup.get('toggleFormControl').disable() : 
      this.toggleFormGroup.get('toggleFormControl').enable();
      this.stateChanges.next();
    }
  @Input()
    public get value(): boolean {
    let n = this.toggleFormGroup.value;
    if (n.toggleFormControl !== null){
      return n.toggleFormControl.value;
    }
     return null;
    }
    public set value(val: boolean) {
      this.toggleFormGroup.setValue({toggleFormControl: val});
      this.stateChanges.next();
      this.onTouched();
    }      
    @Input()
    public get placeholder(): string {
      return this._placeholder;
    }  
    public set placeholder(value: string) {
      this._placeholder = value;
      this.stateChanges.next();
    }
  @Output() selectionChange: EventEmitter<boolean> = new EventEmitter<boolean>();
  
  public constructor(private injector: Injector, fb: FormBuilder) {
      this.toggleFormGroup = fb.group({
         'toggleFormControl': ''
      });
 }

  ngOnInit(): void {
    this.ngControl = this.injector.get(NgControl);
    if (this.ngControl != null) { this.ngControl.valueAccessor = this; }
  }
  ngOnDestroy(): void {
    this.stateChanges.complete();
  }
  ngDoCheck(): void {
    if(this.ngControl) {
       this.errorState = this.ngControl.invalid && this.ngControl.touched;
      this.stateChanges.next();
    }
 }

  public toggleClick($event: MatSlideToggleChange) {
    this.onChange($event.checked);
    this.selectionChange.emit($event.checked);
  }

  public onChanged = (val: boolean) => {};
  public onTouched = () => {};

  writeValue(value: any) {
    console.log('writeValue triggered, incoming value is: ' + value);
    if (value !== this.inputControl.value) {
      this.inputControl.setValue(value);
      this.onChanged(value);
      this.stateChanges.next();
    }
  }

  get empty() {
    if (this.inputControl?.pristine || this.inputControl?.untouched) return true;
    else return false;
  }

  onBlur() {
    this.onTouched();
  }

  onChange(val: boolean) {
    this.writeValue(val);
  }
  
  public registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  public registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  public setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }
}

这是我的自定义组件的 html:

<mat-slide-toggle
[formControl]="inputControl"
[id]="toggleId"
[class]="toggleClass"
color="primary"
labelPosition="after"
[checked]="isChecked"
[disabled]="disabled"
[required]="required"
(change)="toggleClick($event)"
[tabIndex]="tabNumber"
[matTooltip]="toolTip"
>{{toggleText}}</mat-slide-toggle>

如何在不收到警告的情况下动态禁用我的自定义组件?

标签: javascriptangularangular-material

解决方案


您需要将 formGroup 绑定添加到您的自定义组件,

 <div [formGroup]="yourFormGroup">        
    <mat-slide-toggle ...>
      {{toggleText}}
    </mat-slide-toggle>
  </div>

您还需要在组件中定义该 formGroup,

FormGroup yourFormGroup = new FormGroup({
  inputControl: new FormControl()
});

一旦设置正确,您就不需要绑定到自定义控件模板中的 [disabled] 属性。


推荐阅读