Search code examples
javascriptangularangular-formsangular-formbuilder

Nested dynamic form in angular - Cannot read property 'controls' of undefined


I'm trying to build a website which provides the functionality of uploading your own courses.

Course Structure

    Name of course
    |-Module1
      |-Lecture1
      |-Lecture2
    |-Module2
      |-Lecture1
      |-Lecture2

Using Angular I'm trying to create a dynamic form which will add/remove modules within the course and lecture within a module

So far, I have written the following -

course-upload.component.ts

export class CourseUploadComponent implements OnInit {

    courseUploadForm: FormGroup;

    constructor(private formBuilder: FormBuilder) { }

    ngOnInit() {
        this.courseUploadForm = this.formBuilder.group({
            coursename: ['', Validators.required],
            modules: this.formBuilder.array([
                this.initModules()
            ])
        })
    }

    initModules() {
        return this.formBuilder.group({
            modulename: ['', Validators.required],
            lectures: this.formBuilder.array([
                this.initLecture()
            ])
        });
    }

    initLecture() {
        return this.formBuilder.group({
            lecturename: ['', Validators.required],
            description: ['', Validators.required],
            lecture: ['', Validators.required]
        });
    }

    addModule() {
        const control = <FormArray>this.courseUploadForm.get('modules');
        control.push(this.initModules());
    }

    addLecture() {
        const control = <FormArray>this.courseUploadForm.get('lectures');
        control.push(this.initLecture());
    }

    removeModule(i: number) {
        const control = <FormArray>this.courseUploadForm.get('modules');
        control.removeAt(i);

    } 

    removeLecture(i: number) {
        const control = <FormArray>this.courseUploadForm.get('lectures');
        control.removeAt(i);
    }

    getModulesControls(i: number) {
        >>>> return [(this.courseUploadForm.controls.modules as FormArray).controls[i]['controls']];
    }

    getLecturesControls(i: number) {
        return [(this.courseUploadForm.controls.lectures as FormArray).controls[i]['controls']];
    }

}

course-upload.component.html

<form [formGroup]="courseUploadForm" novalidate>

    <div formArrayName="modules">

        <mat-card *ngFor="let module of courseUploadForm.get('modules').value; let i=index">

            <mat-card-subtitle>
                {{i+1}}
            </mat-card-subtitle>

            <div [formGroupName]="i">

                <mat-form-field>
                    <mat-label>Module Name</mat-label>
                   **>>>** <input matInput placeholder="Module Name" formControlName="modulename">
                    <ng-container *ngFor="let control of getModulesControls(j)">
                        <mat-error *ngIf="!control.name.valid">Name Required</mat-error>
                    </ng-container>
                </mat-form-field>

                <div formArrayName="lectures">

                    <mat-card *ngFor="let lecture of module.get('lectures').value; let j=index">

                        <mat-card-subtitle>
                            Lecture {{i+1}}: {{lecture.name}}
                        </mat-card-subtitle>

                        <div [formGroupName]="j">

                            <mat-form-field>
                                <mat-label>Name</mat-label>
                                <input matInput placeholder="Lecture Name" formControlName="lecturename">
                                <ng-container *ngFor="let control of getLecturesControls(j)">
                                    <mat-error *ngIf="!control.name.valid">Name Required</mat-error>
                                </ng-container>
                            </mat-form-field>

                            <mat-form-field>
                                <mat-label>Description</mat-label>
                                <input matInput placeholder="Lecture Description" formControlName="description">
                                <ng-container *ngFor="let control of getLecturesControls(j)">
                                    <mat-error *ngIf="!control.description.valid">Description Required</mat-error>
                                </ng-container>
                            </mat-form-field>

                            <mat-form-field>
                                <mat-label>Lecture</mat-label>
                                <input matInput placeholder="Lecture Video" formControlName="lecture">
                                <ng-container *ngFor="let control of getLecturesControls(j)">
                                    <mat-error *ngIf="!control.lecture.valid">Lecture Video Required</mat-error>
                                </ng-container>
                            </mat-form-field>
                            <mat-card-actions>
                                <button mat-raised-button color="accent" (click)="addLecture()">Add Another
                                    Lecture</button>
                                <button mat-raised-button color="warn"
                                    *ngIf="module.get('lectures')['controls'].length > 1"
                                    (click)="removeLecture(j)">Remove This Lecture</button>
                            </mat-card-actions>
                        </div>

                    </mat-card>

                </div>
                <mat-card-actions>
                    <button mat-raised-button color="accent" (click)="addModule()">Add Another Module</button>
                    <button mat-raised-button color="warn"
                        *ngIf="courseUploadForm.get('modules')['controls'].length > 1" (click)="removeModule(i)">Remove
                        This Module</button>
                </mat-card-actions>
            </div>

        </mat-card>

    </div>

</form>

I get the error:

Cannot read property 'controls' of undefined

at CourseUploadComponent.getModulesControls 

at CourseUploadComponent_mat_card_2_Template 

I've highlighted the lines that throw the error with ** > **

Some help?


Solution

  • j is not defined here it should have been smg different

    <ng-container *ngFor="let control of getModulesControls(j)">
        <mat-error *ngIf="!control.name.valid">Name Required</mat-error>
    </ng-container>
    

    I suppose you meant to use the variable i there: getModulesControls(i)

    Also, line 5 of the HTML file the variable module is defined as an Object. Line 23 module.get('lectures') looks like you expect a FormGroup in the module variable. Take a look at this example from Angular docs. Pay attention to both HTML markup and TS. You will need to create several getters like get cities(): FormArray (: