Angular: TypeError: тип «любой» должен иметь метод Symbol.iterator, который возвращает итератор

В моем коде я сталкиваюсь с ошибкой «TypeError: тип «любой» должен иметь метод Symbol.iterator, который возвращает итератор». Я пытаюсь создать функцию, позволяющую пользователям вводить подзадачи в поле ввода и подтверждать их, нажав Enter. Эти подзадачи затем должны отображаться на экране в виде маркированного списка.

Примечание. Я закомментировал и

  • элементы в html-файле, чтобы обеспечить правильную компиляцию демо-версии StackBlitz.

    Демо@StackBlitz

    Вот упрощенная версия моего кода:

    задача-форма.компонент.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>
    

    задача-form.comComponent.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.info(this.taskForm.value.subtasks);
      }
    
      public clearSubtask() {
        this.taskForm.patchValue({ subtasks: '' });
      }
    }
    
  • 🤔 А знаете ли вы, что...
    Для создания пользовательских интерфейсов в Angular используется декларативный язык шаблонов.


    1
    55
    1

    Ответ:

    Решено

    Вам нужно сохранить подзадачи в виде массива, а имя элемента управления subtask должно содержать только описание, тогда сложность снижается, и вы строите всю свою логику поверх этого и оставляете элемент управления формы, чтобы редактировать только описание.

    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() {}
    }
    

    Демо-версия Stackblitz