Я столкнулся с двумя проблемами с функцией редактирования подзадач в моем приложении:
Когда я нажимаю кнопку «Изменить», чтобы отредактировать подзадачу и внести изменения, подзадача обновляется, даже если я не нажимаю кнопку «Сохранить». Вместо этого, когда я нажимаю кнопку «Создать/Обновить», изменения в подзадаче сохраняются автоматически. Такое поведение является неожиданным, поскольку я не сохранил подзадачу явно.
После внесения изменений в подзадачу без сохранения, затем обновление задачи. После повторного открытия диалога задачи вместо значка редактирования (карандаш) отображается значок сохранения (дискета). Правильное поведение должно отображать значок редактирования при повторном открытии задачи без каких-либо несохраненных изменений.
Вот подробное описание действий по воспроизведению этих проблем:
По второму вопросу:
Буду признателен за любые советы по решению этих проблем.
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>
🤔 А знаете ли вы, что...
Angular поддерживает локализацию и многоязычность приложений.
Отсюда:
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>