Я столкнулся с двумя проблемами с функцией редактирования подзадач в моем приложении:
Когда я нажимаю кнопку «Изменить», чтобы отредактировать подзадачу и внести изменения, подзадача обновляется, даже если я не нажимаю кнопку «Сохранить». Вместо этого, когда я нажимаю кнопку «Создать/Обновить», изменения в подзадаче сохраняются автоматически. Такое поведение является неожиданным, поскольку я не сохранил подзадачу явно.
После внесения изменений в подзадачу без сохранения, затем обновление задачи. После повторного открытия диалога задачи вместо значка редактирования (карандаш) отображается значок сохранения (дискета). Правильное поведение должно отображать значок редактирования при повторном открытии задачи без каких-либо несохраненных изменений.
Вот подробное описание действий по воспроизведению этих проблем:
По второму вопросу:
Буду признателен за любые советы по решению этих проблем.
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>•</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>
Отсюда:
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? Спасибо
Я имею в виду, что это происходит в вашем решении Stackblitz.
Хммм, но мой StackBlitz действительно не может воспроизвести проблему. Вы имеете в виду эту демо ?
Да, я имел в виду вашу демо-версию. Я добавил несколько фотографий в свой первый пост, чтобы мой вопрос был более понятным.
Извините за это, теперь я понимаю вашу проблему. Я обновил свой ответ. Пожалуйста, проверьте. Спасибо.
Спасибо за решение. Но я все равно заметил ошибку. Если вы сделаете следующее: нажмите «Редактировать» -> «Создать подзадачу» -> «Удалить первую подзадачу» -> щелкните значок редактирования второй подзадачи -> «Не сохранять подзадачу» -> нажмите «Создать/Обновить». Возникает ошибка «Невозможно прочитать свойства неопределенного значения» (чтение «описания»). К сожалению, я так и не понял, почему возникает эта ошибка?
Вы добавили this.data.task.subtasks.splice(index, 1)
в метод deleteSubtask
?
Да, я добавил это. Ошибка возникает в вашем решении stackblitz. Но это происходит только в том случае, если осталась одна подзадача.
Извините, пожалуйста, проверьте последнюю демо еще раз. Я редактирую оба метода: onUpdateTask
и onSubmit
.
Спасибо за ответ, но, к сожалению, проблема все еще актуальна. Когда я редактирую подзадачу и вношу изменения, не нажимая «Сохранить», изменения автоматически сохраняются, когда я нажимаю «Создать/Обновить». Кроме того, при повторном открытии диалогового окна задачи вместо значка редактирования отображается значок сохранения. Не могли бы вы дать дальнейшие указания?