【问题标题】:binding data to angular checkbox将数据绑定到角度复选框
【发布时间】:2019-07-25 19:43:27
【问题描述】:

我设法在 UI 上正确绑定了来自 this.empDetails.services 的数据,复选框被正确选中,并且还列出了所有复选框选项。

但是,数据并没有推送到serviceFormArray,当我点击更新而不更改复选框时,this.updateServicesForm.value 为空。

我必须取消选中那些选中的复选框,然后再次选中它,以便它将推送到formarray

我尝试了一些更改,但无济于事,有人可以建议什么是正确的代码来归档我需要的内容吗?太感谢了。 HTML

<form action="javascript:" [formGroup]="updateSvcForm">
  <div class="row" *ngFor="let service of servicesOptions; let i=index">
    <div class="col-sm-12">
      <div class="checkbox-color checkbox-primary">
        <input type="checkbox" id={{service.value}} [value]="service.value" (change)="onCheckChange($event)" [checked]=isSelected(service.value)>
        <label for={{service.value}}>
          {{service.description}}
        </label>
      </div>
    </div>
  </div>
  <div class="form-group row">
    <label class="col-sm-2"></label>
    <div class="col-sm-10">
      <button class="btn btn-primary m-b-0 ripple light" (click)="updateServices()">Update</button>
    </div>
  </div>
</form>

Component.TS

sservicesOptions = [
  { description: '1. Sweeping', value: 'sweeping' },
  { description: '2. Mopping', value: 'mopping' },
  { description: '3. Windows', value: 'windows' },
  { description: '4. Washing Clothes', value: 'washingclothes' },
];


this.updateSvcForm= this.fb.group({
  sservices: new FormArray([]),
});

onCheckChange(event) {
  const sservicesFormArray: FormArray =
    this.updateSvcForm.get('sservices') as FormArray;


  if (event.target.checked) {
    sservicesFormArray.push(new FormControl(event.target.value));
  }
  else {
    let i: number = 0;
    sservicesFormArray.controls.forEach((ctrl: FormControl) => {
      if (ctrl.value == event.target.value) {
        sservicesFormArray.removeAt(i);
        return;
      }
      i++;
    });
  }
}

isSelected(sserviceOption) {
  return this.empDetails.services.indexOf(serviceOption) >= 0;
}
    console.log(this.updateSvcForm.value);
  }

来自 this.empDetails.services API 的数据返回

sservices: Array(2)
0: "mopping"
1: "washingclothes"
length: 2
__proto__: Array(0)

