'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");
}
  }));
   }
   


Solution 1:[1]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eliseo