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 जोड़ने का प्रयास करता हूं, तो मुझे एक त्रुटि संदेश मिलता है:

त्रुटि त्रुटि: नाम के साथ प्रपत्र नियंत्रण के लिए कोई मूल्य अभिगमकर्ता: 'सर्वेक्षण टाइप'

मैंने जोड़ने की कोशिश की ngDefaultControl सफलता के बिना । ऐसा लगता है कि कोई इनपुट / चयन नहीं है ... और मुझे नहीं पता कि क्या करना है।

मैं इस फॉर्म में अपने क्लिक को इस रूप में बाँधना चाहूँगा कि जब कोई पूरे कार्ड पर क्लिक करे तो वह मेरे 'टाइप' को 'रूप' में बदल देगा। क्या यह संभव है?


मुझे नहीं पता मेरी बात यह है कि: formControl html में form control के लिए जाता है लेकिन div एक फॉर्म कंट्रोल नहीं है। मैं अपने कार्ड के type.id के साथ tu बाइंड टू माय सर्वे टाइप करूंगा
jbtd

मुझे पता है कि मैं पुराने कोणीय तरीके का उपयोग कर सकता हूं और मेरे चयनित टाइप इसे बाँध सकता है, लेकिन मैं कोणीय 4 से प्रतिक्रियाशील रूप का उपयोग करने और सीखने की कोशिश कर रहा था और न ही इस प्रकार के मामले के साथ फॉर्मकंट्रोल का उपयोग करना जानता हूं।
jbtd

ठीक है, मैं शायद उस मामले को प्रतिक्रियाशील रूप से संभाल नहीं सकता। वैसे भी :) :)
jbtd

मैंने एक उत्तर दिया है कि कैसे उप-घटकों में बड़े रूपों को तोड़ दिया जाए यहाँ stackoverflow.com/a/56375605/2398593 लेकिन यह भी सिर्फ एक कस्टम नियंत्रण मूल्य अभिगमकर्ता के साथ बहुत अच्छी तरह से लागू होता है। इसके अलावा github.com/cloudnc/ngx-sub-form :)
मैक्सिमे

जवाबों:


251

आप formControlNameकेवल उन निर्देशों का उपयोग कर सकते हैं जो लागू होते हैं ControlValueAccessor

इंटरफ़ेस लागू करें

