Angular4-양식 제어를위한 값 접근자가 없음


146

사용자 정의 요소가 있습니다.

<div formControlName="surveyType">
  <div *ngFor="let type of surveyTypes"
       (click)="onSelectType(type)"
       [class.selected]="type === selectedType">
    <md-icon>{{ type.icon }}</md-icon>
    <span>{{ type.description }}</span>
  </div>
</div>

formControlName을 추가하려고하면 오류 메시지가 나타납니다.

오류 오류 : 이름이 'surveyType'인 양식 제어에 대한 값 액세서가 없습니다.

나는 추가하려고 ngDefaultControl 성공하지 않고 . 입력 / 선택이 없기 때문에 ... 어떻게 해야할지 모르겠습니다.

누군가가 전체 카드를 클릭 할 때 내 '유형'을 formControl에 넣을 수 있도록 클릭을이 formControl에 바인딩하고 싶습니다. 가능합니까?


내 요점을 모른다 : formControl은 HTML에서 양식 제어로 이동하지만 div는 양식 제어가 아닙니다. 내 설문 조사 유형을 내 카드 div의 type.id와 바인딩하고 싶습니다.
jbtd

나는 오래된 각도 방식을 사용할 수 있고 selectedType을 바인딩 할 수 있지만 각도 4에서 반응 형을 사용하려고 시도 하고이 유형의 경우에 formControl을 사용하는 방법을 모른다.
jbtd 2012 년

좋아, 아마도 그 경우는 반응 형으로 처리 할 수 ​​없을지도 모른다. Thx 어쨌든 :)
jbtd

거대한 양식을 하위 구성 요소로 나누는 방법에 대한 답변을 여기에서 stackoverflow.com/a/56375605/2398593 그러나 사용자 지정 제어 값 액세서만으로도 잘 적용됩니다. 또한 체크 아웃 github.com/cloudnc/ngx-sub-form :
maxime1992

답변:


251

formControlName구현하는 지시문에서만 사용할 수 있습니다ControlValueAccessor .

인터페이스 구현

따라서 원하는 ControlValueAccessor것을 수행하려면 을 구현하는 구성 요소를 만들어야합니다. 즉 , 다음 세 가지 기능을 구현해야합니다 .

  • writeValue (모델에서 뷰로 값을 쓰는 방법을 Angular에게 알려줍니다)
  • registerOnChange (뷰가 변경 될 때 호출되는 핸들러 함수를 등록합니다)
  • registerOnTouched (컴포넌트가 터치 이벤트를 수신 할 때 호출 될 핸들러를 등록하며, 컴포넌트가 포커스되었는지 여부를 아는 데 유용합니다).

공급자 등록

그런 다음 Angular 에이 지시문이 ControlValueAccessor(유형 스크립트가 JavaScript로 컴파일 될 때 코드에서 제거되기 때문에 인터페이스가 잘리지 않을 것입니다)라고 알려 주어야합니다. 공급자등록하면 됩니다.

공급자는 기존 값을 제공 NG_VALUE_ACCESSOR하고 사용해야합니다 . forwardRef여기도 필요합니다 . 참고 NG_VALUE_ACCESSOR해야한다 멀티 제공 .

예를 들어, 사용자 지정 지시문의 이름이 MyControlComponent 인 경우 @Component데코레이터에 전달 된 객체 내부에 다음 줄을 따라 무언가를 추가해야합니다 .

providers: [
  { 
    provide: NG_VALUE_ACCESSOR,
    multi: true,
    useExisting: forwardRef(() => MyControlComponent),
  }
]

용법

구성 요소를 사용할 준비가되었습니다. 함께 템플릿 기반의 형태 , ngModel결합은 정상적으로 작동합니다.

으로 반응 형태 , 당신은 지금 제대로 사용할 수 있습니다 formControlName예상대로 폼 컨트롤이 작동합니다.

자원


72

난 당신이 사용한다고 생각 formControlName="surveyType"inputA의하지div


네 물론입니다.하지만 내 카드 div를 html 양식 컨트롤이 될 다른 것으로 바꾸는 방법을 모르겠습니다
jbtd

5
CustomValueAccessor의 포인트는 무엇이든 폼 컨트롤을 추가 할 경우에도 사업부입니다
SoEzPz

4
@SoEzPz 이것은 좋지 않은 패턴입니다. 래퍼 구성 요소의 입력 기능을 모방하여 표준 HTML 메서드를 직접 구현합니다 (기본적으로 휠을 다시 발명하고 코드를 자세하게 작성). 그러나 90 %의 경우 <ng-content>래퍼 구성 요소 를 사용하여 원하는 모든 것을 달성 하고 정의하는 상위 구성 요소를 formControls<wrapper> 안에 넣을 수 있습니다.
Phil

3

