[javascript] Angular ReactiveForms : 체크 박스 값 배열 생성?

동일한에 바인딩 된 확인란 목록이 주어 졌을 때 단순히 /가 아닌에 formControlName바인딩 된 확인란 값 배열을 어떻게 생성 할 수 있습니까?formControltruefalse

예:

<form [formGroup]="checkboxGroup">
    <input type="checkbox" id="checkbox-1" value="value-1" formControlName="myValues" />
    <input type="checkbox" id="checkbox-2" value="value-2" formControlName="myValues" />
    <input type="checkbox" id="checkbox-3" value="value-2" formControlName="myValues" />
</form>

checkboxGroup.controls['myValues'].value 현재 생산 :

true or false

내가 생산하고 싶은 것 :

['value-1', 'value-2', ...]



답변

silentsod 답변의 도움으로 formBuilder에서 상태 대신 값을 얻는 솔루션을 작성했습니다.

formArray에서 값을 추가하거나 제거하는 메서드를 사용합니다. 나쁜 접근 일 수 있지만 작동합니다!

component.html

<div *ngFor="let choice of checks; let i=index" class="col-md-2">
  <label>
    <input type="checkbox" [value]="choice.value" (change)="onCheckChange($event)">
    {{choice.description}}
  </label>
</div>

component.ts

// For example, an array of choices
public checks: Array<ChoiceClass> = [
  {description: 'descr1', value: 'value1'},
  {description: "descr2", value: 'value2'},
  {description: "descr3", value: 'value3'}
];

initModelForm(): FormGroup{
  return this._fb.group({
    otherControls: [''],
    // The formArray, empty 
    myChoices: new FormArray([]),
  }
}

onCheckChange(event) {
  const formArray: FormArray = this.myForm.get('myChoices') as FormArray;

  /* Selected */
  if(event.target.checked){
    // Add a new control in the arrayForm
    formArray.push(new FormControl(event.target.value));
  }
  /* unselected */
  else{
    // find the unselected element
    let i: number = 0;

    formArray.controls.forEach((ctrl: FormControl) => {
      if(ctrl.value == event.target.value) {
        // Remove the unselected element from the arrayForm
        formArray.removeAt(i);
        return;
      }

      i++;
    });
  }
}

양식을 제출할 때 예를 들어 모델은 다음과 같습니다.

  otherControls : "foo",
  myChoices : ['value1', 'value2']

모델에 이미 확인 된 값이있는 경우 formArray를 채우는 함수 만 빠졌습니다.


답변

https://angular.io/docs/ts/latest/api/forms/index/FormArray-class.html 을 사용하기에 좋은 곳이 있습니다.FormArray

시작하기 위해 우리는 a FormBuilder또는 newing up a 로 컨트롤 배열을 구축 할 것입니다 .FormArray

FormBuilder

this.checkboxGroup = _fb.group({
  myValues: _fb.array([true, false, true])
});

새로운 FormArray

let checkboxArray = new FormArray([
  new FormControl(true),
  new FormControl(false),
  new FormControl(true)]);

this.checkboxGroup = _fb.group({
  myValues: checkboxArray
});

쉽게 할 수 있지만 템플릿을 변경하고 템플릿 엔진이 컨트롤에 바인딩하는 방법을 처리하도록 할 것입니다.

template.html

<form [formGroup]="checkboxGroup">
    <input *ngFor="let control of checkboxGroup.controls['myValues'].controls"
    type="checkbox" id="checkbox-1" value="value-1" [formControl]="control" />
  </form>

여기에 우리의 우리의 세트 온 셈 반복하는 FormControls우리의에서 myValues FormArray각 제어를 위해 우리가 바인딩하고 [formControl]해당 컨트롤에 대신에 FormArray제어 및 <div>{{checkboxGroup.controls['myValues'].value}}</div>생산 true,false,true도 좀 덜 수동 템플릿 구문을하면서.

이 예제를 사용할 수 있습니다. http://plnkr.co/edit/a9OdMAq2YIwQFo7gixbj?p=preview to poke around


답변

체크 박스 정보가 API에서 비동기 적으로 채워지는 경우에도 이전 버전보다 Angular 6에서이 작업을 수행하는 것이 훨씬 쉽습니다.

가장 먼저 깨달아야 할 것은 Angular 6의 keyvalue파이프 덕분에 FormArray더 이상 사용할 필요가 없으며 대신 FormGroup.

먼저 FormBuilder를 생성자에 전달합니다.

constructor(
    private _formBuilder: FormBuilder,
) { }

그런 다음 양식을 초기화하십시오.

ngOnInit() {

    this.form = this._formBuilder.group({
        'checkboxes': this._formBuilder.group({}),
    });

}

우리의 체크 박스 옵션 데이터를 사용할 수있을 때 반복, 그것을 우리는 중첩에 직접 밀어 수 FormGroup라는 이름으로 FormControl숫자가 배열을 검색 인덱스에 의존하지 않고.

const checkboxes = <FormGroup>this.form.get('checkboxes');
options.forEach((option: any) => {
    checkboxes.addControl(option.title, new FormControl(true));
});

마지막으로, 템플릿 keyvalue에서 체크 박스 를 반복하면됩니다 : 추가 없음 let index = i, 체크 박스는 자동으로 알파벳 순서로 표시됩니다. 훨씬 깔끔합니다.

<form [formGroup]="form">

    <h3>Options</h3>

    <div formGroupName="checkboxes">

        <ul>
            <li *ngFor="let item of form.get('checkboxes').value | keyvalue">
                <label>
                    <input type="checkbox" [formControlName]="item.key" [value]="item.value" /> {{ item.key }}
                </label>
            </li>
        </ul>

    </div>

</form>


답변

JSON 형식의 체크 박스 값을 찾는 경우

{ "name": "", "countries": [ { "US": true }, { "Germany": true }, { "France": true } ] }

여기에 전체 예가 있습니다 .

질문에있는 대신 국가 이름을 확인란 값으로 사용하는 것에 대해 사과드립니다. 추가 설명-

양식에 대한 FormGroup 만들기

 createForm() {

    //Form Group for a Hero Form
    this.heroForm = this.fb.group({
      name: '',
      countries: this.fb.array([])
    });

    let countries=['US','Germany','France'];

    this.setCountries(countries);}
 }

