Angular FormGroup 值未使用 patchValue() 在 DOM 中更新

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

我有一个 FiltersAccordion 组件来管理表过滤。由于用户必须能够将过滤器分组为 AND 和 OR 组,因此该组件非常复杂。我当前的文件结构是:

  • FiltersAccordion
    :管理整体“保存过滤器”逻辑。
  • FiltersBlock
    :显示按 AND 分组的过滤器列表。
  • Filter
    :每个过滤器都包含一个或多个输入/选择/复选框。

目标是每个

Filters
都可以从一个
FilterBlock
拖放到另一个
。现在的主要问题是添加到放置目标块的“新过滤器”保留了所有正确的数据,但是没有在 DOM 中的表单值中反映它

这是

FilterBlock
组件的代码。这里
filterGroups
是一个 FormGroup 数组,它在
<ul>
中循环:

// filters-block.component.html

<ul
  class="filter__group__list"
  (dragover)="handleDragOver($event)"
  (drop)="handleDrop($event)"
>
  <li
    class="filter__group__item"
    draggable="true"
    *ngFor="let group of filterGroups; let idx = index"
  >
    <app-filter
      (change)="handleUpdateFilter($event, idx)"
      [columnsService]="columnsService"
      [data]="data"
      [defaultValues]="group"
      (dragStart)="handleDragStart($event, idx)"
      (dragEnd)="handleDragEnd($event, idx)"
      (removeFilter)="handleRemoveFilter(idx)"
    ></app-filter>
  </li>
</ul>
// filters-block.component.ts

// * Filters grouped by AND operator
export class FiltersBlockComponent implements OnInit {
  @Output() dragStart = new EventEmitter<{ event: DragEvent; item: number }>();
  @Output() dragEnd = new EventEmitter<{ event: DragEvent; item: number }>();
  @Output() removeBlock = new EventEmitter<void>();

  public filterGroups: FormGroup<FilterGroupTO>[];

  constructor() {}

  ngOnInit() {
    this.filterGroups = [
      new FormGroup<FilterFormGroupTO>({
        checkBox: new FormControl<boolean | null>(false),
        field: new FormControl<FilterableColumnsTO | null>(null),
        relation: new FormControl<string | null>(''),
        value: new FormControl<FilterableColumnsTO | null>(null),
      }),
    ];
  }

  handleUpdateFilter(filter: FilterFormGroupTO, index: number) {
    this.filterGroups[index].patchValue(filter as any);
  }

  handleRemoveFilter(index: number) {
    this.filterGroups.splice(index, 1);

    if (this.filterGroups.length === 0) {
      this.removeBlock.emit();
    }
  }

  handleDragStart(event: DragEvent, index: number) {
    this.dragStart.emit({ event, item: index });
  }

  handleDragEnd(event: DragEvent, index: number) {
    this.dragEnd.emit({ event, item: index });
  }

  handleDragOver(event: DragEvent) {
    event.preventDefault();
    if (event.dataTransfer) event.dataTransfer.dropEffect = 'move';
  }

  handleDrop(event: DragEvent) {
    event.preventDefault();
    if (event.dataTransfer) {
      const filterData = event.dataTransfer.getData('filter');
      const properFilter = JSON.parse(filterData);

      const newGroup = new FormGroup<FilterFormGroupTO>({
        checkBox: new FormControl<boolean | null>(properFilter.checkBox),
        field: new FormControl<FilterableColumnsTO | null>(properFilter.field),
        relation: new FormControl<string | null>(properFilter.relation),
        value: new FormControl<FilterableColumnsTO | null>(properFilter.value),
      });
      this.filterGroups.push(newGroup);
    }
  }
}

过滤器组件包含所有表单逻辑和输入:

<div
  [formGroup]="filterFormGroup"
  class="filter__group__item"
  [draggable]="enableDrag"
  (dragstart)="handleDragStart($event)"
  (dragend)="handleDragEnd($event)"
>
      <select
        formControlName="field"
      >
        <option
          *ngFor="let option of filtersColumns"
          [displayValue]="option.fieldName"
          [readValue]="option"
          >{{ option.fieldName }}</option
        >
      </select>
      <select
        formControlName="relation"
      >
        <option
          *ngFor="
            let option of filterFormGroup.controls['field'].value?.operations;
            let i = index
          "
          [readValue]="option"
          >{{
            "DIALOGS.FILTERS.RELATION." + option | translate
          }}</option
        >
      </select>
      <select
        formControlName="value"
      >
        <option
          *ngFor="let option of fieldDistincValues; let i = index"
          [displayValue]="option"
          [readValue]="option"
          >{{ option }}</option
        >
      </select>
      <input
        formControlName="value"
        type="text"
      ></input>
    </div>
    <app-toggle
      formControlName="checkBox"
      [label]="'DIALOGS.FILTERS.CHECKBOX' | translate"
    ></app-toggle>
