Having trouble making Angular Dynamic Nested FormArrays function properly

I'm currently working on creating an array of books with multiple authors that can be added or removed dynamically.

As I delve into learning Angular, I've encountered the necessity of having a nested array within another array in my project.

The main objective is to generate an array of books where each book will contain a corresponding array of authors. However, I keep encountering the following error message:

Error: Cannot find control with path: 'books -> 0 -> authors -> 0

Below is the TypeScript code snippet I have implemented :

import { Component, Inject, OnInit } from '@angular/core';
import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';



import {STEPPER_GLOBAL_OPTIONS} from '@angular/cdk/stepper';

/**
 * @title Stepper that displays errors in the steps
 */
@Component({
  selector: 'app-home',
  templateUrl: 'home.component.html',
  styleUrls: ['home.component.scss'],
  providers: [
    {
      provide: STEPPER_GLOBAL_OPTIONS,
      useValue: {showError: true},
    },
  ],
})
export class HomeComponent implements OnInit {

  bookForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.bookForm = this.fb.group({
      books: this.fb.array([])  // Create an empty FormArray for books
    });
  }

  ngOnInit(): void {
      this.createBook()
  }

  get books() {
    return this.bookForm.get('books') as FormArray;
  }

  addBook() {
    this.books.push(this.createBook());
  }

  removeBook(index: number) {
    this.books.removeAt(index);
  }

  createBook() {
    return this.fb.group({
      title: ['', Validators.required],
      authors: this.fb.array([this.createAuthor()])  
    });
  }
  createAuthor() {
    return this.fb.control('', Validators.required);
  }


  getAuthors(bookIndex: number) {
    return this.books.at(bookIndex).get('authors') as FormArray;
  }
  addAuthor(bookIndex: number) {
    const book = this.books.at(bookIndex) as FormGroup;
    const authors = book.get('authors') as FormArray;
    authors.push(this.createAuthor());
  }
  removeAuthor(bookIndex: number, authorIndex: number) {
    const book = this.books.at(bookIndex) as FormGroup;
    const authors = book.get('authors') as FormArray;
    authors.removeAt(authorIndex);
  }

  onSubmit() {
    if (this.bookForm.valid) {
      console.log(this.bookForm.value);
    }
  }
 
}

Please find below the HTML code segment used:

<form [formGroup]="bookForm" (ngSubmit)="onSubmit()">
    <div formArrayName="books">
      <div *ngFor="let book of books.controls; let bookIndex = index">
        <div [formGroupName]="bookIndex">
          <div>
            <label>
              Book Title:
              <input formControlName="title">
            </label>
            <button type="button" (click)="removeBook(bookIndex)">Remove Book</button>
          </div>
          <div formArrayName="authors">
            <div *ngFor="let author of getAuthors(bookIndex).controls; let authorIndex = index">
              <div [formGroupName]="authorIndex">
                <label>
                  Author:
                  <input formControlName="authorName">
                </label>
                <button type="button" (click)="removeAuthor(bookIndex, authorIndex)">Remove Author</button>
              </div>
            </div>
            <button type="button" (click)="addAuthor(bookIndex)">Add Author</button>
          </div>
        </div>
      </div>
      <button type="button" (click)="addBook()">Add Book</button>
    </div>
    <button type="submit">Submit</button>
  </form>
  

Answer №1

A FormArray can store different types of data structures such as a FormArray of FormGroup (an array of objects), a FormArray of FormControls (an array of strings, numbers, Dates, booleans..) or a FormArray of FormArrays (an array of arrays)

1.- If your authors consist of FormControls (only storing the name), you need to make changes in the .html file

    <div formArrayName="authors">
        <div *ngFor="let author of getAuthors(bookIndex).controls; let authorIndex = index">
            <label>
              Author:
              <input [formControlName]="authorIndex">
            </label>
            <button type="button" 
                    (click)="removeAuthor(bookIndex, authorIndex)">
                  Remove Author
            </button>
        </div>
        <button type="button" (click)="addAuthor(bookIndex)">Add Author</button>
      </div>

2.- If your authors are formGroups, your create author function should look like this

  createAuthor() {
    return this.fb.group({
      authorName:this.fb.control('', Validators.required)
    })
  }

NOTE: To check the value of your form, add the following code at the end of your component.html

<pre>
{{bookForm.value|json}}
</pre>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Getting the URL after an equal sign in TypeScript

Could anyone assist me with retrieving the token from the URL to be sent to the database at a later time? mysite.com/changePassword?token=45456112241121131154 Thank you for any assistance. ...

What factors does mongo consider when serializing an object?

I recently started working with BigNumbers from the bignumber.js package As I delve into Mongo, I find myself pondering how Mongo manages to serialize objects correctly, such as the BigNumbers. In my case, I have encountered a puzzling situation where two ...

Enabling the use of an extensive interface when passing a function as a parameter in React with Typescript

