Подзадача обновляется без сохранения, и при повторном открытии диалогового окна задачи отображается неверный значок

Я столкнулся с двумя проблемами с функцией редактирования подзадач в моем приложении:

  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));

задача-form.comComponent.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 = [];
  }
}

задача-форма.компонент.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>
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
2
0
51
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 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 внутри формы, она отправит форму и закроет модальное окно. Чтобы избежать этого, вам нужно добавить атрибут type = "button" к элементам <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

Спасибо за ответ, но, к сожалению, проблема все еще актуальна. Когда я редактирую подзадачу и вношу изменения, не нажимая «Сохранить», изменения автоматически сохраняются, когда я нажимаю «Создать/Обновить». Кроме того, при повторном открытии диалогового окна задачи вместо значка редактирования отображается значок сохранения. Не могли бы вы дать дальнейшие указания?

coder 14.07.2024 13:10

Хм, может быть, ты поделишься своим последним кодом в StackBlitz? Спасибо

Yong Shun 14.07.2024 13:32

Я имею в виду, что это происходит в вашем решении Stackblitz.

coder 14.07.2024 13:33

Хммм, но мой StackBlitz действительно не может воспроизвести проблему. Вы имеете в виду эту демо ?

Yong Shun 14.07.2024 13:42

Да, я имел в виду вашу демо-версию. Я добавил несколько фотографий в свой первый пост, чтобы мой вопрос был более понятным.

coder 14.07.2024 14:36

Извините за это, теперь я понимаю вашу проблему. Я обновил свой ответ. Пожалуйста, проверьте. Спасибо.

Yong Shun 14.07.2024 15:07

Спасибо за решение. Но я все равно заметил ошибку. Если вы сделаете следующее: нажмите «Редактировать» -> «Создать подзадачу» -> «Удалить первую подзадачу» -> щелкните значок редактирования второй подзадачи -> «Не сохранять подзадачу» -> нажмите «Создать/Обновить». Возникает ошибка «Невозможно прочитать свойства неопределенного значения» (чтение «описания»). К сожалению, я так и не понял, почему возникает эта ошибка?

coder 15.07.2024 01:56

Вы добавили this.data.task.subtasks.splice(index, 1) в метод deleteSubtask?

Yong Shun 15.07.2024 02:13

Да, я добавил это. Ошибка возникает в вашем решении stackblitz. Но это происходит только в том случае, если осталась одна подзадача.

coder 15.07.2024 02:21

Извините, пожалуйста, проверьте последнюю демо еще раз. Я редактирую оба метода: onUpdateTask и onSubmit.

Yong Shun 15.07.2024 02:35

Другие вопросы по теме