각 체크 박스를 체크 박스의 값인 유일한 속성을 가진 객체로 만든 FormGroup이되도록합니다.

 setCountries(countries:string[]) {

    //One Form Group for one country
    const countriesFGs = countries.map(country =>{
            let obj={};obj[country]=true;
            return this.fb.group(obj)
    });

    const countryFormArray = this.fb.array(countriesFGs);
    this.heroForm.setControl('countries', countryFormArray);
  }

확인란의 FormGroups 배열은 부모 Form의 ‘countries’에 대한 컨트롤을 설정하는 데 사용됩니다.

  get countries(): FormArray {
      return this.heroForm.get('countries') as FormArray;
  };

템플릿에서 파이프를 사용하여 확인란 컨트롤의 이름을 가져옵니다.

  <div formArrayName="countries" class="well well-lg">
      <div *ngFor="let country of countries.controls; let i=index" [formGroupName]="i" >
          <div *ngFor="let key of country.controls | mapToKeys" >
              <input type="checkbox" formControlName="{{key.key}}">{{key.key}}
          </div>
      </div>
  </div>


답변

여기에는 반응 형을 사용하여 질문에 완전히 답하는 솔루션이 없으므로 여기에 동일한 솔루션이 있습니다.


요약

StackBlitz 예제와 함께 자세한 설명은 다음과 같습니다.

  1. FormArray확인란에 사용 하고 양식을 초기화합니다.
  2. valueChanges관찰하면 디스플레이 뭔가 있지만, 구성 요소의 다른 매장 뭔가 양식을 할 때 적합합니다. 여기 에서 true/ false값을 원하는 값에 매핑합니다 .
  3. false제출시 값을 필터링하십시오 .
  4. valueChangesObservable 에서 구독을 취소합니다 .