</div>
// filter.component.ts

export interface FilterFormGroupTO {
  field: FormControl<FilterableColumnsTO | null>;
  relation: FormControl<string | null>;
  value: FormControl<FilterableColumnsTO | null>;
  checkBox: FormControl<boolean | null>;
}

export class FilterComponent implements OnInit {
  @Output() change = new EventEmitter<FilterFormGroupTO>();
  @Output() dragStart = new EventEmitter<DragEvent>();
  @Output() dragEnd = new EventEmitter<DragEvent>();
  @Output() removeFilter = new EventEmitter<void>();
  @Input() defaultValues: FormGroup<FilterFormGroupTO>;

  // filters
  private selectedFilters: FilterTO[] = [];
  public availableFilters: Columns[] = [];

  // form
  public filterFormGroup: FormGroup<FilterFormGroupTO>;

  constructor(filtersService: FiltersService) {}

  ngOnInit() {
    // Initialize form. NOT WORKING
    this.filterFormGroup = new FormGroup<FilterFormGroupTO>({
      checkBox: new FormControl<boolean | null>(
        this.defaultValues.value.checkBox as boolean | null
      ),
      field: new FormControl<FilterableColumnsTO | null>(
        this.defaultValues.value.field as FilterableColumnsTO | null
      ),
      relation: new FormControl<string | null>(
        this.defaultValues.value.relation as string | null
      ),
      value: new FormControl<FilterableColumnsTO | null>(
        this.defaultValues.value.value as FilterableColumnsTO | null
      ),
    });

    // Patch form values. NOT WORKING
    this.filterFormGroup.patchValues({
      checkBox: this.defaultValues.value.checkBox as boolean | null,
      field: this.defaultValues.value.field as FilterableColumnsTO | null,
      relation: this.defaultValues.value.relation as string | null,
      value: this.defaultValues.value.value as FilterableColumnsTO | null,
    });

    // Get available filters
    filtersService().subscribe((res) => {
      this.availableFilters = res;
    });

    // Changes in form listener
    this.filterFormGroup.valueChanges.subscribe((value) => {
      this.change.emit(value as unknown as FilterFormGroupTO);
    });
  }

  handleRemoveFilter() {
    this.removeFilter.emit();
  }

  handleDragStart(event: DragEvent) {
    const fieldValue = this.filterFormGroup.value['field'];
    const checkboxValue = Boolean(this.filterFormGroup.value['checkBox']);
    const relationValue = this.filterFormGroup.value['relation'];
    const valueValue = this.filterFormGroup.value['value'];

    const data = {
      checkBox: checkboxValue,
      field: fieldValue,
      relation: relationValue,
      value: valueValue,
    };

    if (this.enableDrag) {
      event.dataTransfer?.setData('filter', JSON.stringify(data));

      if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move';
      this.dragStart.emit(event);
    }
  }

  handleDragEnd(event: DragEvent) {
    if (this.enableDrag) {
      this.dragEnd.emit(event);

      if (event.dataTransfer?.dropEffect === 'move') {
        this.handleRemoveFilter();
      }
    }
  }
}

正如我所指出的,当我记录它时,

ngOnInit
组件的
Filter
确实携带了正确的数据。即使我
console.log(this.filterFormGroup)
我也得到了正确的值。为什么它们没有在 DOM 中渲染?

我处理这个问题的方式是错误的吗?我是 Angular 表单的新手,这是我能做到的最好的。谢谢。

javascript angular forms drag-and-drop
1个回答
0
投票

既然您提到

FilterComponent
是子组件,并且拖放过滤器时表单并未初始化,那是因为您的表单创建是在
ngOnInit
生命周期挂钩上处理的。

您必须记住,如果父组件对子组件有进一步的更改,并且您想要处理一些逻辑(在本例中,重新填充表单),则此钩子只会运行一次

 ngOnInit
不会在意。

有多种方法可以处理此问题,一种方法是使用

ngOnChanges
ChangeStrategy.onPush
,如下例所示:

@Component({
  ...,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit, OnChanges {
   @Input() userData!: User;
   protected form!: FormGroup<UserForm>();   

   ngOnInit() {
    // some other logic...
   }

   ngOnChanges() {
     this._initForm();
   }

   private _initForm(): void {
      this.form = new FormGroup<UserForm>({
         name = new FormControl<string | null>(this.userData?.name || null),
         address = new FormControl<string | null>(this.userData?.addr || null)
      })
   }
}

每次使用

new
值更新 userData 时(即使是第一次),
ngOnChanges
都会检测到它,您可以处理表单创建和初始化。

这种方法通常用于主/详细 UI 设计(至少根据我的经验),我认为与您的

FilterBlock
/
Filter
的设计相同。

演示

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