网站搜索

angular - 子任务在未保存的情况下被更新,并且重新打开任务对话框时显示错误的图标


我在应用程序中使用子任务编辑功能时遇到了两个问题:

  1. 当我点击“编辑”按钮来编辑一个子任务并进行更改时,即使我没有点击“保存”按钮,子任务也会被更新。相反,当我点击“创建/更新”按钮时,子任务的更改会自动保存。这种行为是意料之外的,因为我并没有明确保存子任务。

  2. 在对子任务进行修改但未保存,然后更新任务后,再次打开任务对话框时,会显示保存图标(软盘)而不是编辑图标(铅笔)。正确的行为应该是在任务没有任何未保存更改时重新打开时显示编辑图标。

以下是重现这些问题的详细步骤说明:

  1. 点击子任务的“编辑”按钮。
  2. 对子任务进行更改而不点击“保存”。
  3. 点击“创建/更新”按钮。

  4. 请注意,即使“保存”图标未被点击,子任务也会随更改而更新。

关于第二个问题:

  1. 对子任务进行更改 但不保存。
  2. 更新任务而不保存子任务
  3. 重新打开任务对话框。
  4. 注意磁盘图标被显示出来,而不是铅笔图标。

我将非常感激任何关于如何解决这些问题的建议。

Stackblitz 演示

main.ts

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [TaskFormComponent, CommonModule],
  template: `
  <!-- <app-task-form></app-task-form> --> 
  <br><br><br>
  <button (click)="updateTask()">Edit</button>

  <div>Updated Subtasks:</div>
  <ul>
    <li *ngFor="let subtask of this.task.subtasks">
      {{ subtask.description }}
    </li>
  </ul>

  `,
})
export class App {
  name = 'Angular';
  task: Task = {
    id: 1,
    title: 'Task 1',
    subtasks: [
      {
        id: 1,
        taskId: 1,
        description: 'Subtask 1',
        isDone: false,
        isEditable: false,
      },
      {
        id: 2,
        taskId: 1,
        description: 'Subtask 2',
        isDone: false,
        isEditable: false,
      },
    ],
  };
  constructor(private dialog: MatDialog) {}
  public updateTask() {
    this.dialog
      .open(TaskFormComponent, {
        data: { fromPopup: true, task: this.task },
      })
      .afterClosed()
      .pipe(filter((task) => task))
      .subscribe((task) => {});
  }
}

bootstrapApplication(App, {
  providers: [provideAnimations()],
}).catch((err) => console.error(err));

task-form.component.ts

export class TaskFormComponent {
  protected readonly Object = Object;
  taskForm!: FormGroup;
  fromPopup = false;
  subtasks: Subtask[] = [];
  constructor(
    private fb: FormBuilder,
    @Optional() private dialogRef: MatDialogRef<TaskFormComponent>,
    @Optional()
    @Inject(MAT_DIALOG_DATA)
    public data: { fromPopup: boolean; task: Task }
  ) {
    if (!this.data) {
      this.data = {
        fromPopup: true,
        task: {
          id: 1,
          title: '',
          subtasks: [],
        },
      };
    }
  }

  ngOnInit() {
    this.fromPopup = !!this.data?.fromPopup;

    this.taskForm = this.fb.group({
      id: this.data?.task?.id,
      title: new FormControl(''),
      subtasks: new FormControl(''),
    });

    if (this.data?.task) {
      this.taskForm.patchValue({
        id: this.data.task?.id,
        title: this.data.task.title,
      });
      this.subtasks = this.data.task.subtasks;
    }
  }

  public get subtasksFormControl() {
    return this.taskForm.get('subtasks') as FormControl;
  }

  public addSubtask() {
    const subtask = this.taskForm.get('subtasks')?.value;
    if (subtask.trim()) {
      this.subtasks.push({
        id: undefined,
        taskId: 1,
        description: subtask,
        isDone: false,
        isEditable: false,
      } as Subtask);

      this.subtasksFormControl.setValue(this.subtasks);
    }
    this.clearSubtask();
  }

  public clearSubtask() {
    this.taskForm.patchValue({ subtask: '' });
  }

  public editSubtask(index: number) {
    this.subtasks[index].isEditable = true;
  }

  public saveSubtask(index: number, subtask: Subtask) {
    this.subtasks[index].isEditable = false;
    this.subtasks[index] = subtask;
  }

  public deleteSubtask(index: number) {
    this.subtasks.splice(index, 1);
  }

  public onSubmit() {
    if (this.data?.task) {
      const taskRawValue = {
        ...this.taskForm.getRawValue(),
        subtasks: this.subtasks,
      };
      this.onUpdateTask();
    } else {
    }
    this.onReset();
  }

  public onUpdateTask() {
    const taskRawValue = {
      ...this.taskForm.getRawValue(),
      subtasks: this.subtasks,
    };
    if (this.fromPopup) {
      this.dialogRef.close(taskRawValue);
    } else {
    }
  }

  public onReset() {
    this.taskForm.reset();
    this.subtasks = [];
  }
}

