【问题标题】:Angular Reactive Forms for property grid属性网格的角度反应形式
【发布时间】:2021-11-30 10:10:10
【问题描述】:

使用 Angular 12,我想验证和检测列表中的更改。

我有一个属性网格(键/值对表,列出具有可编辑值的属性,但每个值可以是不同的类型,字符串/布尔值/int/等)。

我想为该属性网格添加验证和更改检测。

应该对每个项目进行验证,并且只需要对整个列表进行更改检测(不关心更改了哪一行/项目)。

我已经构建了这样的东西:

export class InnerSetting {
  key!: string;
  displayName!: string;
  originalValue?: string;
  value?: string;
  type!: PropertyTypeEnum; //string | int | boolean | ...
  validation?: string;
  minValue?: number;
  maxValue?: number;
  isNullable!: boolean
}
constructor(private formBuilder: FormBuilder) {
    this.form = this.formBuilder.group({
      properties: new FormArray([])
    });
  }

  ngOnInit(): void {
    this.settings.forEach(item => this.formArray.push(this.formBuilder.group(
      {
        key: [item.key],
        type: [item.type],
        name: [item.displayName],
        value: [ item.value, [Validators.required, Validators.minLength(2)] ], //Depends on type and validation stuff, will be added later.
        originalValue: [ item.originalValue ]
      }
    )));

    //Not sure which of these will work, so added both for now.
    this.form.valueChanges.pipe(debounceTime(500)).subscribe(changes => {
      this.areChangesDetected = changes != null;
    });

    this.formArray.valueChanges.pipe(debounceTime(500)).subscribe(changes => {
      this.areChangesDetected = changes != null;
    });
  }

  get formArray() {
    return this.form.controls.properties as FormArray;
  }

在使用Form 之前,我只是使用InnerSetting 列表,所以请记住,我刚开始用表单替换列表。

setting 属性是一个 InnerSetting 对象。

<form [formGroup]="form" class="group-wrapper">
  <div class="d-flex item-row" *ngFor="let setting of formArray.controls; let i = index">
    <div class="item flex-fill d-flex" [ngSwitch]="setting.type" [formGroupName]="i">
      <span>{{setting.name}}</span>

      <select *ngSwitchCase="'boolean'" class="flex-grow-1" name="role" id="role-select" [(ngModel)]="setting.value">
        <option [value]="'0'">False</option>
        <option [value]="'1'">True</option>
      </select>

      <div contenteditable *ngSwitchDefault class="flex-grow-1" type="text" [id]="setting.key" [innerText]="setting.value"></div>
    </div>

    <button class="remove-relation" (click)="reset(setting)">
      <fa-icon [icon]="faUndo"></fa-icon>
    </button>
  </div>
</form>

问题

因为我需要根据设置类型(布尔值、字符串、数字等)显示不同的元素。如何从formArray.controls 访问该信息?

另外,如何使用contenteditable 绑定到非标准输入控件,例如我的div


编辑

我注意到formArray.controlsFormGroup 的数组,我可以访问this.formArray.controls[0].controls['name'].value 中的值。

现在的问题是根据类型将该控件设置为字段(选择或 div 或输入)。

【问题讨论】:

  • 这里*ngFor="let setting of formArray.controls; 你的设置是FormGroup。如果你想从setting 访问displayName,你可以做setting.get('displayName').value。如果您可以在 stackblitz.com 上提供它,我可以提供更适当的帮助
  • 要使用contenteditable将formControl绑定到div,您需要通过实现https://angular.io/api/forms/ControlValueAccessor接口来创建自定义formControl
  • 谢谢,它通过使用 setting.get('name').value 访问器工作。现在的问题是将控件绑定到输入和特殊输入。我会看看上面的链接。
  • 我尝试使用[formControl]="setting.get('value')",但它抱怨AbstractControl | null 不能分配给FormControl
  • 问题是当你从 FormGroup 获取时,你会得到 AbstractControl 而不是 FormControl,因为你的 setting FG 不知道它的 FormControl。它只是返回我们无法分配给[formControl] 的 AbstractControl。你能在stackblitz上分享吗,

标签: angular angular-reactive-forms angular-controlvalueaccessor


【解决方案1】:

您的模板将如下所示

<form [formGroup]="form" class="group-wrapper">
  <div
    formArrayName="properties"
    class="d-flex item-row"
    *ngFor="let setting of settingFG; let i = index"
  >
    <div
      class="item flex-fill d-flex"
      [ngSwitch]="setting.get('type').value"
      [formGroupName]="i"
    >
      <!-- <span>{{ setting.get('name').value }}</span> -->

      <select
        *ngSwitchCase="'boolean'"
        class="flex-grow-1"
        name="role"
        id="role-select"
        formControlName="value"
      >
        ...
      </select>
      // custom div form control
      <div-control *ngSwitchDefault formControlName="value"></div-control>

    </div>

    <button class="remove-relation" (click)="reset(setting)">X</button>
  </div>
</form>

获取FormGroup数组的辅助方法

 get settingFG(): FormGroup[] {
    return this.formArray.controls as FormGroup[];
  }

要将FormControl添加到div我们需要实现ControlValueAccessor

export const DIV_VALUE_ACCESSOR: any = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => DivController),
  multi: true,
};

@Component({
  selector: 'div-control',
  providers: [DIV_VALUE_ACCESSOR],
  template: `<div contenteditable #div (input)="changeText(div.innerText)" [textContent]="value"><div>`,
})
export class DivController implements ControlValueAccessor {
  value = '';
  disabled = false;
  private onTouched!: Function;
  private onChanged!: Function;

  changeText(text: string) {
    this.onTouched(); // <-- mark as touched
    this.value = text;
    this.onChanged(text); // <-- call function to let know of a change
  }
  writeValue(value: string): void {
    this.value = value ?? '';
  }
  registerOnChange(fn: any): void {
    this.onChanged = fn; // <-- save the function
  }
  registerOnTouched(fn: any): void {
    this.onTouched = fn; // <-- save the function
  }
  setDisabledState(isDisabled: boolean) {
    this.disabled = isDisabled;
  }
}

*不要忘记在模块中添加声明数组。

Full Demo

【讨论】:

  • 效果很好,谢谢!
  • 我只需要修复&lt;div contenteditable&gt;,因为在每次文本更改中,文本插入符号位置都会丢失(即移动到位置0)。
  • 我没有看到这个问题
  • 哦,它只发生在按 Enter 键时。我相信插入符号已移至 changeText() 中的位置 0。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-27
  • 2020-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-26
相关资源
最近更新 更多