Search code examples
angularformsvalidationformarray

How to disable submit button if a form array is empty?


I have a form array in my angular form. I want to apply validation to this form array. If the form array is empty the submit button should be disabled. The condition for submitting the form is that it should have at least one item in the array.

  <!-- Left Panel -->
  <app-empsidebar></app-empsidebar>
  <div id="right-panel" class="right-panel">
      <app-empheader></app-empheader>
      <!-- Header -->
      <div class="content">
          <div class="animated fadeIn">         
              <div class="card">
                  <div class="card-header">
                      <strong class="card-title"> Add Course</strong>
                  </div>
                  {{this.courseForm.value | json}} 
                  <!-- {{this.TrainingModule.value | json}}  -->
                  <form  (ngSubmit)="onsubmit()" [formGroup]="courseForm" > 
                      <div class="row col-md-12">
                          <div class="col-md-6">
                              <div class="col-md-12 mt-4">
                                  <div class="row form-group">
                                      <div class="col-md-3">
                                          <label for="selectSm" class="form-control-label control-label">Department</label>
                                      </div>
                                      <div class="col-md-9">
                                          <select name="department" id="department" class="form-control-sm form-control" formControlName="department">
                                              <option value="" selected disabled>Select Department</option>
                                              <option value="EM">Employee</option>
                                              <option value="HR">HR</option>
                                              <option value="HA">HR Assistant</option>
                                              <option value="MN">Manager</option>
                                              <option value="LM">Line Manager</option>
                                          </select>
                                          <br>
                                          <span style="color:red;" *ngIf="department && department.invalid && department.touched ">Required</span>
                                      </div>
                                  </div>
                              </div>
                              <div class="col-md-12">
                                  <div class="row form-group">
                                      <div class="col col-md-3"><label for="text-input" class="form-control-label control-label">Title</label></div>
                                      <div class="col-12 col-md-9">
                                          <input type="text" id="title" name="title" required  class="form-control" formControlName="title">
                                          <span style="color:red;" *ngIf="title && title.invalid && title.touched">Required</span>
                                        </div>
                                  </div>
                              </div>
                              <div class="col-md-12">
                                  <div class="form-group row">
                                      <label for="staticEmail" class="col-sm-3 control-label">Description</label>
                                      <div class="col-12 col-md-9">
                                          <textarea name="description" id="description" end_date rows="3" class="form-control" formControlName="description"></textarea>
                                         
                                        </div>
                                     
                                  </div>
                              </div>
                          </div>
                          <div class="col-md-6">
                              <div class="col-md-12 mt-4">
                                  <div class="row form-group">
                                      <div class="col col-md-3"><label for="text-input" class="form-control-label control-label">Start Date</label></div>
                                      <div class="col-12 col-md-9"><input type="date" id="start_date" name="start_date"  class="form-control" formControlName="start_date"></div>
                                  </div>
                              </div>
                              <div class="col-md-12">
                                  <div class="row form-group">
                                      <div class="col col-md-3"><label for="text-input" class="form-control-label control-label">End Date</label></div>
                                      <div class="col-12 col-md-9"><input type="date" id="end_date" name="end_date"  class="form-control" formControlName="end_date"></div>
                                  </div>
                              </div>
                              
                              <div class="col-md-12">
                                  <div class="form-group row">
                                      <label for="staticEmail" class="col-sm-3 control-label">Assessment</label>
                                      <div class="col-sm-9">
                                          <input type="radio" id="assessment_status" name="assessment_status" value="1" checked formControlName="assessment_status" >
                                          <label class="ml-1 mr-3">No</label> 
                                          <input type="radio" id="assessment_status" name="assessment_status" value="2" formControlName="assessment_status">
                                          <label class="ml-1">Yes</label>
                                      </div>
                                  </div>
                              </div>
                          </div>
                          <div class="col-md-12" formArrayName="TrainingModules" >
                              <div class="">
                                  <div class="text-right mt-2">
                                      <button type="button" (click)="addModule()" class="btn btn-success btn-sm" ><i class="fa fas fa-plus-circle"></i> Add Module</button>
                                  </div>
                                  <div class="table-stats ov-h">
                                      <table class="table data">
                                          <thead>
                                              <tr>
                                                  <th scope="col" class="tb_font_sty">Module Name</th>
                                                  <th scope="col" class="tb_font_sty">Source</th>
                                                  <th scope="col" class="tb_font_sty">Path</th>
                                                  <th scope="col" class="tb_font_sty">Duration</th>
                                                  <th scope="col" class="tb_font_sty"></th>
                                              </tr>
                                          </thead>
                                          <tbody *ngFor="let TrainingModule of TrainingModules().controls; let i=index" [formGroupName]="i" >
                                              <tr>
                                                  <!-- <input type="text" name="index" value="{{i+1}}"> -->
                                                  <td scope="row "><input type="text" name="modules[]" class="form-control form-control-sm" formControlName="modules"[ngClass]="{'is-invalid':
                                                    trainingModulescon(i).controls['modules'].errors && (trainingModulescon(i).controls['modules'].dirty || trainingModulescon(i).controls['modules'].touched)}">
                                                    
                                              
                                                <div *ngFor="let validation of validation_modulemessage.modules"  class="invalid-feedback">
                                                  <div  *ngIf="trainingModulescon(i).controls['modules'].hasError(validation.type) && (trainingModulescon(i).controls['modules'].dirty || trainingModulescon(i).controls['modules'].touched)">
                                                  {{ validation.message }} 
                                              </div> 
                                              </div>
                                                </td>
                                                  <td class="">
                                                      <select name="selectSource[]" id="{{i}}" class="form-control form-control-sm" formControlName="selectSource" (change)="selectsource($event)" [ngClass]="{'is-invalid':
                                                      trainingModulescon(i).controls['selectSource'].errors && (trainingModulescon(i).controls['selectSource'].dirty || trainingModulescon(i).controls['selectSource'].touched)}">
                                                          <option value="" selected disabled>Select Source</option>
                                                          <option value="1">Youtube</option>
                                                          <option value="2">Video</option>
                                                          <option value="3">Audio</option>
                                                          <option value="4">Document</option>
                                                      </select>
                                                     
                                                      
                                                
                                                  <div *ngFor="let validation of validation_modulemessage.selectSource"  class="invalid-feedback">
                                                    <div  *ngIf="trainingModulescon(i).controls['selectSource'].hasError(validation.type) && (trainingModulescon(i).controls['selectSource'].dirty || trainingModulescon(i).controls['selectSource'].touched)">
                                                    {{ validation.message }} 
                                                </div> 
                                                </div>
                                                  </td>
                                                  <td class="path_{{i}}">
                                                      <input type="text"class="form-control form-control-sm" name="path[]" formControlName="path" [ngClass]="{'is-invalid':
                                                      trainingModulescon(i).controls['path'].errors && (trainingModulescon(i).controls['path'].dirty || trainingModulescon(i).controls['path'].touched)}">
                                                      
                                                
                                                  <div *ngFor="let validation of validation_modulemessage.path"  class="invalid-feedback">
                                                    <div  *ngIf="trainingModulescon(i).controls['path'].hasError(validation.type) && (trainingModulescon(i).controls['path'].dirty || trainingModulescon(i).controls['path'].touched)">
                                                    {{ validation.message }} 
                                                </div> 
                                                </div>
                                            </td>
                                                  <td class="video_{{i}} hide">
                                                      <input type="file"  formControlName="video" name="video[]">
                                                  </td>
                                                  <td class="audio_{{i}} hide">
                                                      <input type="file"    formControlName="audio" name="audio[]"><!-- style="width: 95px;" -->
                                                  </td>
                                                  <td class="upldoc_{{i}} hide">
                                                      <input type="file" formControlName="upl_doc" name="upldoc[]">
                                                  </td>
                                                  <td class="">
                                                      <input type="text"  name="duration[]" class="form-control form-control-sm"  formControlName="duration" [ngClass]="{'is-invalid':trainingModulescon(i).controls['duration'].errors && (trainingModulescon(i).controls['duration'].dirty || trainingModulescon(i).controls['duration'].touched)}">
                                                      
                                                
                                                  <div *ngFor="let validation of validation_modulemessage.duration"  class="invalid-feedback">
                                                    <div  *ngIf="trainingModulescon(i).controls['duration'].hasError(validation.type) && (trainingModulescon(i).controls['duration'].dirty || trainingModulescon(i).controls['duration'].touched)">
                                                    {{ validation.message }} 
                                                </div> 
                                                </div>
                                                  </td>       
                                                  <td>
                                                      <button class="btn btn-danger btn-sm" (click)="removeModule(i)"><i class="fa fa-trash" aria-hidden="true"></i></button>
                                                  </td>
                                              </tr>
                                          </tbody>
                                      </table>
                                  </div>
                                  <!-- /.table-stats -->
                              </div>
                          </div>
                      </div>
                      <div class="col-md-12 mt-4">
                          <div class="mb-2" align="right">
                              <button type="submit" class="btn btn-outline-success mr-1"  [disabled]="(courseForm.controls['department'].invalid || courseForm.controls['title'].invalid) || !trainingmodulevalid() ">Submit</button>
                              <button type="button" class="btn btn-outline-danger">Reset</button>
                          </div> 
                      </div>
                  </form>
              </div>
          </div>
          <div class="clearfix"></div>
      <!-- Footer -->
      <app-empfooter></app-empfooter>
      <!-- Footer -->
  </div>
  <!-- Right Panel -->
  
  