오류는 Angular가에를 입을 때 무엇을 해야할지 모른다는 것을 의미 formControl합니다 div. 이 문제를 해결하려면 두 가지 옵션이 있습니다.

  1. formControlName상자에 Angular가 지원하는 요소를 넣습니다 . 사람들은 다음과 같습니다 input, textarea그리고 select.
  2. ControlValueAccessor인터페이스를 구현합니다 . 그렇게함으로써 Angular에게 "컨트롤 값에 접근하는 방법"(이름)을 알려줍니다. 또는 간단한 용어로 : formControlName요소 를 넣을 때 자연스럽게 관련된 값이없는해야 할 일.

이제 ControlValueAccessor인터페이스를 구현하는 것은 처음에는 다소 어려울 수 있습니다. 특히 이것에 대한 문서가 많지 않기 때문에 코드에 많은 상용구를 추가해야합니다. 따라서 몇 가지 간단한 단계를 통해이를 분석해 보겠습니다.

양식 컨트롤을 자체 구성 요소로 이동

를 구현하려면 ControlValueAccessor새 구성 요소 (또는 지시문)를 작성해야합니다. 폼 컨트롤과 관련된 코드를 이동하십시오. 이와 같이 쉽게 재사용 할 수 있습니다. 구성 요소 내부에 이미 컨트롤이있는 것은 처음부터 ControlValueAccessor인터페이스 를 구현해야하는 이유 일 수 있습니다. 그렇지 않으면 사용자 지정 구성 요소를 각도 형식과 함께 사용할 수 없기 때문입니다.

상용구를 코드에 추가

ControlValueAccessor인터페이스 구현 은 매우 장황합니다. 여기에 함께 제공되는 상용구가 있습니다.

import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // a) copy paste this providers property (adjust the component name in the forward ref)
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // c) copy paste this code
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // d) copy paste this code
  writeValue(input: string) {
    // TODO
  }

개별 부품은 무엇입니까?

  • a) 런타임 중에 ControlValueAccessor인터페이스 를 구현했음을 Angular에 알립니다.
  • b) ControlValueAccessor인터페이스를 구현하고 있는지 확인하십시오
  • c) 아마도 가장 혼란스러운 부분 일 것입니다. 기본적으로 수행중인 작업은 Angular에게 클래스 속성 / 메소드를 재정의하는 수단을 제공 onChange하고 onTouch런타임 동안 자체 구현으로 해당 함수를 호출 할 수 있도록하는 것입니다. 따라서이 점을 이해하는 것이 중요합니다 . 처음 빈 구현 이외의 onChange 및 onTouch를 직접 구현할 필요는 없습니다 . (c)와 함께하는 유일한 일은 Angular가 클래스에 자체 함수를 첨부하도록하는 것입니다. 왜? 당신은 할 수 있습니다 전화onChangeonTouch 적절한 시간에 Angular에서 제공 및 메소드 . 우리는 이것이 아래에서 어떻게 작동하는지 볼 것입니다.
  • d) 우리는이 writeValue메소드가 구현 될 때 다음 섹션에서 어떻게 작동 하는지 볼 것이다. 여기에 넣었으므로 필요한 모든 속성 ControlValueAccessor이 구현되고 코드가 여전히 컴파일됩니다.

writeValue 구현

무엇 writeValue않습니다,하는 폼 컨트롤이 외부에서 변경 될 때, 사용자 정의 컴포넌트 안에 뭔가를 할 . 예를 들어, 사용자 지정 양식 컨트롤 구성 요소의 이름을 지정 app-custom-input하고 다음과 같이 부모 구성 요소에서 사용하는 경우 :

<form [formGroup]="form">
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

그런 다음 writeValue부모 구성 요소가 어떻게 든 값을 변경할 때마다 트리거 myFormControl됩니다. 예를 들어 양식 초기화 중에 this.form = this.formBuilder.group({myFormControl: ""});또는 양식 재설정 중에있을 수 있습니다 this.form.reset();.

양식 컨트롤의 값이 외부에서 변경되는 경우 일반적으로 수행하려는 작업은 양식 컨트롤 값을 나타내는 로컬 변수에 쓰는 것입니다. 예를 들어, CustomInputComponent텍스트 기반 양식 컨트롤을 중심으로 회전 하면 다음과 같이 보일 수 있습니다.

writeValue(input: string) {
  this.input = input;
}

그리고 html에서 CustomInputComponent:

<input type="text"
       [ngModel]="input">

Angular 문서에 설명 된대로 입력 요소에 직접 쓸 수도 있습니다.

이제 외부에서 무언가가 변경 될 때 구성 요소 내부에서 발생하는 작업을 처리했습니다. 이제 다른 방향을 봅시다. 컴포넌트 내부에서 무언가가 변경 될 때 어떻게 외부 세계에 알리나요?

onChange 호출

