Angular4 - Form kontrolü için değer erişimcisi yok


146

Özel bir öğe var:

<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 eklemeye çalıştığımda bir hata iletisi alıyorum:

HATA Hatası: Form denetimi için şu adla değer erişimcisi yok: 'surveyType'

ngDefaultControlBaşarısız eklemeye çalıştım . Görünüşe göre giriş / seçim yok ... ve ne yapacağımı bilmiyorum.

Birisi benim 'türü' formControl içine itmek olurdu tüm kartı tıklatmak için bu formControl benim tıklamayı bağlamak istiyorum. Mümkün mü?


Benim açımdan olduğunu bilmiyorum: html form denetimi için formControl gitmek ama div bir form denetimi değil. Tu Bind my anket yapmak istiyorumTip benim kart div türünü.
jbtd

i eski açısal yolu kullanabilirsiniz ve benim selectedType bağlamak olabilir biliyorum ama ben açısal 4 reaktif formu kullanmaya çalışıyorum ve nasıl bu tür bir durumda formControl kullanmak bilmiyorum.
jbtd

Tamam bu dava belki reaktif bir form tarafından ele olamaz jsut s. Yine de Thx :)
jbtd

Burada büyük formların alt bileşenlere nasıl bölüneceği hakkında bir cevap yaptım stackoverflow.com/a/56375605/2398593 Ancak bu sadece özel bir kontrol değeri erişimcisi ile çok iyi uygulanır. Ayrıca github.com/cloudnc/ngx-sub-form :) adresini ziyaret edin
maxime1992

Yanıtlar:


251

formControlNameYalnızca uygulanan yönergelerde kullanabilirsiniz ControlValueAccessor.

Arayüzü uygulayın

Yani, istediğinizi yapmak için , aşağıdaki üç işlevi uygulamakControlValueAccessor anlamına gelen, uygulayan bir bileşen oluşturmanız gerekir :

  • writeValue (Angular'a modelden görünüme nasıl değer yazılacağını söyler)
  • registerOnChange (görünüm değiştiğinde çağrılan bir işleyici işlevini kaydeder)
  • registerOnTouched (bileşen bir odaklanma olayı aldığında çağrılacak bir işleyiciyi kaydeder, bileşenin odaklanıp odaklanmadığını bilmek için yararlıdır).

Bir sağlayıcı kaydedin

Daha sonra, ControlValueAccessorAngular'a bu direktifin bir olduğunu söylemelisiniz (arayüz, TypeScript JavaScript'e derlendiğinde koddan çıkarıldığı için kesmeyecektir). Bunu bir sağlayıcı kaydederek yaparsınız .

Sağlayıcı mevcut bir değeri sağlamalı NG_VALUE_ACCESSORve kullanmalıdır . forwardRefBurada bir de ihtiyacınız olacak . Bunun NG_VALUE_ACCESSORbir çoklu sağlayıcı olması gerektiğini unutmayın .

Örneğin, özel yönerge MyControlComponent olarak adlandırılmışsa, @Componentdekoratöre iletilen nesnenin içindeki aşağıdaki satırlara bir şey eklemeniz gerekir :

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

kullanım

Bileşeniniz kullanıma hazır. İle şablon odaklı formları , ngModelbağlayıcı artık düzgün çalışacaktır.

İle reaktif formlar , artık düzgün kullanabilirsiniz formControlNameve beklendiği gibi form denetimi davranacaktır.

kaynaklar


72

Kullanabileceğin gerektiğini düşünüyorum formControlName="surveyType", bir de inputa üzerinde değil,div


Evet emin, ama nasıl html form kontrolü olacak başka bir şeye benim kart div açmak için bilmiyorum
jbtd

5
CustomValueAccessor'ın amacı HERHANGİ bir form, hatta bir div için form kontrolü eklemektir
SoEzPz

4
@SoEzPz Bu kötü bir model. Standart HTML yöntemlerini kendiniz yeniden uygulayarak (böylece temelde tekerleği yeniden icat ederek ve kodunuzu ayrıntılı hale getirerek) bir sarmalayıcı bileşeninde Giriş işlevini taklit edersiniz. ancak vakaların% 90'ında <ng-content>, bir sarmalayıcı bileşenini kullanarak istediğiniz her şeyi başarabilir ve tanımlayan ana bileşenin formControls<wirpper>
Phil