【问题讨论】:

  • 如何填写this.EmployeeDetails?你能显示更多代码吗
  • 只是一个正常的api返回。返回结果如帖子最后部分所示
  • 你能说明如何从 api 获取它吗?你在 HTML 中使用updateServicesForm 吗? (是updateSvcForm 吗?)
  • 是的,它的 updateSvcForm。 html中的FormGroupName
  • 等待 this.employeeService.getEmployeeDetails(this.Employeeuuid).subscribe((res) => { this.EmployeeDetails = res as Employee[];

标签: javascript angular typescript checkbox


【解决方案1】:

这样做的原因是您使用checked 来标记应选中哪些复选框,它们与您的表单数组没有关联,因此如果您不触摸复选框,表单数组将正确为空。

我可以想出几个选项来解决这个问题...还有以下更改:

更改功能可以改成这样:

onCheckChange(event) {
  if (event.target.checked) {
    this.ssArray.push(this.fb.control(event.target.value));
  }
  else {
   this.ssArray.removeAt(this.ssArray.value.findIndex(x => x === event.target.value))
  }
}

不管你怎么做,你的方法也行:)我也喜欢使用FormBuilder(这里注入为fb)。

我喜欢在这种情况下使用 getter:

get ssArray() {
  return this.updateSvcForm.get('sservices') as FormArray;
}

我能想到的选项:

  1. checked 属性添加到数组sservicesOptions 中的对象
  2. 保留您的 isSelected 函数,但最初将所选选项添加到您的表单数组中

我最喜欢选项 1,因此向对象添加 checked 属性:

servicesOptions = [
  { description: '1. Sweeping', value: 'sweeping', checked: false },
  { description: '2. Mopping', value: 'mopping', checked: false },
  { description: '3. Windows', value: 'windows', checked: false },
  { description: '4. Washing Clothes', value: 'washingclothes', checked: false },
];

然后,当您构建表单时,更改应预选的已检查状态,并将应检查的值添加到您的表单数组中:

constructor(private fb: FormBuilder) {
  this.updateSvcForm = this.fb.group({
    sservices: this.fb.array([]),
  });
  // change the checked status for the checkboxes that should be checked
  this.servicesOptions.map(x => 
    this.empDetails.services.indexOf(x) >= 0 ? x.checked = true : x.checked = false)
  // initially set the selected form controls to the formarray
  this.empDetails.services.map((x) => this.ssArray.push(this.fb.control(x)))
}

然后你可以在模板中添加[checked]="service.checked"

DEMO


选项 2:

保持您的checked 函数与您一样,只需记住将预先选择的值添加到您的表单数组中。我不太喜欢这个选项,因为例如,我们最终会在模板中调用一个函数,这真的不推荐。但无论如何,保持代码和现在一样,只需将初始值添加到 formarray:

this.updateSvcForm = this.fb.group({
  sservices: this.fb.array([]),
});
// add the intial values to the formarray:
this.empDetails.services.map((x) => this.ssArray.push(this.fb.control(x)))

DEMO

我在函数内部添加了一个 console.log,以显示它是如何被调用的。像这样的演示没关系,但如果你有一个大表格,我会真的提醒你使用这个解决方案。


还有第三种选择,将所有值实际设置为表单数组,然后切换复选框的布尔值,但这需要对代码进行一些重构,我不知道你是否愿意。但是也有这个选项。

【讨论】:

    【解决方案2】:

    您忘记设置表单数组sservices 的新值:

    onCheckChange(event) {
      const sservicesFormArray: FormArray =
        this.updateSvcForm.get('sservices') as FormArray;
    
    
      if (event.target.checked) {
        sservicesFormArray.push(new FormControl(event.target.value));
      }
      else {
        let i: number = 0;
        sservicesFormArray.controls.forEach((ctrl: FormControl) => {
          if (ctrl.value == event.target.value) {
            sservicesFormArray.removeAt(i);
            break;
          }
          i++;
        });
      }
      // set the new value of sservices form array
      this.updateSvcForm.setControl('sservices', sservicesFormArray);
    }
    

    【讨论】:

    • 我会尝试,但我怀疑它是否有效。因为这是 onCheckChange 仅在手动选中复选框时才会触发。
    【解决方案3】:

    “简单”的方法是创建一个值为真/假的 FormArray。参见stackblitz中的示例

    更新:更正一些错误

    您使用数据和 sservicesOptions 填充 formArray

    getFormArrayService(data:any[]):FormArray
      {
        //e.g. data=['mopping','washingclothes']
        // return a FormArray and the value will be [false,true,false,true]
        //if data=null, return a FormArray [false,false,false,false]
        return new FormArray(
           this.sservicesOptions.map(x=>new FormControl(data?data.find(dat=>dat==x.value)?true:false:false))
        )
      }
    

    所以,你可以在 ngInit 中制作一些类似

    ngOnInit()
      {
        this.updateSvcForm=new FormGroup({
          sservices:this.getFormArrayService(null)
        })
      }
    

    并在提交表单时,转换值

      submit(updateSvcForm)
      {
          if (updateSvcForm.valid)
          {
              let services:string[]=[];
              updateSvcForm.value.sservices.forEach((x,index)=>
              {
                  if (x)
                     services.push(this.sservicesOptions.value)
              })
              const result={
                  ...updateSvcForm.value, //all value of the form but
                  sservices:services
              }
              console.log(result)
          }
      }
    

    .html 变得像

    <form *ngIf="updateSvcForm" [formGroup]="updateSvcForm" (submit)="submit(updateSvcForm)">
        <div formArrayName="sservices">
          <div *ngFor="let control of updateSvcForm.get('sservices').controls;let i=index">
            <input type="checkbox" [formControlName]="i"/>
            {{sservicesOptions[i].description}}
    
            </div>
          </div>
          <button type="submit">submit</button>
        </form>
        {{updateSvcForm?.value|json}}
    

    customFormControl 的“不那么简单的方法”,请参阅stackblitz 中的示例

    基本上,我们创建了一系列复选框,复选框的每次更改都返回“booleansToProp”。在示例中,我添加了一个属性“required”,如果没有检查,则表示它无效,如果我们可以返回字符串而不是数组,则表示它是字符串

    @Component({
      selector: 'check-box-group',
      template: `
          <ng-container *ngFor="let item of source;let i=index;let last=last">
    
          <div  [ngClass]="last?'form-group':''" class="form-check" >
             <input type="checkbox" class="form-check-input"  id="{{_name+''+i}}"
                  [ngModel]="_selectedItems[i]"
                 (ngModelChange)="setValue($event,i)" (blur)="onTouched()" >
             <label class="form-check-label" for="{{_name+''+i}}">{{item[_col]}}</label>
          </div>
    
          </ng-container>
      `,
      providers: [
        {
          provide: NG_VALUE_ACCESSOR,
          useExisting: forwardRef(() => CheckBoxGroupComponent),
          multi: true
        },
        {
          provide: NG_VALIDATORS,
          useExisting: forwardRef(() => CheckBoxGroupComponent),
          multi: true,
        }
    
      ],
      styles:[`
        .focused {
           outline: black dotted thin;
        }`
      ]
    })
    export class CheckBoxGroupComponent implements ControlValueAccessor {
    
      @Input() 
      set source(value)
      {
        this._source=value;
        //we need to know which column has the "value" and which column has the "text"
        //replace all extrange character else ":" and ","
        let aux=JSON.stringify(value[0]).replace(/[^\w|:|,\s]/gi, '').split(',');
        this._key=aux[0].split(':')[0]
        this._col=aux[1].split(':')[0]
      }
      get source()
      {
        return this._source;
      }
    
      _selectedItems: any[] = [];
      _source;
      _key: string;
      _col: string;
      _name:string="";
      _isString:boolean=false;
      _isRequired:boolean=false;
      onChange;
      onTouched;
    
      constructor(el:ElementRef) { 
        let name=el.nativeElement.getAttribute('name');
        //we store in this._isRequired if the element has an attribute "required"       
         this._isRequired=el.nativeElement.getAttribute('isRequired')!=null?true:false;
        //idem if the element has an attribute "isString" 
    
        this._isString=el.nativeElement.getAttribute('isString')!=null?true:false;
        //Is necesary give a name to the control if there're severals check-box-group
        this._name=name?name:"ck";
    
        }
      writeValue(value: any[]|any): void {
        this._selectedItems = this._isString?
           this.propsToBoolean(value?value.split(','):""):this.propsToBoolean(value);
      }
    
      registerOnChange(fn: any): void {
        this.onChange = fn;
      }
    
      registerOnTouched(fn: any): void {
        this.onTouched = fn;
      }
    
      setDisabledState(isDisabled: boolean): void {
      }
      //setValue is called each time you check/uncheck a checkbox
      //Simple call to this.onChange with the value o the result of the
      //function this.booleanToProps
      setValue(value: boolean, index: number) {
        this._selectedItems[index] = value;
        this.onChange(this._isString?
             this.booleanToProps(this._selectedItems).join(','):
             this.booleanToProps(this._selectedItems));
    
      }
    
      validate(control: AbstractControl): ValidationErrors | null{
        if (!this._isRequired)
          return null;
        if (!this._selectedItems.find(x=>x))
          return {error:"you must select one option at last"}
    
        return null
      }
    
      //we received an array (or a string separated by commas) and
      //return an array of true/false
      propsToBoolean(props): any[] {
        let propsString=props?props.map(x=>''+x):null;
        return props ? this.source.map((x: any) => propsString.indexOf(''+x[this._key]) >= 0)
          : this.source.map(x => false);
    
      }
    
      //we received an array of true/false and return an array with the values
      //or with teh values separated by commas
      booleanToProps(propsBoolean: boolean[]) {
        let props: any[] = [];
        if (propsBoolean) {
          propsBoolean.forEach((item, index) => {
            if (item)
              props.push(this.source[index][this._key])
          })
        }
        return props;
    
      }
    
    }
    

    【讨论】:

    • 我在哪里调用 getFormArrayService?
    • services.push(this.sservicesOptions.value) 这些值从何而来?
    • @Devora,我更新了我的答案并制作了一个 stackbliz。希望对你有帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-08
    • 2018-05-16
    • 1970-01-01
    • 2018-01-03
    • 2011-11-18
    • 2017-04-14
    • 2019-03-30
    相关资源
    最近更新 更多