다음 단계는의 내부 변경 사항을 상위 구성 요소에 알리는 것 CustomInputComponent입니다. 여기 에서 위의 (c) 의 onChangeonTouch기능이 작동합니다. 이러한 함수를 호출하면 구성 요소 내부의 변경 사항을 외부에 알릴 수 있습니다. 값의 변경 사항을 외부로 전파 하려면 인수로 새 값을 사용하여 onChange호출 해야합니다 . 예를 들어, 사용자가 사용자 input정의 컴포넌트 의 필드에 무언가 onChange를 입력하면 업데이트 된 값으로 호출 합니다.

<input type="text"
       [ngModel]="input"
       (ngModelChange)="onChange($event)">

위에서 구현 (c)를 다시 확인하면 무슨 일이 일어나고 있는지 알 수 있습니다 : Angular는 onChange클래스 속성 에 대한 자체 구현 입니다. 이 구현에는 업데이트 된 제어 값인 하나의 인수가 필요합니다. 지금하고있는 일은 해당 메소드를 호출하여 Angular에 변경 사항을 알리는 것입니다. 이제 Angular가 외부의 양식 값을 변경합니다. 이것이이 모든 것의 핵심 부분입니다. Angular에 폼 컨트롤을 업데이트해야 할 때와를 호출하여 어떤 값으로 업데이트해야하는지 알려주었습니다onChange . "제어 값에 액세스"하는 수단을 제공했습니다.

그건 그렇고 : 이름 onChange은 나에 의해 선택됩니다. 예를 들어 propagateChange또는 이와 유사한 것을 여기에서 선택할 수 있습니다 . 그러나 이름을 지정하면 Angular에서 제공하는 하나의 인수를 취하는 동일한 함수가됩니다.registerOnChange 런타임 동안 메소드에 됩니다.

onTouch 호출

폼 컨트롤을 "만질"수 있으므로 Angular는 사용자 지정 폼 컨트롤을 터치 할 때 이해할 수있는 수단을 제공해야합니다. onTouch함수 를 호출하여 짐작할 수 있습니다 . 따라서 여기 예제에서, 즉시 사용 가능한 양식 컨트롤에 대해 Angular가 수행하는 방식을 준수 onTouch하려면 입력 필드가 흐리게 표시 될 때 호출해야합니다 .

<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">

다시, onTouch 내가 선택한 이름이지만 실제 기능은 Angular에서 제공하며 인수가 없습니다. Angular에 알리기 때문에 양식 컨트롤이 터치되었음을 의미합니다.

함께 모아서

그래서 그것이 모두 함께 올 때 어떻게 보입니까? 다음과 같아야합니다.

// custom-input.component.ts
import {Component, OnInit, forwardRef} from '@angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '@angular/forms';


@Component({
  selector: 'app-custom-input',
  templateUrl: './custom-input.component.html',
  styleUrls: ['./custom-input.component.scss'],

  // Step 1: copy paste this providers property
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => CustomInputComponent),
      multi: true
    }
  ]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {

  // Step 3: Copy paste this stuff here
  onChange: any = () => {}
  onTouch: any = () => {}
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }

  // Step 4: Define what should happen in this component, if something changes outside
  input: string;
  writeValue(input: string) {
    this.input = input;
  }

  // Step 5: Handle what should happen on the outside, if something changes on the inside
  // in this simple case, we've handled all of that in the .html
  // a) we've bound to the local variable with ngModel
  // b) we emit to the ouside by calling onChange on ngModelChange

}
// custom-input.component.html
<input type="text"
       [(ngModel)]="input"
       (ngModelChange)="onChange($event)"
       (blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>

// OR

<form [formGroup]="form" >
  <app-custom-input formControlName="myFormControl"></app-custom-input>
</form>

더 많은 예

중첩 양식

컨트롤 값 접근자는 중첩 된 양식 그룹에 적합한 도구가 아닙니다. 중첩 된 양식 그룹의 경우 간단히 @Input() subform대신 사용할 수 있습니다 . 제어 값 액세서는 래퍼가 controls아닌 랩을 의미합니다 groups. 다음 예제는 중첩 양식에 입력을 사용하는 방법을 참조하십시오. https://stackblitz.com/edit/angular-nested-forms-input-2

출처


-1

Angular는이 유형의 제어에 대해 다른 ValueAccessor를 갖기 때문에 선택 입력 제어의 "다중"속성 때문이었습니다.

const countryControl = new FormControl();

그리고 내부 템플릿 사용

    <select multiple name="countries" [formControl]="countryControl">
      <option *ngFor="let country of countries" [ngValue]="country">
       {{ country.name }}
      </option>
    </select>

자세한 내용은 공식 문서를 참조하십시오


"복수"로 인한 것은 무엇입니까 ? 코드가 어떻게 해결되는지, 또는 원래 문제가 무엇인지 알 수 없습니다. 코드는 일반적인 기본 사용법을 보여줍니다.
Lazar Ljubenović
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.