StackBlitz 예제


상해

FormArray를 사용하여 양식 정의

이미 답변에서 언급했듯이 올바른 것으로 표시되었습니다. FormArray데이터를 배열로 가져 오는 것을 선호하는 경우에 사용할 수있는 방법입니다. 따라서 가장 먼저해야 할 일은 양식을 만드는 것입니다.

checkboxGroup: FormGroup;
checkboxes = [{
    name: 'Value 1',
    value: 'value-1'
}, {
    name: 'Value 2',
    value: 'value-2'
}];

this.checkboxGroup = this.fb.group({
    checkboxes: this.fb.array(this.checkboxes.map(x => false))
});

이렇게하면 모든 확인란의 초기 값이로 설정됩니다 false.

다음으로, 이러한 양식 변수를 템플릿에 등록하고 checkboxes배열 ( FormArray확인란 데이터 제외)을 반복하여 템플릿 에 표시해야합니다.

<form [formGroup]="checkboxGroup">
    <ng-container *ngFor="let checkbox of checkboxes; let i = index" formArrayName="checkboxes">
        <input type="checkbox" [formControlName]="i" />{{checkbox.name}}
    </ng-container>
</form>

관찰 가능한 valueChanges 활용

여기에 주어진 답변에서 언급되지 않은 부분이 있습니다. 이와 같은 상황에서 우리가 말한 데이터를 표시하고 싶지만 다른 것으로 저장하고 싶은 상황에서 valueChangesObservable은 매우 유용합니다. 사용하여 valueChanges, 우리는 변화 관찰 가능 checkboxes하고 / 용 로부터 수신 된 값이 원하는 데이터로한다. 체크 박스에 전달 된 진실 값은 체크 된 것으로 표시되고 그 반대의 경우도 마찬가지 이므로 체크 박스의 선택은 변경되지 않습니다 .maptruefalseFormArray

subscription: Subscription;

const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
this.subscription = checkboxControl.valueChanges.subscribe(checkbox => {
    checkboxControl.setValue(
        checkboxControl.value.map((value, i) => value ? this.checkboxes[i].value : false),
        { emitEvent: false }
    );
});

이것은 기본적으로 FormArray값을 원래 checkboxes배열에 매핑하고 value확인란이로 표시된 경우를 반환하고 true그렇지 않으면을 반환합니다 false. 는 emitEvent: false, 설정 이후 여기서 중요한 FormArray이 발생할 수없이 값 valueChanges무한 루프를 만드는 이벤트를 방출합니다. 설정하면 emitEventfalse, 우리는 확인하고 있습니다 valueChanges우리가 여기에 값을 설정할 때 방출하지 않습니다 관찰.

거짓 값 필터링

에서 false값을 직접 필터링 할 수는 없습니다 FormArray. 체크 박스에 바인딩되어 있기 때문에 템플릿이 엉망 이 되기 때문입니다. 따라서 가능한 가장 좋은 해결책은 false제출하는 동안 값 을 필터링 하는 것입니다. 이를 수행하려면 스프레드 연산자를 사용하십시오.

submit() {
    const checkboxControl = (this.checkboxGroup.controls.checkboxes as FormArray);
    const formValue = {
        ...this.checkboxGroup.value,
        checkboxes: checkboxControl.value.filter(value => !!value)
    }
    // Submit formValue here instead of this.checkboxGroup.value as it contains the filtered data
}

이것은 기본적으로 필터링 falsy 으로부터 값을 checkboxes.

valueChanges 구독 취소

마지막으로 구독 취소하는 것을 잊지 마세요. valueChanges

ngOnDestroy() {
    this.subscription.unsubscribe();
}

참고 : 값을 FormArrayin 로 설정할 수없는 특수한 경우가 있습니다 valueChanges. 즉, 확인란 값이 숫자로 설정된 경우 0입니다. 확인란을 선택 FormControl하면을 숫자 0(허위 값)로 설정하고 선택하지 않은 상태로 유지하므로 확인란을 선택할 수없는 것처럼 보입니다 . 숫자 0를 값으로 사용하지 않는 것이 좋지만 필요한 경우 조건부로 0문자열 '0'또는 일반 true과 같은 일부 진실한 값으로 설정 한 다음 제출시 다시 숫자로 변환해야합니다 0.

