0

I have from group "additionalForm" in that i have formArray called "validations" here the formArray is dynamic which is binding the values to validtionsField array. In the validtionsField array i have three objects in which i have two values which needs to be compared and they are Min-length and Max-Length.

ex. If i enter min length greater then the max length it should give error.

here is code for the above functionality

import {
  Component,
  OnInit,
  Inject
} from "@angular/core";
import {
  FormControl,
  FormArray,
  FormGroup,
  FormBuilder,
  Validators,
  AbstractControl,
  ValidationErrors,
  NgControlStatus,
  ValidatorFn
} from "@angular/forms";
import {
  MatDialogRef,
  MAT_DIALOG_DATA,
  MatSnackBar
} from "@angular/material";

@Component({
  selector: "app-root",
  templateUrl: "./app.component.html",
  styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
  validtionsField = [{
      validField: "Min Length",
      type: false,
      fieldType: "input",
      value: 1,
      keyval: "minLength"
    },
    {
      validField: "Max Length",
      type: false,
      fieldType: "input",
      value: 50,
      keyval: "maxLength"
    },
    {
      validField: "DataType",
      type: false,
      fieldType: "dropDown",
      dataTypeList: [],
      dataTypeId: "minLength",
      keyval: "dataTypeId",
      value: 874
    }
  ];

  dataType = [{
      id: 3701,
      localeId: 1,
      tenantId: 1,
      parentCategoryId: null,
      parentContentId: 873,
      name: "Alphabets",
      description: null
    },
    {
      id: 3702,
      localeId: 1,
      tenantId: 1,
      parentCategoryId: null,
      parentContentId: 874,
      name: "Alphanumeric",
      description: null
    }
  ];
  additionalForm: FormGroup = this.fb.group({
    fieldName: ["", [Validators.required]],
    validations: this.fb.array([])
  });

  constructor(public fb: FormBuilder) {}

  ngOnInit() {
    let frmArray = this.additionalForm.get("validations") as FormArray;

    for (let data of this.validtionsField) {
      frmArray.push(this.initSection(data));
    }
  }
  initSection(data) {
    return this.fb.group({
      validField: [data.validField, [Validators.required]],
      type: [data.type, [Validators.required]],
      value: [data.value, [Validators.required]],
      dataTypeList: [this.dataType, [Validators.required]],
      fieldType: [data.fieldType, [Validators.required]],
      validArray: []
    }, {
      validator: this.customValidator
    });
  }

  checkFieldType(data): any {
    return data === "dropDown";
  }

  // trying to access using below functioa to compare values min and max length
  public customValidator(control: AbstractControl): ValidationErrors | null {
    const newValue = control.get("value") ? control.get("value").value : null;
    const values = control.get("value") ? control.get("value").value : [];
    console.log("1 " + newValue);
    console.log(values);
    for (let i = 0, j = values.length; i < j; i++) {
      if (newValue === values[i]) {
        return {
          duplicate2: true
        };
      }
    }
    return null;
  }
}
<form [formGroup]="additionalForm">
  <mat-form-field>
    <input formControlName='fieldName' placeholder="Field Name" required matInput>
  </mat-form-field>
  <div class="row">
    <div class="col-md-12 col-sm-12">
      \
      <div formArrayName="validations">
        <ng-container *ngFor="let validationForm of  additionalForm.controls.validations.controls; let i = index">
          <div class="valid-data" [formGroupName]="i">
            <span>
                  <label>{{validationForm.value.validField }}</label>

                </span>
            <span>
                  <ng-container *ngIf="checkFieldType(validationForm.value.fieldType ); else input">
                    <mat-form-field class="select-dataType">
                      <mat-select required formControlName='value'  placeholder="Datatype">
                        <mat-option *ngFor="let fieldTypeData of validationForm.value.dataTypeList"
                          [value]='fieldTypeData.parentContentId'>
                          {{fieldTypeData.name}}</mat-option>
                      </mat-select>
                    </mat-form-field>
                  </ng-container>
                  <ng-template #input>
                    <mat-form-field>
                      <input required  formControlName='value' pattern= "[0-9]+" matInput>
                    </mat-form-field>
                  </ng-template>
                </span>
            <div *ngIf="validationForm.get('value')?.touched ">
              <div class="error" *ngIf="validationForm.get('value').hasError('required')">
                {{validationForm.value.validField}} is required
              </div>
            </div>
          </div>
        </ng-container>
      </div>
    </div>
  </div>
</form>

above is the TS and HTML code and below is the function which i am trying to get old and new value from control but its failing, its giving me value from same input field from same min-length

/// trying this below functio to compare the min and max length.

public customValidator(control: AbstractControl): ValidationErrors | null {
  const newValue = control.get('value') ? control.get('value').value : null;
  const values = control.get('value') ? control.get('value').value : [];
  console.log("1 " + newValue);
  console.log(values);
  for (let i = 0, j = values.length; i < j; i++) {
    if (newValue === values[i]) {
      return {
        'duplicate2': true
      };
    }
  }
  return null;
}

Please help me to compare values from dynamic form array, and here what ever values enters to from-array object are all bind to formcontrolName "value"

here is the link for code :

https://stackblitz.com/edit/angular6-material-components-demo-wgtafn

2 Answers 2

1

Since you have two fields minLength & maxLength which validation depends on each other, you can add a validator to the parent group and use a custom ErrorStateMatcher to translate the parent group errors to the children. I also used a FormGroup instead of FormArray, in this case it's more convenient.

@Component({...})
export class AppComponent {
  ...

  readonly invalidLengthMatcher: ErrorStateMatcher = {
    isErrorState: () => {
      const control = this.additionalForm.get('validations');
      return control.hasError('invalidLength');
    }
  };

  readonly controlFields = this.validtionsField.map(field => ({
    field,
    control: new FormControl(field.value, Validators.required),
    errorMatcher: this.errorMatcherByFieldId(field.keyval)
  }));

  private readonly controlMap = this.controlFields.reduce((controls, controlField) => {
    controls[controlField.field.keyval] = controlField.control;
    return controls;
  }, {});

  readonly additionalForm = new FormGroup({
    fieldName: new FormControl("", [Validators.required]),
    validations: new FormGroup(this.controlMap, {
      validators: (group: FormGroup) => {
        const [minLength, maxLength] = ['minLength', 'maxLength'].map(fieldId => {
          const control = group.get(fieldId);
          return Number(control.value);
        });

        if (minLength > maxLength) {
          return {
            'invalidLength': true
          };
        } else {
          return null;
        }
      }
    })
  });

  private errorMatcherByFieldId(fieldId: string): ErrorStateMatcher | undefined {
    switch (fieldId) {
      case 'minLength':
      case 'maxLength':
        return this.invalidLengthMatcher;
    }
  }
}
<form [formGroup]="additionalForm">
  <mat-form-field>
    <input formControlName='fieldName' placeholder="Field Name" required matInput>
  </mat-form-field>
  <div class="row">
    <div class="col-md-12 col-sm-12">
      <div formGroupName="validations" >
        <div *ngFor="let controlField of controlFields" class="valid-data">
          <span>
            <label>{{controlField.field.validField}}</label>
          </span>
          <span [ngSwitch]="controlField.field.fieldType">
            <mat-form-field *ngSwitchCase="'dropDown'" class="select-dataType">
              <mat-select required placeholder="Datatype" [formControlName]="controlField.field.keyval">
                <mat-option *ngFor="let fieldTypeData of dataType"
                            [value]='fieldTypeData.parentContentId'>{{fieldTypeData.name}}</mat-option>
              </mat-select>
            </mat-form-field>
            <mat-form-field *ngSwitchCase="'input'">
              <input matInput
                      required
                      type="number"
                      pattern= "[0-9]+"
                      [formControlName]="controlField.field.keyval"
                      [errorStateMatcher]="controlField.errorMatcher">
            </mat-form-field>
          </span>
          ...
        </div>
      </div>
    </div>
  </div>
</form>

StackBlitz

Sign up to request clarification or add additional context in comments.

2 Comments

@vinuta You are welcome! A like is the best reward :)
can you please try to change the logic?
1
+50

