Angular 反应形式的条件验证

conditional validation of Angular reactive forms

我是 angular 的新手,正在研究反应式。

我有一个 html table,通过循环生成控件

我想根据以下情况添加验证

  1. 当此页面加载时,默认情况下应禁用“保存”按钮(这是我通过使用 [disabled]="!myform.valid"
  2. 实现的
  3. 只有当用户在任何文本框或特定行的 select 复选框中输入值时,保存按钮才应启用。 复选框 select 和在文本框中添加值不应允许用户。 复选框应 selected 或者用户可以在任何文本框中输入值。

我试过这个来实现

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

这是我的代码

    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());
}

我知道这是否有点棘手,但仍然没有找到解决方案。这对我很有帮助。

我的component.html代码

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

更新:

理解了整个问题后,我们可以通过为每个表单项添加一个新的验证器来解决它。

这里是复制示例(你可以在 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()] }
    );
  }

辅助函数和验证器


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;
}

最后,在模板中,通过此条件添加禁用属性

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

这里是复制示例 https://stackblitz.com/edit/angular-bootstrap-4-starter-vvimke?file=src/app/app.component.ts

为什么不对任务使用 FormArray?由于您有一个 formArray 并且在 formArray 内部,您应该使用两个函数:

  //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
  }

你的函数 addformFields 变得像

  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()})
  }

验证器就像

  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"}
    }
  }

.html来控制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>

看看,如果,例如你定义了一个 .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);
}

一个stackblitz