StackBlitz 예제

StackBlitz는 또한 UI에서 체크 된 것으로 표시되도록 체크 박스에 기본값을 전달할 때를위한 코드를 가지고 있습니다.


답변

TL; DR

  1. FormGroup을 사용하여 확인란 목록을 채우는 것을 선호합니다.
  2. 하나 이상의 체크 박스가 선택되었는지 확인하기위한 맞춤 유효성 검사기 작성
  3. 작업 예 https://stackblitz.com/edit/angular-validate-at-least-one-checkbox-was-selected

이것은 또한 때때로 나를 놀라게했기 때문에 FormArray와 FormGroup 접근 방식을 모두 시도했습니다.

대부분의 경우 확인란 목록이 서버에 채워져 API를 통해 수신했습니다. 그러나 때로는 미리 정의 된 값이있는 정적 확인란 세트가 있습니다. 각 사용 사례에서 해당 FormArray 또는 FormGroup이 사용됩니다.

기본적으로 FormArray의 변형이다 FormGroup. 주요 차이점은 데이터가 배열로 직렬화된다는 것입니다 (FormGroup의 경우 객체로 직렬화되는 것과 반대). 이것은 동적 양식과 같이 그룹 내에 얼마나 많은 컨트롤이 있을지 모르는 경우에 특히 유용 할 수 있습니다.

단순성을 위해 다음과 같은 간단한 제품 생성 양식이 있다고 상상해보십시오.

  • 필수 제품 이름 텍스트 상자 1 개.
  • 선택할 카테고리 목록으로, 적어도 하나를 확인해야합니다. 목록이 서버에서 검색된다고 가정합니다.

먼저 제품 이름 formControl 만있는 양식을 설정합니다. 필수 필드입니다.

this.form = this.formBuilder.group({
    name: ["", Validators.required]
});

범주가 동적으로 렌더링되므로 나중에 데이터가 준비된 후 이러한 데이터를 양식에 추가해야합니다.

this.getCategories().subscribe(categories => {
    this.form.addControl("categoriesFormArr", this.buildCategoryFormArr(categories));
    this.form.addControl("categoriesFormGroup", this.buildCategoryFormGroup(categories));
})

카테고리 목록을 작성하는 방법에는 두 가지가 있습니다.