You had to attach this validator to form array so that you can have access to all controls.

My Custom Validator :-

    export function customValidateArray(): ValidatorFn {
    return (formArray:FormArray):{[key: string]: any} | null=>{
      console.log('calling');
      let valid:boolean=true;
      setTimeout(()=>console.log(formArray),200);
      let minIndex = Object.keys(formArray.controls).findIndex((key) => formArray.controls[key].value.validField.toLowerCase()==="min length");
      let minLengthValue = formArray.controls[minIndex].value.value;
      let maxLengthValue = formArray.controls[Object.keys(formArray.controls).find((key) => formArray.controls[key].value.validField.toLowerCase()==="max length")].value.value;
      return minLengthValue < maxLengthValue ? null : {error: 'Min Length Should be less than max length', controlName : formArray.controls[minIndex].value.validField};
    }
  };

I added it to form array ngOnInit of your code :-

ngOnInit() {
    let frmArray = this.additionalForm.get("validations") as FormArray;

    for (let data of this.validtionsField) {
      frmArray.push(this.initSection(data));
    }
    frmArray.setValidators(customValidateArray());
  }

Use it in template like :-

<div class="error" *ngIf="additionalForm.controls.validations.errors && validationForm.value.validField ===  additionalForm.controls.validations.errors.controlName">
                             {{additionalForm.controls.validations.errors.error}}
                      </div>

Working stackblitz :-

https://stackblitz.com/edit/angular6-material-components-demo-p1tpql

13 Comments

how to bind this to html?
you can save frmArray in global variable and in template can check if its having that error or like i have appended it to form array, if you can append it to additional form that will do.
tried all ways but when i am trying to binding to template its giving circulate json error
hey i was able to update the answer with working and also added a working stackblitz. please check.
you can set validators on form arrays in their construction, FYI
|

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.