task-form.component.html

<div mat-dialog-title>Create Task</div>

<mat-dialog-content>
  <form [formGroup]="taskForm" (ngSubmit)="onSubmit()">
    <mat-form-field>
      <mat-label>Title</mat-label>
      <input
        matInput
        formControlName="title"
        type="text"
        placeholder="Enter a title"
      />
    </mat-form-field>

    <mat-form-field>
      <mat-label>Subtasks</mat-label>
      <input
        matInput
        formControlName="subtasks"
        type="text"
        placeholder="Enter a subtask"
      />
      <button mat-icon-button matSuffix (click)="addSubtask()">
        <mat-icon>add</mat-icon>
      </button>
      <div class="vertical-divider" matSuffix></div>
      <button mat-icon-button matSuffix (click)="clearSubtask()">
        <mat-icon>close</mat-icon>
      </button>
      <mat-hint>Press enter to add a subtask</mat-hint>
    </mat-form-field>
  </form>
  <!-- SHOW HERE ALL SUBTASKS-->

  <div class="subtasks-container">
    @for (subtask of subtasks; track subtask){
    <div class="subtask-text">
      <span>&#8226;</span>
      <input
        [(ngModel)]="subtask.description"
        type="text"
        name="subtask"
        [readonly]="!subtask.isEditable"
        [ngClass]="{
          editable: subtask.isEditable,
          'read-only': !subtask.isEditable
        }"
      />
      <div class="subtasks-actions-container">
        @if(!subtask.isEditable) {
        <mat-icon color="primary" (click)="editSubtask($index)">edit</mat-icon>
        } @if(subtask.isEditable) {
        <mat-icon color="primary" (click)="saveSubtask($index, subtask)"
          >save</mat-icon
        >
        }
        <mat-icon color="warn" (click)="deleteSubtask($index)">delete</mat-icon>
      </div>
    </div>
    }
  </div>
</mat-dialog-content>

<mat-dialog-actions>
  <button (click)="onSubmit()" mat-raised-button color="primary" type="submit">
    Create/Update
  </button>
</mat-dialog-actions>

答案#1

从这里:

this.dialog
  .open(TaskFormComponent, {
    data: { fromPopup: true, task: this.task },
  })

你正在将 this.task 的引用共享到 MatDialogData 中,这导致在 TaskFormComponent 中所做的任何更改都会反映到你的父组件 (App)。

你应该使用深拷贝来解决它。

前提条件:安装 loadash

import * as _ from 'lodash';

public updateTask() {
  this.dialog
    .open(TaskFormComponent, {
      data: { fromPopup: true, task: _.cloneDeep(this.task) },
    })
    .afterClosed()
    .pipe(filter((task) => task))
    .subscribe((task) => (this.task = task));
}

另外,不要忘记将更改后的值重新赋给 task。


已更新

您在保存最新(已更改)数据时,没有检查 isEditable 状态。在保存和模态框关闭之前,您应该检查 isEditable 状态,并将那些未保存的子任务(按索引/位置)恢复为原始值。此外,您需要对 subtasks 进行深拷贝,以保留来自 MAT_DIALOG_DATA 的原始值。

import * as _ from 'lodash';

ngOnInit() {
  ...

  if (this.data?.task) {
    this.taskForm.patchValue({
      id: this.data.task?.id,
      title: this.data.task.title,
    });
    this.subtasks = _.cloneDeep(this.data.task.subtasks);
  }
}

public deleteSubtask(index: number) {
  this.subtasks.splice(index, 1);
  this.data.task.subtasks.splice(index, 1);
}

public onSubmit() {
  if (this.data?.task) {
    const taskRawValue = {
      ...this.taskForm.getRawValue(),
      subtasks: this.subtasks.map((x, i) =>
        x.isEditable ? (this.data.task.subtasks.at(i) ?? x) : x
      ),
    };

    this.onUpdateTask();
  } else {
  }
  this.onReset();
}

public onUpdateTask() {
  const taskRawValue = {
    ...this.taskForm.getRawValue(),
    subtasks: this.subtasks.map((x, i) =>
      x.isEditable ? (this.data.task.subtasks.at(i) ?? x) : x
    ),
  };
  if (this.fromPopup) {
    this.dialogRef.close(taskRawValue);
  } else {
  }
}

问题与关注

问题 1:我注意到你的控件名称有一个拼写错误,导致在添加子任务后表单控件的值未清除。请按如下方式更正:

public clearSubtask() {
  this.taskForm.patchValue({ subtasks: '' });
}

问题 2:<button> 元素的默认行为是 type="submit"。作为表单中 subtasks 表单控件的添加和关闭按钮,这将提交表单并关闭模态框。为了避免这种情况,你必须在 <button> 元素中添加 type="button" 属性。

<button mat-icon-button matSuffix (click)="addSubtask()" type="button">
  <mat-icon>add</mat-icon>
</button>

<button mat-icon-button matSuffix (click)="clearSubtask()" type="button">
  <mat-icon>close</mat-icon>
</button>

StackBlitz 演示