I'm encountering a "TypeError: Type 'any' must have a Symbol.iterator method that returns an iterator" error in my code. I'm trying to create a feature where users can input subtasks into an input field and confirm them by pressing Enter. These subtasks should then be displayed in a bulleted list format on the screen.
Note: I've commented out the and
Here's a simplified version of my code:
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
(keydown.enter)="addSubtask()"
(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-->
<!--
<ul>
@for (subtask of subtasksFormControl.value; track subtask.id){
<li>
{{ subtask }}
<button mat-icon-button (click)="editSubtask(subtask)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button (click)="deleteSubtask(subtask)">
<mat-icon>delete</mat-icon>
</button>
</li>
}
</ul>-->
</mat-dialog-content>
<mat-dialog-actions>
<button (click)="onSubmit()" mat-raised-button color="primary" type="submit">
Create Task
</button>
</mat-dialog-actions>
task-form.component.ts
export class TaskFormComponent {
protected readonly Object = Object;
taskForm!: FormGroup;
fromPopup = false;
constructor(
private fb: FormBuilder,
@Optional() private dialogRef: MatDialogRef<TaskFormComponent>,
@Optional()
@Inject(MAT_DIALOG_DATA)
public data: { fromPopup: boolean; task: Task }
) {}
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,
subTasks: this.data.task.subtasks,
});
}
}
public get subtasksFormControl() {
return this.taskForm.get('subtasks') as FormControl;
}
public addSubtask() {
this.taskForm.value.subtasks;
console.log(this.taskForm.value.subtasks);
}
public clearSubtask() {
this.taskForm.patchValue({ subtasks: '' });
}
}
You have to keep the subtasks as an array and the control name subtask
to contain only the description, then the complexity goes down and you build all your logic on top of this and leave the form control to only edit the description.
import { Component } from '@angular/core';
import { CommonModule, JsonPipe } from '@angular/common';
import { Inject, Optional } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckbox } from '@angular/material/checkbox';
import {
MatError,
MatFormField,
MatFormFieldModule,
MatLabel,
} from '@angular/material/form-field';
import {
FormArray,
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
} from '@angular/forms';
import { MatDatepickerModule } from '@angular/material/datepicker';
import { MatInputModule } from '@angular/material/input';
import { MatOption } from '@angular/material/core';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { TitleCasePipe } from '@angular/common';
import { MatSelect } from '@angular/material/select';
import { MatIconModule } from '@angular/material/icon';
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
import {
MAT_DIALOG_DATA,
MatDialogModule,
MatDialogRef,
} from '@angular/material/dialog';
import { Task } from '../../app/task';
import { Subtask } from '../subtask';
@Component({
selector: 'app-task-form',
standalone: true,
imports: [
CommonModule,
MatButtonModule,
MatCheckbox,
MatError,
MatFormField,
MatLabel,
ReactiveFormsModule,
MatDatepickerModule,
MatInputModule,
MatFormFieldModule,
MatButtonToggleModule,
TitleCasePipe,
MatSelect,
MatOption,
MatRadioGroup,
MatRadioButton,
MatDialogModule,
MatIconModule,
JsonPipe,
],
templateUrl: './task-form.component.html',
styleUrl: './task-form.component.css',
})
export class TaskFormComponent {
protected readonly Object = Object;
taskForm!: FormGroup;
fromPopup = false;
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(''),
subtask: new FormControl(''),
});
if (this.data?.task) {
this.taskForm.patchValue({
id: this.data.task?.id,
title: this.data.task.title,
});
if (this.data.task.subtasks.length) {
this.data.task.subtasks.forEach((subTask: Subtask) => {
this.subtasksFormArray.push(new FormControl(subTask.description));
});
}
}
}
public get subtasksFormArray() {
return this.taskForm.get('subtasks') as FormArray;
}
public get subtasksFormArrayControls() {
return (this.taskForm.get('subtasks') as FormArray)
.controls as FormControl[];
}
public addSubtask() {
const ctrl = this.taskForm.get('subtask');
this.data.task.subtasks.push({
id: Math.random(),
description: this.taskForm.value.subtask,
isDone: false,
taskId: this.taskForm.value.id,
});
}
public clearSubtask() {
this.taskForm.patchValue({ subtask: '' });
}
public editSubtask(subtask: Subtask) {}
public deleteSubtask(subtask: Subtask) {}
public onSubmit() {}
public onReset() {}
}