इसलिए, आप जो चाहते हैं, उसे करने के लिए, आपको एक घटक बनाना होगा जो लागू करता है ControlValueAccessor, जिसका अर्थ है निम्नलिखित तीन कार्यों को लागू करना :

  • writeValue (दृश्य में मॉडल से मूल्य लिखने के लिए कोणीय को बताता है)
  • registerOnChange (एक हैंडलर फ़ंक्शन को पंजीकृत करता है जिसे दृश्य बदलने पर कहा जाता है)
  • registerOnTouched (जब घटक को ध्यान केंद्रित किया गया है, यह जानने के लिए उपयोगी एक स्पर्श घटना प्राप्त होने पर हैंडलर को बुलाया जाएगा।

एक प्रदाता पंजीकृत करें

फिर, आपको कोणीय को बताना होगा कि यह निर्देश एक है ControlValueAccessor(इंटरफ़ेस को इसे काट नहीं दिया जाता है क्योंकि यह कोड से छीन लिया जाता है जब टाइपस्क्रिप्ट जावास्क्रिप्ट में संकलित किया जाता है)। आप एक प्रदाता को पंजीकृत करके ऐसा करते हैं ।

प्रदाता को मौजूदा मूल्य प्रदान करना चाहिए NG_VALUE_ACCESSORऔर उसका उपयोग करना चाहिए । आपको forwardRefयहां भी आवश्यकता होगी । ध्यान दें कि NG_VALUE_ACCESSORएक बहु प्रदाता होना चाहिए ।

उदाहरण के लिए, यदि आपके कस्टम निर्देश का नाम MyControlComponent है, तो आपको ऑब्जेक्ट के अंदर निम्नलिखित पंक्तियों के साथ कुछ जोड़ना चाहिए: @Component डेकोरेटर :

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

प्रयोग

आपका घटक उपयोग के लिए तैयार है। साथ टेम्प्लेट संचालित रूपों ,ngModel बंधन अब ठीक से काम करेंगे।

प्रतिक्रियाशील रूपों के साथ , अब आप ठीक से उपयोग कर सकते हैंformControlName और प्रपत्र नियंत्रण अपेक्षित रूप से व्यवहार करेगा।

साधन


72

मुझे लगता है कि आप का उपयोग करना चाहिए formControlName="surveyType"एक पर inputहै और एक पर नहींdiv


हाँ यकीन है, लेकिन मैं नहीं जानता कि कैसे मेरे कार्ड डिव को किसी और चीज़ में बदलना है जो एक html फॉर्म कंट्रोल होगा
jbtd

5
CustomValueAccessor की बात कुछ भी करने के लिए प्रपत्र नियंत्रण जोड़ने के लिए है, यहां तक ​​कि एक div
SoEzPz

4
@SoEzPz हालांकि यह एक बुरा पैटर्न है। आप एक आवरण घटक में इनपुट कार्यक्षमता की नकल करते हैं, मानक HTML-विधियों को स्वयं लागू करते हैं (इस प्रकार मूल रूप से पहिया का फिर से आविष्कार कर रहे हैं और अपना कोड वर्बोज़ बना रहे हैं)। लेकिन 90% मामलों में आप <ng-content>एक रैपर घटक का उपयोग करके अपने इच्छित सभी कार्य को पूरा कर सकते हैं और मूल घटक को परिभाषित formControlsकरते हैं जो <input> <रैपर>
फिल

3

त्रुटि का मतलब है, कि कोणीय को पता नहीं है कि जब आप एक formControlपर डालते हैं तो क्या करना है div। इसे ठीक करने के लिए, आपके पास दो विकल्प हैं।

  1. आप formControlNameएक तत्व पर डालते हैं , जो बॉक्स से बाहर कोणीय द्वारा समर्थित है। उन हैं: input, textareaऔरselect
  2. आप ControlValueAccessorइंटरफ़ेस लागू करें। ऐसा करके, आप कोणीय "अपने नियंत्रण के मूल्य का उपयोग कैसे करें" (इसलिए नाम) बता रहे हैं। या सरल शब्दों में: क्या करें, जब आप 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इंटरफ़ेस लागू किया था
  • बी) सुनिश्चित करता है कि आप ControlValueAccessorइंटरफ़ेस लागू कर रहे हैं
  • ग) यह शायद सबसे भ्रमित करने वाला हिस्सा है। मूल रूप से आप क्या कर रहे हैं, आप अपनी कक्षा की संपत्तियों / विधियों को ओवरराइड करने के लिए कोणीय साधन देते हैं onChangeऔरonTouch रनटाइम के दौरान इसका स्वयं का कार्यान्वयन होता है, जैसे कि आप तब उन कार्यों को कॉल कर सकते हैं। इसलिए इस बिंदु को समझना महत्वपूर्ण है: आपको onChange और onTouch को स्वयं लागू करने की आवश्यकता नहीं है (प्रारंभिक खाली कार्यान्वयन के अलावा)। केवल एक चीज जो आपके (c) के साथ कर रही है, वह यह है कि कोणीय संलग्न करना आपकी कक्षा के लिए स्वयं के कार्य हैं। क्यों? तो तुम तो कर सकते हैं फोनonChange औरonTouch उचित समय पर कोणीय द्वारा प्रदान तरीकों। हम देखेंगे कि यह नीचे कैसे काम करता है।
  • d) हम यह भी देखेंगे कि writeValueजब हम इसे लागू करते हैं तो यह विधि अगले भाग में कैसे काम करती है। मैंने इसे यहां रखा है, इसलिए सभी आवश्यक गुण ControlValueAccessorलागू किए गए हैं और आपका कोड अभी भी संकलित है।

राइटवॉल लागू करें

क्या 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">

आप इसे सीधे इनपुट तत्व पर भी लिख सकते हैं जैसा कि कोणीय डॉक्स में वर्णित है।

अब आप संभाल चुके हैं कि आपके घटक के अंदर क्या होता है जब कुछ बाहर बदलता है। अब दूसरी दिशा को देखते हैं। जब आप अपने घटक के अंदर कुछ परिवर्तन करते हैं तो आप बाहरी दुनिया को कैसे सूचित करते हैं?

OnChange बुला रहा है