enter image description here

My code is as follows.

import { Component, OnInit } from '@angular/core';
// import {} from '@angular/forms';
import { Router } from '@angular/router';
import { ApiCallService } from 'src/app/api-call.service';
import { FormBuilder, FormGroup,Validators,FormArray,FormControl } from '@angular/forms';

@Component({
  selector: 'app-addtraining',
  templateUrl: './addtraining.component.html',
  styleUrls: ['./addtraining.component.scss']
})
export class AddtrainingComponent implements OnInit {
  moduleList: any;
  constructor(private route:Router,private apicall:ApiCallService,private fb:FormBuilder){
    }

  courseForm= new FormGroup({ 
    department: new FormControl ('', [Validators.required]),
    title:new FormControl ('', [Validators.required]),
    start_date: new FormControl (),
    end_date: new FormControl (),
    description: new FormControl (),
    assessment_status: new FormControl ("1"),
    TrainingModules:this.fb.array([])
            
  })
 
  

 TrainingModules():FormArray {  
   return this.courseForm.get("TrainingModules") as FormArray ; 
   
 } 
 
 newModule():FormGroup {  
   return this.fb.group({
     modules: new FormControl ('', [Validators.required]),  
     selectSource: new FormControl ('', [Validators.required]),
     path: new FormControl ('', [Validators.required]),
     video: '', 
     audio: '',   
     upl_doc: '', 
     duration: new FormControl ('', [Validators.required])
   });
 }
 trainingModulescon(index: string | number) {
  this.moduleList = this.courseForm.get('TrainingModules') as FormArray;
  const formGroup = this.moduleList.controls[index] as FormGroup;
  return formGroup;
}
trainingmodulevalid(){
  this.moduleList = this.courseForm.get('TrainingModules') as FormArray;
  return (this.moduleList.status=="VALID");
}