3

Hata, Angular'ın bir formControla koyduğunuzda ne yapacağını bilmediği anlamına gelir div. Bunu düzeltmek için iki seçeneğiniz vardır.

  1. formControlNameAçısal tarafından desteklenen bir öğeyi kutunun dışına koyarsınız . Bunlar şunlardır: input, textareave select.
  2. ControlValueAccessorArayüzü uygularsınız . Böylece Angular'a "kontrolünüzün değerine nasıl erişeceğinizi" (dolayısıyla adı) söylüyorsunuz. Veya basit terimlerle: formControlNameBir öğeyi koyduğunuzda, bunun doğal olarak kendisiyle ilişkilendirilmiş bir değeri olmayan ne yapmalı .

Şimdi, ControlValueAccessorarayüzü uygulamak ilk başta biraz göz korkutucu olabilir. Özellikle bununla ilgili çok iyi bir dokümantasyon olmadığından ve kodunuza çok fazla kaynak plakası eklemeniz gerektiğinden. Bunu izlemesi gereken basit adımlarla yıkmaya çalışayım.

Form denetiminizi kendi bileşenine taşıma

Uygulamasını uygulamak için ControlValueAccessoryeni bir bileşen (veya yönerge) oluşturmanız gerekir. Form denetiminizle ilgili kodu oraya taşıyın. Bu şekilde kolayca tekrar kullanılabilir. Bir bileşenin içinde zaten bir kontrole sahip olmak ilk etapta neden olabilir, neden ControlValueAccessorarayüzü uygulamanız gerekir , aksi takdirde özel bileşeninizi Açısal formlarla birlikte kullanamazsınız.

Isıtıcı plakayı kodunuza ekleyin

Uygulama ControlValueAccessorarayüzü burada onunla birlikte Demirbaş var oldukça ayrıntılı geçerli:

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
  }

Peki münferit parçalar ne yapıyor?

  • a) Çalışma zamanında ControlValueAccessorarayüzü uyguladığınızı açısal olarak bilmenizi sağlar
  • b) ControlValueAccessorArayüzü uyguladığınızdan emin olur
  • c) Bu muhtemelen en kafa karıştırıcı kısımdır. Temelde yaptığınız şey, Angular'a sınıf özelliklerinizi / yöntemlerinizi onChangeve onTouchçalışma sırasında kendi uygulamasıyla geçersiz kılma araçlarını verirsiniz, böylece bu işlevleri çağırabilirsiniz. Bu noktayı anlamak önemlidir: onChange ve onTouch'u kendiniz uygulamanız gerekmez (ilk boş uygulama dışında). (C) ile yaptığınız tek şey Angular'ın kendi işlevlerini sınıfınıza eklemesine izin vermektir. Neden? O zaman siz yapabilirsiniz çağırıronChange ve onTouchuygun zamanda açısal tarafından sağlanan yöntemler. Bunun nasıl çalıştığını aşağıda göreceğiz.
  • d) writeValueYöntemin uygulandığında sonraki bölümde nasıl çalıştığını da göreceğiz . Ben buraya koydum, böylece tüm gerekli özellikleri ControlValueAccessoruygulanır ve kod hala derler.

WriteValue uygulayın

Ne writeValueyapar, form denetimi dışarıdan değiştirildiğinde özel bileşeninizin içinde bir şey yapmaktır . Örneğin, özel form denetim bileşeninizi adlandırdıysanız app-custom-inputve bunu üst bileşende şu şekilde kullanıyorsanız:

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

Daha sonra writeValueana bileşeni nasılsa değerini her değiştirdiğinde tetiklenen alır myFormControl. Bu, örneğin formun ( this.form = this.formBuilder.group({myFormControl: ""});) başlatılması sırasında veya formun sıfırlanması sırasında olabilir this.form.reset();.

Form denetiminin değeri dışarıdan değişirse, genellikle yapmak isteyeceğiniz şey, form denetim değerini temsil eden yerel bir değişkene yazmaktır. Örneğin, CustomInputComponentmetin tabanlı bir form denetimi etrafında dönerse, şöyle görünebilir:

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

ve html'de CustomInputComponent:

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