While working with React, I am facing a challenge with passing multiple functions as props and encountering an issue when using extended interfaces as parameter types for a function in a "standard" component. I have tried typing the component itself with g ...

Error loading ngsw-worker.js in Angular 7

Issue An app that utilizes a Service Worker is experiencing problems. The app was recently upgraded from Angular 6.1 to version 7. Upon uploading the updated files to the server, an error message is displayed: https://i.sstatic.net/B7uPf.png Error Det ...

Tips for converting JSON String data to JSON Number data

Hello everyone, I am facing an issue with converting the 'review' value from String to a numerical format in JSON. This is causing problems when trying to perform calculations, leading to incorrect results. The scenario involves saving user comm ...

How to resolve the issue of checkbox not binding the value of an object field in Angular 4?

Can anyone help me with binding the field value in the current object and switching the checkbox based on its value? This is my checkbox: <label class="checkbox-inline checbox-switch switch-success"> <input #livingRoom type="checkbox" name ...

Can a discriminated union be generated using mapped types in TypeScript?

Imagine you have an interface called X: type X = { red: number, blue: string } Can a union type Y be created using mapped types? If not, are there other ways to construct it at the type level? type Y = { kind: "red" payload: number } | ...

How can I incorporate dynamic fields into a Typescript type/interface?

In my Typescript interface, I have a predefined set of fields like this: export interface Data { date_created: string; stamp: string; } let myData: Data; But now I need to incorporate "dynamic" fields that can be determined only at runtime. This me ...

Could you confirm if this is a TypeScript function?

Recently, while delving into the vue-next source code, I stumbled upon a particular line that left me puzzled. Due to my limited experience with TypeScript, I found myself struggling to grasp its purpose. Could someone clarify if this snippet constitutes ...

Troubleshooting: Why is my custom AngularJS component not displaying the Toast?

I created a unique Custom Alert component that is triggered whenever there is an error in a query through a notification service, which is invoked by an HTTP interceptor. Here is how my custom alert component is structured : alert.component.html <div ...

The embedded Twitter widget in the Angular 2+ app is visible only upon initial page load

After implementing the built-in function from Twitter docs into ngAfterViewInit function, my app works flawlessly. However, I encountered an issue where the widget disappears when switching routes. Here is the code that only functions on the initial page ...

What is the best method for accessing a store in Next.js with Redux Toolkit?

Currently, I am working on incorporating integration testing for my application using Jest. To achieve this, I need to render components in order to interact with various queries. However, in order to render a component, it must be wrapped in a Provider to ...

Guide on navigating to a different page using a function with router link in Angular using TypeScript

Trying my hand at Angualar and Typescript for the first time. I am working on creating a login page where users can move to another page if their credentials are correct. To achieve this, I want to use a function that is triggered by clicking a button. How ...

Creating an Angular 2 MVC 5 Razor application and looking to add angular attributes to an @Html.DropDownFor? Here's how to do

Is there a way to achieve this kind of functionality in HTML using Razor? <select class="form-control" ([ngModel])="selectedWorkout" (ngModelChange)="updateWorkout($event)" #selectList="ngModel"> <option value="44">Pick me!</option> & ...

I am facing an issue with navigating between pages in Ionic 2. I am trying to move from one page to another, but it doesn't seem

My journey with Ionic 2 has just begun, and I'm excited to create a new page and navigate from the home page to the about page. However, when I attempted to use this.navCtrl.push('AboutPage'), an error message stating "push property does no ...

Exploration of mapping in Angular using the HttpClient's post

After much consideration, I decided to update some outdated Angular Http code to use HttpClient. The app used to rely on Promise-based code, which has now been mostly removed. Here's a snippet of my old Promise function: public getUser(profileId: nu ...

Why is it necessary for me to manually include and configure all d3.js dependencies in the SystemJS config file?

Currently, I am utilizing the systemjs.config.js file for an Application built on Angular5.x. To implement DAG charts in the application, I installed npm install --save @swimlane/ngx-graph and npm install --save @swimlane/ngx-charts. I have set up a comp ...

Error: The property '...' is not found in the ReactElement<any, any> type, but it is required in the type '{...}'

As a beginner in TypeScript, I am currently working on rendering a page by fetching data from getStaticProps. The code snippet I am using for this purpose is: import React, {FormEvent, useState} from "react"; import { InferGetStaticPropsType } fr ...

How to retrieve a value from an Angular form control in an HTML file

I have a button that toggles between map view and list view <ion-content> <ion-segment #viewController (ionChange)="changeViewState($event)"> <ion-segment-button value="map"> <ion-label>Map</ion-label> & ...

Generate a new Map<string, IDoSomethingWith<Something>> instance

Can anyone help me figure out how to instantiate a generic Map using TypeScript? Map<string, IDoSomethingWith<Something>> I attempted the following: const test: ReadonlyArray<string> = ['somekey']; new Map<string, IDoSomet ...