2

I'm having a Contact form in Angular using a Reactive form. The form contains firstName, lastName and an Array of address. Each address formgroup contains a checkbox, if the checkbox is checked, validation of the State text box mandatory is needed, along with min and max char length.

I have written a custom validator namely "stateValidator" and I tried to get the sibling element "isMandatory" but I am not able to get the control.

I tried the following approach

  • control.root.get('isMandatory'); - Its returning null
  • control.parent.get('isMandatory'); - Its throwing exception

I found some links in stackoverflow, but there is no answer available and some answers are not giving solutions, for example the code above: control.root.get('isMandatory'); I got this from one of the video tutorials but nothing worked.

The complete working source code is available in StackBlitz: https://stackblitz.com/edit/angular-custom-validators-in-dynamic-formarray

app.component.ts

import { Component } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, Validators, AbstractControl } from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';
  public userForm: FormGroup;

  constructor(private _fb: FormBuilder) {
    this.userForm = this._fb.group({
      firstName: [],
      lastName: [],
      address: this._fb.array([this.addAddressGroup()])
    });
  }

  private addAddressGroup(): FormGroup {
    return this._fb.group({
      street: [],
      city: [],
      state: ['', this.stateValidator],
      isMandatory: [false, [Validators.required]]
    });
  }

  get addressArray(): FormArray {
    return <FormArray>this.userForm.get('address');
  }

  addAddress(): void {
    this.addressArray.push(this.addAddressGroup());
  }

  removeAddress(index: number): void {
    this.addressArray.removeAt(index);
  }

  stateValidator(control: AbstractControl): any {
        if(typeof control === 'undefined' || control === null 
        || typeof control.value === 'undefined' || control.value === null) {
            return {
                required: true
            }
        }

        const stateName: string = control.value.trim();
        const isPrimaryControl: AbstractControl = control.root.get('isMandatory');

        console.log(isPrimaryControl)

        if(typeof isPrimaryControl === 'undefined' || isPrimaryControl === null||
        typeof isPrimaryControl.value === 'undefined' || isPrimaryControl.value === null) {
            return {
                invalidFlag: true
            }
        }

        const isPrimary: boolean = isPrimaryControl.value;

        if(isPrimary === true) {
            if(stateName.length < 3) {
                return {
                    minLength: true
                };
            } else if(stateName.length > 50) {
                return {
                    maxLength: true
                };
            }
        } else {
            control.setValue('');
        }

        return null;
    }
}

app.component.html

<form class="example-form" [formGroup]="userForm">
  <div>
    <mat-card class="example-card">
      <mat-card-header>
        <mat-card-title>Users Creation</mat-card-title>
      </mat-card-header>
      <mat-card-content>
        <div class="primary-container">
          <mat-form-field>
            <input matInput placeholder="First Name" value="" formControlName="firstName">
          </mat-form-field>
          <mat-form-field>
            <input matInput placeholder="Last Name" value="" formControlName="lastName">
          </mat-form-field>
        </div>
        <div formArrayName="address">
          <div class="address-container" *ngFor="let group of addressArray.controls; let i = index;"
            [formGroupName]="i">
            <fieldset>
              <legend>
                <h3>Address: {{i + 1}}</h3>
              </legend>
              <mat-checkbox formControlName="isMandatory">Mandatory</mat-checkbox>
              <div>
                <mat-form-field>
                  <input matInput placeholder="Street" value="" formControlName="street">
                </mat-form-field>
                <mat-form-field>
                  <input matInput placeholder="City" value="" formControlName="city">
                </mat-form-field>
                <mat-form-field>
                  <input matInput placeholder="State" value="" formControlName="state">
                </mat-form-field>
              </div>
            </fieldset>
          </div>
        </div>
        <div class="form-row org-desc-parent-margin">
          <button mat-raised-button (click)="addAddress()">Add more address</button>
        </div>
      </mat-card-content>
    </mat-card>
  </div>
</form>
<mat-card class="pre-code">
  <mat-card-header>
    <mat-card-title>Users Information</mat-card-title>
  </mat-card-header>
  <mat-card-content>
    <pre>{{userForm.value | json}}</pre>
  </mat-card-content>
</mat-card>

Kindly assist me how to get the sibling abstract control in the custom validator method.

I tried the code which was specified in the following couple of answers

isPrimaryControl = (<FormGroup>control.parent).get('isMandatory')

It's throwing an error ERROR Error: too much recursion. Kindly assist me how to fix this.

7
  • If you have to put validations which include multiple formControls, consider adding the Validator to the formGroup instead of a single formControl. Commented Apr 11, 2019 at 8:04
  • @SachinGupta - My primary aim of this question is how to get the sibling control over another control. Commented Apr 12, 2019 at 11:24
  • As stated in my answer, you can use (<FormGroup>control.parent).get('isMandatory'). However, depending on where you access control.parent, there might be a chance that the value is undefined so you have to put proper checks Commented Apr 12, 2019 at 11:26
  • I'm getting an error ERROR Error: too much recursion if I add the code isPrimaryControl = (<FormGroup>control.parent).get('isMandatory') Commented Apr 12, 2019 at 11:35
  • 1
    comment the control.setValue('') Commented Apr 12, 2019 at 11:51

2 Answers 2

4

You may need to cast the parent to FormGroup, try using :

if(control.parent) {
    (<FormGroup>control.parent).get('isMandatory')
}
Sign up to request clarification or add additional context in comments.

7 Comments

Its giving an console error ERROR Error: control.parent is undefined
I tried in the chrome console with a debugger in stateValidator function, it gave the parent group and I was also able to get the isMandatory control.
could you please see the code in StackBlitz stackblitz.com/edit/…
Seen now. When the control is created, it is not attached to a parent, hence you will get the error. Just put a if (control.parent) check and everything else should work fine.
Forked your code stackblitz.com/edit/… You need to comment the control.setValue('') or you will not be able to type anything in control. Some minor tweaking on click of checkbox might be needed for proper validation.
|
1

You can get the value of isMandatory from Form control like this, i have changed your stateValidator method to basically typecast the control to concrete sub class then from the controls array you can get the formControls

stateValidator(control: AbstractControl): any {
let mandatory:boolean=false;
if(control.parent){
  console.log('control',<FormGroup>control.parent.controls.isMandatory.value);
  mandatory=<FormGroup>control.parent.controls.isMandatory.value;
}
    if((typeof control === 'undefined' || control === null 
    || typeof control.value === 'undefined' || control.value === null)&& mandatory) {
      debugger;
        return {
            required: true
        }
    }

    const stateName: string = control.value.trim();
    let isPrimaryControl: AbstractControl=null;
    if(control.parent){
     isPrimaryControl=<FormGroup>control.parent.controls.isMandatory;
    console.log(isPrimaryControl)
    }
    if(typeof isPrimaryControl === 'undefined' || isPrimaryControl === null||typeof isPrimaryControl.value === 'undefined' || isPrimaryControl.value === null) {
        return {
            invalidFlag: true
        }
    }

    const isPrimary: boolean = isPrimaryControl.value;

    if(isPrimary === true) {
        if(stateName.length < 3) {
            return {
                minLength: true
            };
        } else if(stateName.length > 50) {
            return {
                maxLength: true
            };
        }
    } else {
        control.setValue('');
    }

    return null;
}

4 Comments

I think its crashing the page if I use control.parent.controls (i.e., In actual application it throws an exception)
What’s the exception it’s throwing?
Try your code in the following StackBlitz stackblitz.com/edit/… it will crash the window.
I'm getting the error ERROR Error: too much recursion

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.