我有一个 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 表单的新手,这是我能做到的最好的。谢谢。
既然您提到
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
的设计相同。