привязка данных к угловому флажку - PullRequest
0 голосов
/ 04 марта 2019

Мне удалось правильно связать данные из 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 return

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

Ответы [ 3 ]

0 голосов
/ 06 марта 2019

«простой» способ - создать FormArray со значениями true / false.см. пример в stackblitz

Обновление : исправить некоторые ошибки

Вы заполняете форму массива данных, используя данные и sservicesOptions

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», а затем указываю, что оно недопустимо, если не отмечена ни одна проверка, и isString, если мы можем вернуть строку, а не массив

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

  }

}
0 голосов
/ 10 марта 2019

Причина этого в том, что вы используете checked, чтобы отметить, какие флажки должны быть отмечены, они не имеют никакого отношения к вашему массиву форм, поэтому, если вы не коснетесь флажков, formarray будет правильно пуст.

Я могу предложить несколько вариантов решения этой проблемы ... также следующие изменения:

функция изменения может быть изменена на это:

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).

Мне нравится использовать геттер в этом случае:

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

Затем, когда вы создадитеизмените отмеченный статус для тех, которые должны быть предварительно выбраны, а также добавьте значения,d будет проверен в вашем массиве форм:

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 внутри функции, чтобы показать, как она вызывается.Это нормально для такой демонстрации, но если у вас большая форма, я бы действительно предостерег для вас использовать это решение.


Был бы третий вариант, чтобыфактически установите все значения в вашем массиве форм, а затем просто переключите логическое значение флажка, но для этого потребуется некоторый рефакторинг кода, который я не знаю, если вы хотите.Но также есть , эта опция тоже.

0 голосов
/ 04 марта 2019

вы забыли установить новое значение формы Array 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);
}
...