Virender Thakur
Virender Thakur

Reputation: 519

conditional validation of Angular reactive forms

I am new to angular and working on reactive form.

I have a html table in which by looping through I have generated controls Table

I want to add validation based on following cases

  1. When this page loads than by default Save button should be disabled( which I have achieved by used [disabled]="!myform.valid"
  2. Save button should enable only when user enter value in any of text boxes or select check box in that particular row. Checkbox select and add value in text box should not allow to user. Either checkbox should be selected or user can enter value in any of text-boxes.

I tried this to achieve

IsFormValid(){return (!!this.myfm.get('myform').value);}// but this returning always true.

Here it my code

    myfm:FormGroup;
  ngOnInit() {
  debugger;
  this.myfm = this.fb.group({
  myform: this.fb.array([this.addformFileds()])
  }); 
}

 addformFileds(): FormGroup {
  return this.fb.group({
  NoTask: ['', Validators.required],// only check box is required either checkbox should click 
 or enter value in any text-boxes
                                    
  task1: ['', null],    
  task2: ['', null],
  task3: ['', null],
  task4: ['', null],
  task5: ['', null],
  task6: ['', null],
  task7: ['', null],
  task8: ['', null],
  task9: ['', null],
  task10: ['', null],
  task11: ['', null],
  task12: ['', null] 
});
}

 //adding more rows on click of Add More Rows button
addEntried():void{
this.idAddNewRow=true; //indicator that new rows is being created 
(<FormGroup>this.myfm.get('myform')).push(this.addEntried());
}

I know if is bit tricky but still didn't get solution for this. Any help of this will very helpful for me.

My component.html code

 <form #frmImp='NgForm' [formGroup]="myfm"]>
        <div class="modal-content">
        <div class="modal-header" style="align-items: center"> 
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">       
   <!--<table class="table-bordered table-responsive" style="overflow-x: true;"> to disable the scroll bar if screen resolution is less than 1920x1080-->
            <table class="table-bordered " >
      <thead>
          <tr style="border-bottom-style: solid"><td id="" class=""  colspan="15" style="text-align: center"><b>Imp Task Details</b></td>
          </tr> 
          <tr>
            <th style="text-align: center;"> </th>
            <th style="text-align: center;"> </th>
            <th style="text-align: center;">No Task</th>
            <th style="text-align: center;">Task 1</th>
            <th style="text-align: center;">Task 2</th>
            <th style="text-align: center;">Task 3</th>
            <th style="text-align: center;">Task 4</th>
            <th style="text-align: center;">Task 5</th>
            <th style="text-align: center;">Task 6</th>
            <th style="text-align: center;">Task 7</th>
            <th style="text-align: center;">Task 8</th>
            <th style="text-align: center;">Task 9</th>
            <th style="text-align: center;">Task 10</th>
            <th style="text-align: center;">Task 11</th>
            <th style="text-align: center;">Task 12</th> 
       
          </tr>
        </thead>
        <tbody formArrayName="myform" **ngFor="let frm of myfm.get('myform')['controls']; let i=index">
          <tr [[formGroupName]="i"]>
            <td></td>
            <td></td>
            <td><input [id]="'txt'+i" type="checkbox" formControlName="NoTask" style="margin-left: 30px;"/></td> 
            <td  ><input [id]="'txt'+i" type="tex" class="textboxFormat" formControlName="task1"   placeholder="0"></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task2" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task3" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task4" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task5" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task6" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task7" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task8" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task9" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"  formControlName="task10" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"  formControlName="task11" ></td>
            <td ><input [id]="'txt'+i" type="tex" class="textboxFormat"   formControlName="task12" ></td>  
          </tr>
           
        </tbody>
    </table> 
  </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary" *ngIf="isAvailable" (click)="addformFileds()">Add More Rows </button> 
          <button type="submit" class="btn btn-primary" (click)=SaveImpDetails(frmImp)>Save </button> 
        </div>
      </div>
    </form>


**component.ts** file code
SaveImpDetails(){
   this.baseSrvc.post(ApiResource.SaveImpDetails, 
    JSON.stringify(body)).subscribe((result=>{
if(result){
   this.alertService.showMessage("Success!", "Details has been 
    recorded 
    successfuly.")
 }if(result.isError){
 this.alert.showMessage("Error!! while saving details");
}
  }));
   }
   

Upvotes: 3

Views: 2781

Answers (2)

Ashot Aleqsanyan
Ashot Aleqsanyan

Reputation: 4453

Update:

After understanding the whole question, we could resolve it by adding a new validator to each form item.

Here is the reproduction example(You can see the same on the Stackblitz):

  myfm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() { this._generateForm(); }

  private _generateForm() {
    this.myfm = this.fb.group({
      myform: this.fb.array([this.addformFileds()]),
    });
  }

  addformFileds(): FormGroup {
    return this.fb.group(
      {
        NoTask: '',
        task1: '',
        task2: '',
        task3: '',
        task4: '',
        task5: '',
        task6: '',
        task7: '',
        task8: '',
        task9: '',
        task10: '',
        task11: '',
        task12: '',
      },
      { validator: [validateRow()] }
    );
  }

and the helper function and validator


function validateRow(): ValidatorFn {
  const pairs: string[][] = [
    ['task1', 'task2'],
    ['task3', 'task4'],
    ['task5', 'task6'],
    ['task7', 'task8'],
    ['task9', 'task10'],
    ['task11', 'task12'],
  ];
  return (group: FormGroup): ValidationErrors | null => {
    if (group.get('NoTask').value) {
      return null;
    }

    // check the pair validities
    const invalidPair = pairs.find((pair) => {
      const firstTask = group.get(pair[0]).value;
      const secondTask = group.get(pair[1]).value;

      if ((firstTask && !secondTask) || (!firstTask && secondTask)) {
        return true;
      }

      return false;
    });

    // return pair validity error object
    if (invalidPair) {
      return { invalidPair };
    }

    if (!singleWhere(group, (item) => item.value)) {
      return {invalidRow: true};
    }

    return null;
  };
}

/**
 * find and return the first control for which
 * the `callback` function returns `true`
 *
 * loop over the `group` controls
 * and return the `control`
 * if the `callback` function returns `true` for the `control`
 *
 * @param group - is the form
 * @param callback - is the callback function
 *
 * @return `AbstractControl`
 */
 function singleWhere(
  group: AbstractControl,
  callback: (control: AbstractControl, index: number) => boolean
): AbstractControl {
  if (!group) {
    return null;
  }
  if (group instanceof FormControl) {
    return callback(group, 0) ? group : null;
  }

  const keys = Object.keys((group as FormGroup).controls);

  for (let i = 0; i < keys.length; i++) {
    const control = group?.get(keys[i]);

    if (singleWhere(control, callback)) {
      return group;
    }
  }

  return null;
}

And finally, in the template, add disable attribute by this condition

[disabled]="myfm.invalid || myfm.untouched"

Here is the reproduction example https://stackblitz.com/edit/angular-bootstrap-4-starter-vvimke?file=src/app/app.component.ts

Upvotes: 2

Eliseo
Eliseo

Reputation: 58099

Why not use a FormArray for the tasks? As you has a formArray and inside a formArray you should use two functions:

  //you can defined your formArray like
  formArray=this.fb.array([0,1,2,3,4,5,6,7].map(_=>this.addformFields()))

  getGroup(i)
  {
    return this.formArray.at(i) as FormGroup
  }

  tasks(i)
  {
    return this.getGroup(i).get('tasks') as FormArray
  }

Your function addformFields becomes like

  addformFields(): FormGroup {
    return this.fb.group({
      noTask:false,
      tasks:this.fb.array([0,1,2,3,4,5,6,7,8,9,10,11].map(_=>this.fb.control(0)))
    },{validator:this.someValue()})
  }

And the validator is like

  someValue(){
    return (control:AbstractControl)=>{
      const group=control as FormGroup;
      let valid=control.get('noTask').value;
      (control.get('tasks') as FormArray).controls.forEach(x=>{
        valid=valid || +x.value!=0
      })
      return valid?null:{error:"You shoul indicate one task or that there' not task"}
    }
  }

The .html to control the FormArray

<table class="form-group">
  <tr>
    <th>Section</th>
    <th *ngFor="let i of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]">Task {{ i }}</th>
  </tr>
  <tbody>
    <tr
      *ngFor="let group of formArray.controls; let i = index"
      [formGroup]="getGroup(i)"
    >
      <td><input type="checkbox" formControlName="noTask" /></td>
      <ng-container formArrayName="tasks">
        <td *ngFor="let control of tasks(i).controls; let j = index">
          <input
            [attr.disabled]="getGroup(i).get('noTask').value ? true : null"
            [formControlName]="j"
          />
        </td>
      </ng-container>
    </tr>
  </tbody>
</table>

See that if, e.g. you defined a .css like

tr.ng-invalid.ng-touched input{
  border:1px solid red;
}
tr.ng-invalid.ng-touched input:focus{
  border-color:   red;
  border-radius: .25rem;
  border-width: 2px;
  outline: 0;
  box-shadow: 0 0 0 .15rem rgba(255,0,0,.25);
}

A stackblitz

Upvotes: 2

Related Questions