 addModule(){
   this.TrainingModules().push(this.newModule()); 
 }  
 removeModule(i:number) {  
   this.TrainingModules().removeAt(i);  
 } 
 validation_modulemessage = {
  path: [{ type: 'required', message: 'path is required' }],
  modules: [{ type: 'required', message: 'Module is required' }],
  selectSource: [{ type: 'required', message: 'Source is required' }],
  duration: [{ type: 'required', message: 'Duration is required' }]
};

 data:any;  
 onsubmit() { 
  this.trainingmodulevalid(); 
  
   this.data=this.courseForm.get("TrainingModules") as FormArray
   // alert(JSON.stringify(this.data.value)); 
   this.apicall.addCourse(this.courseForm.value).subscribe((res)=>{
     alert(JSON.stringify(res)); 
     console.log(JSON.stringify(res));
   })
 } 

 selectsource(event:any){
  var ids=event.target.id;
   if(event.target.value=='1'){
     $('.path_'+ids).removeClass('hide');
     $('.video_'+ids).addClass('hide');
     $('.audio_'+ids).addClass('hide');
     $('.upldoc_'+ids).addClass('hide');
   } else if(event.target.value=='2'){
     $('.video_'+ids).removeClass('hide');
     $('.path_'+ids).addClass('hide');
     $('.audio_'+ids).addClass('hide');
     $('.upldoc_'+ids).addClass('hide');
   } else if(event.target.value=='3'){
     $('.audio_'+ids).removeClass('hide');
     $('.path_'+ids).addClass('hide');
     $('.video_'+ids).addClass('hide');
     $('.upldoc_'+ids).addClass('hide');
   }else {
     $('.upldoc_'+ids).removeClass('hide');
     $('.path_'+ids).addClass('hide');
     $('.video_'+ids).addClass('hide');
     $('.audio_'+ids).addClass('hide');
   }
 }
 get department(){
  return this.courseForm.get('department');
 }
  get title(){
  return this.courseForm.get('title');
 }


 ngOnInit(): void {}
}

Solution

  • I would suggest a custom validator. Your function has to return a null, if the validation turns out positive (not false) and should return an error object, so you can show in on your template.

    export function customValidateLengthArray(): ValidatorFn {
        return (formArray:FormArray):{[error: string]: any} | null => {
          if( formArray.length > 0 ){
          return null;
          }
          else {
            return {error: 'User has to select at least one training module'};
          }
        }
     };
    

    And in your formArray you can use said validator basically like this...

    trainingModules:this.fb.array([],customValidateLengthArray())
    

    After that, you can implement your own logic in your submit method which could basically go like this:

    submit(): void {
       if(this.courseForm.valid) {
       // send your form)
       }
    }
    

    This would cover the programmatical part, now you can also bind the validity of the form in general on your button.

    <button type="submit" class="btn btn-primary" [disabled]="courseForm?.invalid" >Save</button>
    

    I've basically freehanded the code a bit, but that should do the trick.