Jake12342134
Jake12342134

Reputation: 1769

TypeError: Cannot read property 'invalid' of null - Angular Reactive Forms

I have a reactive form within my Angular app that is being used to receive IP address ranges from the user. I am making use of a reactive form so that the user is able to enter and remove IP ranges at their own leisure.

The form itself makes use of a FormBuilder to create a FormArray with two form controls that are text inputs. All of the code to create the form and each form array element is relatively simplistic. However there seems to be an issue with something to do with displaying validation errors within my form. Specifically the lines

<mat-error *ngIf="endingRange.invalid">
    <p>IP address is not valid.</p>
</mat-error>

If I comment out these lines the form works properly without any issues. However if I have the lines uncommented then I start to get the following error:

TypeError: Cannot read property 'invalid' of null

This doesn't really make much sense to me as I have a getting for this particular form control declared within my component file like this

get endingRange() {
        return this.ipRangeForm.get('endingRange');
    }

Another aspect that may be an issue is that the form is initially hidden from the view and is enabled through the use of a toggle. I'm not sure if it would be causing the errors but it's worth mentioning. You can see the logic for this in the below files.

Here are the full files being used.

SettingsComponent

export class SettingsComponent implements OnInit {

ipRestrictionEnabled: boolean;
ipRangeForm: FormGroup;

constructor(
    baseService: PageService, private formBuilder: FormBuilder
) {
    super(baseService);
}

ngOnInit() {
    this.ipRestrictionEnabled = false;
    this.ipRangeForm = this.formBuilder.group(
        {
            ipRanges: this.formBuilder.array([])
        }
    );
}

ngOnDestroy() {
    this.destroy$.next(true);
}

getPageConfig() {
    return {
        pageKey: 'settings',
        displaySidebar: true,
        displayToolbar: true,
        backButtonRoute: ''
    };
}

toggleIpRestriction(): void {
    this.ipRestrictionEnabled = !this.ipRestrictionEnabled;
}

get ipRangeForms() {
    return this.ipRangeForm.get('ipRanges') as FormArray;
}

addRange() {
    const ipRange = this.formBuilder.group({
        startingRange: ['',
            [
                Validators.required,
                // tslint:disable-next-line:max-line-length
                Validators.pattern('^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$')
            ]
        ],
        endingRange: ['',
            [
                Validators.required,
                // tslint:disable-next-line:max-line-length
                Validators.pattern('^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$')
            ]
        ]
    });

    this.ipRangeForms.push(ipRange);
}

get startingRange() {
    return this.ipRangeForm.get('ipRanges').get('startingRange');
}

get endingRange() {
    return this.ipRangeForm.get('ipRanges').get('endingRange');
}

deleteRange(index) {
    this.ipRangeForms.removeAt(index);
}

submitIpRanges() {
    console.log(this.ipRangeForms.getRawValue());
}

}

SettingsComponent

<ng-container *ngIf="ipRestrictionEnabled">
        <div class="row mt-xl">
            <div class="col-12">
                <h4>{{ getTranslateKey('ipRanges') | translate }}</h4>
                <p>{{ getTranslateKey('ipRangesDescription') | translate }}</p>
            </div>
        </div>

        <div class="row">
            <div class="col-12">
                <form [formGroup]="ipRangeForm">
                    <div formArrayName="ipRanges">
                        <div *ngFor="let ranges of ipRangeForms.controls; let i=index" [formGroupName]="i">

                            <div class="row mb-lg">
                                <div class="col-6">
                                    <mat-card>
                                        <div class="row">
                                            <div class="col-5">

                                                <mat-form-field style="width: 100%;">
                                                    <label>
                                                        <input matInput placeholder="{{ getTranslateKey('startingRange.label') | translate }}" value="" formControlName="startingRange">
                                                    </label>
                                                    <mat-error *ngIf="startingRange.invalid">
                                                        <p>IP address is not valid.</p>
                                                    </mat-error>
                                                </mat-form-field>

                                            </div>
                                            <div class="col-5">

                                                <mat-form-field style="width: 100%;">
                                                    <label>
                                                        <input matInput placeholder="{{ getTranslateKey('endingRange.label') | translate }}" value="" formControlName="endingRange">
                                                    </label>
<!--                                                    <mat-error *ngIf="endingRange.invalid">-->
<!--                                                        <p>IP address is not valid.</p>-->
<!--                                                    </mat-error>-->
                                                </mat-form-field>

                                            </div>
                                            <div class="col-2 remove-column">
                                                <button swui-core-button (click)="deleteRange(i)" class="mr-sm pd-zero"
                                                        color="secondary" buttonStyle="link">
                                                    {{ getTranslateKey('remove') | translate }}
                                                </button>
                                            </div>
                                        </div>
                                    </mat-card>
                                </div>
                            </div>


                        </div>
                    </div>
                </form>
            </div>
        </div>

        <div class="row">
            <div class="col-12">
                <button swui-core-button (click)="addRange()" class="mr-sm" color="secondary" buttonStyle="link">
                    <mat-icon class="button-icon">add</mat-icon>
                    {{ getTranslateKey('addIP') | translate }}
                </button>
            </div>
        </div>
    </ng-container>

Upvotes: 0

Views: 7793

Answers (5)

bryan60
bryan60

Reputation: 29315

The issue is that you’re accessing ipRanges as though it isn’t an array when it is. Angular has no way of knowing which IP Range in the array you want the end range of. Try this instead:

getEndingRange(idx:number) {
  return this.ipRangeForms.controls[idx].get(‘endingRange’);
}

And in template, feed the index into the function.

<mat-error *ngIf="getEndingRange(i).invalid">

Do the same with start ranges

Upvotes: 0

MuruGan
MuruGan

Reputation: 1420

User form array name

  <mat-form-field style="width: 100%;">
       <label>
       <input matInput placeholder="{{ getTranslateKey('startingRange.label') | translate }}" value="" formControlName="startingRange">
       </label>
      <mat-error *ngIf="ranges.get('startingRange').invalid">
         <p>IP address is not valid.</p>
      </mat-error>
   </mat-form-field>

Upvotes: 3

Nimer Awad
Nimer Awad

Reputation: 4199

I think there's a tiny delay between first get calling and formGroup initialization. to ignore that; just put question mark to check endingRange.

<mat-error *ngIf="endingRange?.invalid">
    <p>IP address is not valid.</p>
</mat-error>

So, this'll make angular check for endingRange before check invalid property.

Upvotes: 0

Qellson
Qellson

Reputation: 552

The endingRange doesnt exist on ipRangeForm, it exists on the ipRanges members.

this.ipRangeForm = this.formBuilder.group(
    {
        ipRanges: this.formBuilder.array([])
    }
);

You need to attach the validation to each of the formArray members

Upvotes: 0

Bar Gans
Bar Gans

Reputation: 1653

just change

<mat-error *ngIf="endingRange.invalid">
    <p>IP address is not valid.</p>
</mat-error>

to

<mat-error *ngIf="endingRange && endingRange.invalid"> <p>IP address is not valid.</p> </mat-error>

Upvotes: 0

Related Questions