1. 양식 배열

  buildCategoryFormArr(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormArray {
    const controlArr = categories.map(category => {
      let isSelected = selectedCategoryIds.some(id => id === category.id);
      return this.formBuilder.control(isSelected);
    })
    return this.formBuilder.array(controlArr, atLeastOneCheckboxCheckedValidator())
  }
<div *ngFor="let control of categoriesFormArr?.controls; let i = index" class="checkbox">
  <label><input type="checkbox" [formControl]="control" />
    {{ categories[i]?.title }}
  </label>
</div>

그러면 buildCategoryFormGroupFormArray가 반환됩니다. 또한 선택한 값의 목록을 인수로 사용하므로 데이터 편집을 위해 양식을 재사용하려는 경우 유용 할 수 있습니다. 새 제품 양식을 만들 목적으로 아직 적용 할 수 없습니다.

formArray 값에 액세스하려고 할 때 유의하십시오. 처럼 보일 것 [false, true, true]입니다. 선택한 ID 목록을 얻으려면 목록에서 확인하는 데 약간의 작업이 필요하지만 배열 인덱스를 기반으로합니다. 나에게 좋지는 않지만 작동합니다.

get categoriesFormArraySelectedIds(): string[] {
  return this.categories
  .filter((cat, catIdx) => this.categoriesFormArr.controls.some((control, controlIdx) => catIdx === controlIdx && control.value))
  .map(cat => cat.id);
}

그게 내가 FormGroup그 문제를 사용 하는 이유 입니다.

2. 양식 그룹

formGroup의 다른 점은 키와 양식 컨트롤이 필요한 양식 데이터를 개체로 저장한다는 것입니다. 따라서 키를 categoryId로 설정 한 다음 나중에 검색 할 수있는 것이 좋습니다.

buildCategoryFormGroup(categories: ProductCategory[], selectedCategoryIds: string[] = []): FormGroup {
  let group = this.formBuilder.group({}, {
    validators: atLeastOneCheckboxCheckedValidator()
  });
  categories.forEach(category => {
    let isSelected = selectedCategoryIds.some(id => id === category.id);
    group.addControl(category.id, this.formBuilder.control(isSelected));
  })
  return group;
}
<div *ngFor="let item of categories; let i = index" class="checkbox">
  <label><input type="checkbox" [formControl]="categoriesFormGroup?.controls[item.id]" /> {{ categories[i]?.title }}
  </label>
</div>

양식 그룹의 값은 다음과 같습니다.

{
    "category1": false,
    "category2": true,
    "category3": true,
}

그러나 대부분의 경우 categoryId 목록 만 ["category2", "category3"]. 나는 또한 이러한 데이터를 가져 오기 위해 작성해야합니다. 이 접근 방식은 formArray와 비교할 때 더 좋습니다. 실제로 양식 자체에서 값을 가져올 수 있기 때문입니다.

  get categoriesFormGroupSelectedIds(): string[] {
    let ids: string[] = [];
    for (var key in this.categoriesFormGroup.controls) {
      if (this.categoriesFormGroup.controls[key].value) {
        ids.push(key);
      }
      else {
        ids = ids.filter(id => id !== key);
      }
    }
    return ids;
  }

3. 적어도 하나의 체크 박스를 체크하는 커스텀 유효성 검사기가 선택되었습니다.

적어도 X 개의 체크 박스를 선택하도록 유효성 검사기를 만들었습니다. 기본적으로 하나의 체크 박스에 대해서만 체크합니다.

export function atLeastOneCheckboxCheckedValidator(minRequired = 1): ValidatorFn {
  return function validate(formGroup: FormGroup) {
    let checked = 0;

    Object.keys(formGroup.controls).forEach(key => {
      const control = formGroup.controls[key];

      if (control.value === true) {
        checked++;
      }
    });

    if (checked < minRequired) {
      return {
        requireCheckboxToBeChecked: true,
      };
    }

    return null;
  };
}


답변

클릭 할 때 이벤트를 만든 다음 true 값을 확인란이 나타내는 이름으로 수동으로 변경하면 이름 또는 true가 동일하게 평가되고 true / false 목록 대신 모든 값을 가져올 수 있습니다. 전의:

component.html

<form [formGroup]="customForm" (ngSubmit)="onSubmit()">
    <div class="form-group" *ngFor="let parameter of parameters"> <!--I iterate here to list all my checkboxes -->
        <label class="control-label" for="{{parameter.Title}}"> {{parameter.Title}} </label>
            <div class="checkbox">
              <input
                  type="checkbox"
                  id="{{parameter.Title}}"
                  formControlName="{{parameter.Title}}"
                  (change)="onCheckboxChange($event)"
                  > <!-- ^^THIS^^ is the important part -->
             </div>
      </div>
 </form>

component.ts

onCheckboxChange(event) {
    //We want to get back what the name of the checkbox represents, so I'm intercepting the event and
    //manually changing the value from true to the name of what is being checked.

    //check if the value is true first, if it is then change it to the name of the value
    //this way when it's set to false it will skip over this and make it false, thus unchecking
    //the box
    if(this.customForm.get(event.target.id).value) {
        this.customForm.patchValue({[event.target.id] : event.target.id}); //make sure to have the square brackets
    }
}

이것은 Angular Forms에 의해 이미 true 또는 false로 변경된 후 이벤트를 포착합니다. true 인 경우 이름을 확인란이 나타내는 이름으로 변경합니다. 필요한 경우 true / false를 확인하는 경우에도 true로 평가됩니다. 잘.