使用信号保存 ControlValueAccessor 组件的状态需要“{allowSignalWrites: true }”

问题描述 投票:0回答:1

我有这个组件实现

ControlValueAccessor
并将内部状态保持在信号中
value
:

export class InputComponent implements ControlValueAccessor {
    
    // internal state
    value = signal('');

    // implement writeValue()
    writeValue(value: string): void { this.value.set(value); } // <-- writeValue is updating the signal
    
    // other methods
    
}

InputComponent
用于这样的模板中:

    <!-- ClientComponent template -->
    <app-input [formControl]="fcUrl"/>

其中

fcUrl
FormControl
:

export class ClientComponent {

    url = input.required<string>();
    fcUrl = new FormControl<string>('');

    constructor() {

        effect(() => {

            // update the FormControl when new value for 'url' arrives:
            this.fcUrl.setValue(this.url());

        }, { allowSignalWrites: true });

    }

}

这里,

{ allowSignalWrites: true }
是必要的,因为
writeValue()
InputComponent
正在设置信号的值。否则会抛出运行时错误:

NG0600: Writing to signals is not allowed in a `computed` or an `effect` by default. Use `allowSignalWrites` in the `CreateEffectOptions` to enable this inside effects.

在这种情况下,使用

FormControl
而不使用
effect()
标志来更新
allowSignalWrites
的正确方法是什么(而不是用
effect()
替换
rxjs
,例如
toObservable(this.url).pipe(...)
)?

angular angular-signals angular-controlvalueaccessor
1个回答
-1
投票

如果您按照以下步骤实现

ControlValueAccessor
,则无需传递 formControl 作为输入,然后只需使用
[formControl]
formControlName
即可进行绑定。

然后您可以简单地使用

patchValue
根据更新的
url
信号来更新 formControl。

...
export class Child {
  signUpForm = new FormGroup({
    name: new FormControl(''),
  });
  url = input.required<string>();

    constructor() {
      effect(
        () => {
          // update the FormControl when new value for 'url' arrives:
          this.signUpForm.patchValue({ name: this.url() });
      });
    }
}

控制值访问器:

@Component({
  selector: 'app-input',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule, NgClass],
  template: `
  <input
      [type]="type()"
      #val
      (focus)="markAsTouched()"
      (input)="valueChanged(val.value)"
      [disabled]="disabled"
      [value]="_value"
    />
    <label [for]="label()">
      {{ label() }}
    </label>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: InputComponent,
    },
  ],
})
export class InputComponent implements ControlValueAccessor {
  public readonly label = input.required<InputProps['label']>();
  public readonly checkValidation = input<InputProps['checkValidation']>(false);
  public readonly placeholder = input<InputProps['placeholder']>();

  public type = input<InputProps['type']>('text');
  onChange = (value: any) => {};
  _value!: any;
  onTouched = () => {};

  touched = false;

  disabled = false;

  writeValue(value: number) {
    this._value = value;
  }

  valueChanged(val: string) {
    this.onChange(val);
  }

  registerOnChange(onChange: any) {
    this.onChange = onChange;
  }

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

  markAsTouched() {
    if (!this.touched) {
      this.onTouched();
      this.touched = true;
    }
  }

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

完整代码:

import { Component, effect, input } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import {
  ReactiveFormsModule,
  FormGroup,
  FormsModule,
  FormControl,
  ControlValueAccessor,
  NG_VALUE_ACCESSOR,
  NgControl,
} from '@angular/forms';
import { CommonModule, NgClass } from '@angular/common';

type InputProps = {
  formGroup: FormGroup;
  type: 'text' | 'password' | 'number';
  value: string;
  label: string;
  formControlName: string;
  placeholder: string;
  checkValidation: boolean;
};

@Component({
  selector: 'app-input',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule, NgClass],
  template: `
  <input
      [type]="type()"
      #val
      (focus)="markAsTouched()"
      (input)="valueChanged(val.value)"
      [disabled]="disabled"
      [value]="_value"
    />
    <label [for]="label()">
      {{ label() }}
    </label>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      multi: true,
      useExisting: InputComponent,
    },
  ],
})
export class InputComponent implements ControlValueAccessor {
  public readonly label = input.required<InputProps['label']>();
  public readonly checkValidation = input<InputProps['checkValidation']>(false);
  public readonly placeholder = input<InputProps['placeholder']>();

  public type = input<InputProps['type']>('text');
  onChange = (value: any) => {};
  _value!: any;
  onTouched = () => {};

  touched = false;

  disabled = false;

  writeValue(value: number) {
    this._value = value;
  }

  valueChanged(val: string) {
    this.onChange(val);
  }

  registerOnChange(onChange: any) {
    this.onChange = onChange;
  }

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

  markAsTouched() {
    if (!this.touched) {
      this.onTouched();
      this.touched = true;
    }
  }

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

@Component({
  selector: 'app-child',
  standalone: true,
  imports: [ReactiveFormsModule, InputComponent, CommonModule],
  template: `
    <form [formGroup]="signUpForm">
      <app-input 
        label="Name"
        formControlName="name" 
      ></app-input>
    </form>

    {{signUpForm.value | json}}
  `,
})
export class Child {
  signUpForm = new FormGroup({
    name: new FormControl(''),
  });
  url = input.required<string>();

  constructor() {
    effect(
      () => {
        // update the FormControl when new value for 'url' arrives:
        this.signUpForm.patchValue({ name: this.url() });
      },
      { allowSignalWrites: true }
    );
  }
}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [Child],
  template: `
  <app-child [url]="url"/>
  `,
})
export class App {
  url = Math.random().toString();

  ngOnInit() {
    setInterval(() => {
      this.url = Math.random().toString();
    }, 2000);
  }
}

bootstrapApplication(App);

Stackblitz 演示

© www.soinside.com 2019 - 2024. All rights reserved.