Açısal belgelerde açıklandığı gibi doğrudan giriş öğesine de yazabilirsiniz.

Şimdi dışarıda bir şey değiştiğinde bileşeninizin içinde ne olduğunu ele aldınız. Şimdi diğer yöne bakalım. Bileşeninizin içinde bir şey değiştiğinde dış dünyayı nasıl bilgilendirirsiniz?

Call on onDeğiştir

Bir sonraki adım, ana bileşeni, cihazınızdaki değişiklikler hakkında bilgilendirmektir CustomInputComponent. Burada (c) 'den gelen onChangeve onTouchişlevleri devreye girer. Bu işlevleri çağırarak, bileşeninizdeki değişiklikler hakkında dışarıdan bilgi alabilirsiniz. Değer değişikliklerini dışarıya yaymak için bağımsız değişken olarak onChange öğesini yeni değerle çağırmanız gerekir . Örneğin, kullanıcı inputözel bileşeninizdeki alana bir şey yazarsa, onChangegüncellenmiş değerle çağırırsınız :

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

Uygulamayı (c) yukarıdan tekrar kontrol ederseniz, neler olduğunu göreceksiniz: Açısal, kendi onChangeözelliğinin class özelliğine bağlı olması . Bu uygulama, güncellenmiş denetim değeri olan bir bağımsız değişken bekler. Şu anda yaptığınız şey, bu yöntemi çağırmak ve böylece Angular'a değişiklik hakkında bilgi vermek. Açısal şimdi devam edecek ve dışarıdaki form değerini değiştirecektir. Bütün bunların kilit kısmı budur. Angular'a form denetimini ne zaman güncellemesi gerektiğini ve çağırarak hangi değere sahip olduğunu söyledinizonChange . "Kontrol değerine erişim" için araçlar verdiniz.

Bu arada: İsim onChangebenim tarafımdan seçildi. Burada herhangi bir şey seçebilirsiniz, örneğin propagateChange. Bununla birlikte, bunu adlandırsanız da, Angular tarafından sağlanan ve registerOnChangeçalışma zamanı sırasında yöntem tarafından sınıfınıza bağlı olan, bir bağımsız değişken alan aynı işlev olacaktır .

Dokunulduğunda

Form denetimlerine "dokunabildiğiniz" için, Angular'a özel form denetiminize ne zaman dokunulduğunu anlamanız için araçlar vermelisiniz. onTouchFonksiyonu çağırarak yapabilirsiniz, tahmin ettiniz . Buradaki örneğimiz için, Angular'ın kutudan çıkmış form kontrolleri için bunu nasıl yaptığına uygun kalmak istiyorsanız onTouch, giriş alanı bulanık olduğunda aramalısınız :

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

Yine, onTouchbenim tarafımdan seçilen bir isim, ancak gerçek işlevi Angular tarafından sağlanır ve sıfır argüman alır. Bu, Angular'a izin verdiğiniz için, form kontrolüne dokunulduğunu anlamlıdır.

Hepsini bir araya koy

Peki hepsi bir araya geldiğinde bu nasıl görünüyor? Şöyle görünmelidir:

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

Daha fazla örnek

İç İçe Formlar

Denetim Değeri Erişimcileri iç içe form grupları için doğru araç DEĞİLDİR. Yuvalanmış form grupları için @Input() subformbunun yerine basitçe kullanabilirsiniz . Kontrol Değeri Erişimcileri sarmak içindir controls, değil groups! Yuvalanmış form için bir girdinin nasıl kullanılacağına ilişkin bu örneğe bakın: https://stackblitz.com/edit/angular-nested-forms-input-2

Kaynaklar


-1

Benim için, Angular'ın bu tür kontroller için farklı ValueAccessor'a sahip olması nedeniyle seçme giriş kontrolündeki "çoklu" özelliğinden kaynaklanıyordu.

const countryControl = new FormControl();

Ve şablonun içinde böyle kullanın

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

Daha fazla bilgi için Resmi Dokümanlar


Ne "birden fazla" kaynaklandığını? Kodunuzun bir şeyi nasıl çözdüğünü veya orijinal sorunun ne olduğunu görmüyorum. Kodunuz her zamanki temel kullanımı gösterir.
Lazar Ljubenović
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.