अगला कदम मूल घटक को आपके अंदर के परिवर्तनों के बारे में सूचित करना है CustomInputComponent। यह वह जगह है जहाँ ऊपर से (सी) onChangeऔर onTouchकार्य चलन में आते हैं। उन कार्यों को कॉल करके आप अपने घटक के अंदर परिवर्तनों के बारे में बाहर की सूचना दे सकते हैं। मूल्य के परिवर्तनों को बाहर तक प्रचारित करने के लिए, आपको तर्क के रूप में नए मूल्य के साथ onChange को कॉल करने की आवश्यकता है । उदाहरण के लिए, यदि उपयोगकर्ता inputआपके कस्टम घटक में फ़ील्ड में कुछ टाइप करता है , तो आप onChangeअद्यतन मूल्य के साथ कॉल करते हैं:

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

यदि आप कार्यान्वयन (सी) को फिर से ऊपर से देखते हैं, तो आप देखेंगे कि क्या हो रहा है: कोणीय बाध्य इसका स्वयं का कार्यान्वयन है onChange वर्ग की संपत्ति के । यह कार्यान्वयन एक तर्क की अपेक्षा करता है, जो अद्यतन नियंत्रण मूल्य है। अब आप जो कर रहे हैं, आप उस विधि को कॉल कर रहे हैं और इस प्रकार परिवर्तन के बारे में कोणीय को बता सकते हैं। कोणीय अब आगे बढ़ेगा और बाहर के रूप में मूल्य बदल देगा। इस सब में यह महत्वपूर्ण हिस्सा है। आपने एंगुलर को बताया कि कब उसे फॉर्म कंट्रोल और किस वैल्यू पर कॉल करके अपडेट करना चाहिएonChange । आपने इसे "नियंत्रण मूल्य तक पहुंचने" का साधन दिया है।

वैसे: नाम onChange मेरे द्वारा चुना गया है। आप यहां कुछ भी चुन सकते हैं, उदाहरण के लिए propagateChangeया समान। हालाँकि आप इसे नाम देते हैं, यह वही फ़ंक्शन होगा जो एक तर्क लेता है, जो कि कोणीय द्वारा प्रदान किया गया है और जो आपके वर्ग के लिए बाध्य हैregisterOnChange रनटाइम के दौरान विधि है।

फोन पर कॉलिंग

चूंकि फ़ॉर्म नियंत्रण "छुआ जा सकता है", इसलिए आपको अपने कस्टम फॉर्म नियंत्रण को छूने पर समझने के लिए कोणीय को भी साधन देना चाहिए। आप इसे कर सकते हैं, आपने इसे अनुमान लगाया, onTouchफ़ंक्शन को कॉल करके । तो हमारे उदाहरण के लिए, यदि आप यह जानना चाहते हैं कि कोणीय रूप से आउट-ऑफ-द-बॉक्स नियंत्रण के लिए यह कैसे किया जाता है, तो आपको onTouchइनपुट फ़ील्ड के धुंधला होने पर कॉल करना चाहिए :

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

फिर से, onTouchमेरे द्वारा चुना गया एक नाम है, लेकिन यह वास्तविक कार्य क्या है यह कोणीय द्वारा प्रदान किया गया है और यह शून्य तर्क लेता है। जो समझ में आता है, क्योंकि आप केवल कोणीय को बता रहे हैं, कि प्रपत्र नियंत्रण को छू लिया गया है।

यह सब एक साथ डालें

तो जब यह सब एक साथ आता है तो यह कैसा दिखता है? इसे ऐसा दिखना चाहिए:

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

मेरे लिए यह चुनिंदा इनपुट नियंत्रण पर "एकाधिक" विशेषता के कारण था क्योंकि इस प्रकार के नियंत्रण के लिए कोणीय का अलग-अलग मान है।

const countryControl = new FormControl();

और अंदर टेम्पलेट इस तरह का उपयोग करें

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

अधिक विवरण आधिकारिक दस्तावेज़ देखें


"मल्टीपल" के कारण क्या था? मैं नहीं देखता कि आपका कोड कुछ भी कैसे हल करता है, या मूल समस्या क्या थी। आपका कोड सामान्य बुनियादी उपयोग दिखाता है।
लज़ार लुजुबेनोविक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.