3

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

2 Answers 2

2

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

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

28 Comments

Hi @ashot I tried this but not working, after using this when I select any control it blur our the button and when I leave cursor from control it should blue back which is not. I have added the html code in the question, please see.
@VirenderThakur can you please add the buttons as well?
Ok, thanks. Check the link(I have updated the answer)
Hi @VirenderThakur I have updated the answer, and the stackblitz example, let me know if it works for you
Thank you!! @Ashot Aleqsanyan. Glad you responded.
|
2

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

2 Comments

Thanx @Eliseo for this. Quick question these controls are dynamically generated and can be any number what need to add instead of this [0,1,2,3,4,5,6,7,8,9,10,11].map . 1 to 11
you always can generate an array "on-fly" with a variable "cant" and string.repeat. e.g. '.'.repeat(cant).split('') create an array of "." with length "cant". You can also use in .ts iterate=new Array(this.cant);this.iterate.fill('.')`

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.