Reputation: 1769
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
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
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
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
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
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