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>

আমি যখন কন্ট্রোলনাম ফর্মটি যুক্ত করার চেষ্টা করি তখন আমি একটি ত্রুটি বার্তা পাই:

ত্রুটি ত্রুটি: নাম সহ ফর্ম নিয়ন্ত্রণের জন্য কোনও মান অ্যাক্সেসর নয়: 'জরিপ টাইপ'

আমি ngDefaultControlসাফল্য ছাড়া যোগ করার চেষ্টা করেছি । দেখে মনে হচ্ছে এটি কোনও ইনপুট / নির্বাচন নেই ... এবং আমি কী করব তা জানি না।

আমি এই ক্লিকটি এই ফর্মকন্ট্রোলের সাথে আবদ্ধ করতে চাই যাতে যাতে কেউ যখন পুরো কার্ডটিতে ক্লিক করেন যা আমার টাইপটিকে ফর্মকন্ট্রোলের দিকে ঠেলে দেবে। এটা কি সম্ভব?


আমি জানি না আমার বক্তব্যটি হ'ল: formControl এইচটিএমএলে ফর্ম নিয়ন্ত্রণের জন্য যান তবে ডিভ কোনও ফর্ম নিয়ন্ত্রণ নয়। আমি চাই আমার জরিপের টাইপটি আমার কার্ড ডিভের টাইপ.আইডির সাথে আবদ্ধ করতে চাই
jbtd

আমি জানি আমি পুরানো কৌণিক উপায়ে ব্যবহার করতে পারি এবং আমার নির্বাচিত টাইপটিকে এটিতে আবদ্ধ করতে পারি তবে আমি কৌনিক 4 থেকে প্রতিক্রিয়াশীল ফর্মটি ব্যবহার করতে এবং শিখতে চেষ্টা করছিলাম এবং এই ধরণের ক্ষেত্রে ফর্মকন্ট্রোল কীভাবে ব্যবহার করতে হবে তা আমি জানি না।
jbtd

ঠিক আছে আমি সম্ভবত এটি একটি প্রতিক্রিয়াশীল ফর্ম দ্বারা কেস পরিচালনা করা যাবে না। যাইহোক যাইহোক :)
jbtd

আমি এখানে stackoverflow.com/a/56375605/2398593 উপকারী উপাদানগুলিতে বিশাল আকারগুলি কীভাবে ভাঙতে পারি তার একটি উত্তর দিয়েছি তবে এটি কেবলমাত্র একটি কাস্টম নিয়ন্ত্রণ মান অ্যাক্সেসরের সাথে খুব ভাল প্রয়োগ হয়। এছাড়াও github.com/cloudnc/ngx-sub-form :) দেখুন
maxime1992

উত্তর:


250

আপনি formControlNameকেবল কার্যকর করে এমন নির্দেশিকাগুলিতেই ব্যবহার করতে পারেন ControlValueAccessor

ইন্টারফেস প্রয়োগ করুন

সুতরাং, আপনি যা করতে চান তা করতে আপনাকে একটি উপাদান তৈরি করতে হবে যা কার্যকর করে ControlValueAccessor, যার অর্থ নিম্নলিখিত তিনটি কার্যকারিতা বাস্তবায়ন করা :

  • writeValue (কৌণিক কীভাবে মডেল থেকে দেখুন মান লিখতে হয় তা বলে)
  • registerOnChange (একটি হ্যান্ডলারের ফাংশন নিবন্ধভুক্ত করে যা ভিউ পরিবর্তিত হলে ডাকা হয়)
  • registerOnTouched (যখন উপাদানটি একটি স্পর্শ ইভেন্ট পেয়ে থাকে তখন কল করার জন্য একটি হ্যান্ডলারকে নিবন্ধভুক্ত করে, উপাদানটি ফোকাস করা হয়েছে কিনা তা জানার জন্য দরকারী)।

একটি সরবরাহকারী নিবন্ধন করুন

তারপরে, আপনাকে কৌনিকটি বলতে হবে যে এই নির্দেশিকা হ'ল ControlValueAccessor(টাইপস্ক্রিপ্টটি যখন জাভাস্ক্রিপ্টে সংকলিত হয় তখন কোড থেকে এটি ছিনিয়ে নেওয়া হয় বলে ইন্টারফেসটি এটি কাটবে না)। আপনি কোনও সরবরাহকারীর নিবন্ধন করে এটি করেন

সরবরাহকারী একটি বিদ্যমান মান প্রদান NG_VALUE_ACCESSORএবং ব্যবহার করা উচিত । আপনার এখানেও দরকার হবে forwardRef। নোট যে একাধিক সরবরাহকারীNG_VALUE_ACCESSOR হতে হবে ।

উদাহরণস্বরূপ, যদি আপনার কাস্টম নির্দেশকে মাইকন্ট্রোলকম্পোন্ট হিসাবে নামকরণ করা হয়, তবে আপনাকে ডেকোরেটারে পাস করা বস্তুর ভিতরে নিম্নলিখিত লাইনগুলির সাথে কিছু যুক্ত করা উচিত @Component:

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

ব্যবহার

আপনার উপাদানটি ব্যবহারের জন্য প্রস্তুত। সঙ্গে টেমপ্লেট চালিত ফর্ম , ngModelবাঁধাই এখন সঠিকভাবে কাজ করবে।

সঙ্গে প্রতিক্রিয়াশীল ফর্ম , আপনি এখন সঠিকভাবে ব্যবহার করতে পারেন formControlNameএবং ফর্ম নিয়ন্ত্রণ আশানুরূপ আচরণ করবে।

সম্পদ


72

আমি মনে করি আপনার একটি ব্যবহার formControlName="surveyType"করা উচিত inputএবং একটি তে নয়div


হ্যাঁ নিশ্চিত, তবে আমি জানি না যে কীভাবে আমার কার্ড
ডিভকে

5
কাস্টমভ্যালুএ্যাকসেসরের পয়েন্টটি হ'ল যে কোনও কিছুতে ফর্ম নিয়ন্ত্রণ যুক্ত করা, এমনকি একটি
ডিভ

4
@ সোয়েজপিজেড এটি যদিও একটি খারাপ প্যাটার্ন। আপনি একটি মোড়কের উপাদানগুলিতে ইনপুট কার্যকারিতা নকল করেন, মানক এইচটিএমএল-পদ্ধতিগুলি নিজেকে পুনরায় প্রয়োগ করেন (এইভাবে মূলত চাকাটি পুনরায় উদ্ভাবন করা এবং আপনার কোডটি ভার্বোজ বানানো)। তবে 90% ক্ষেত্রে আপনি <ng-content>একটি মোড়কের উপাদান ব্যবহার করে যা যা করতে চান তা সম্পাদন করতে পারেন এবং যে প্যারেন্ট উপাদানটি সংজ্ঞায়িত করে formControlsকেবল <রিন্টের মধ্যে <ইনপুট> লাগিয়ে দিন
ফিল

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
  }

তাহলে স্বতন্ত্র অংশগুলি কী করছে?

  • ক) রানঘরের সময় কৌনিকটি জানতে দেয় যে আপনি ControlValueAccessorইন্টারফেসটি প্রয়োগ করেছেন
  • খ) আপনি ControlValueAccessorইন্টারফেস প্রয়োগ করছেন তা নিশ্চিত করে
  • গ) এটি সম্ভবত সবচেয়ে বিভ্রান্তিকর অংশ। মূলত আপনি যা করছেন তা হ'ল, আপনি কৌনিককে আপনার শ্রেণীর বৈশিষ্ট্য / পদ্ধতিগুলি ওভারটাইম করার উপায় দিয়েছিলেন onChangeএবং onTouchরানটাইমের সময় এটির নিজস্ব প্রয়োগ রয়েছে, যাতে আপনি সেই ফাংশনগুলিকে কল করতে পারেন। সুতরাং এই পয়েন্টটি বোঝার জন্য গুরুত্বপূর্ণ: আপনাকে onChange এবং অন টিচ নিজেকে প্রয়োগ করতে হবে না (প্রাথমিক শূন্য বাস্তবায়ন ব্যতীত) 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;
}

এবং এইচটিএমএল এ CustomInputComponent:

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

কৌণিক ডক্সে বর্ণিত হিসাবে আপনি এটি সরাসরি ইনপুট উপাদানটিতেও লিখতে পারেন।

বাইরে যখন কিছু পরিবর্তিত হয় তখন আপনি নিজের উপাদানটির ভিতরে কী ঘটে তা পরিচালনা করেছেন। এখন অন্য দিকটি দেখুন। আপনার উপাদানটির অভ্যন্তরে কিছু পরিবর্তন হলে আপনি কীভাবে বাইরের বিশ্বকে জানান?

কল করে অন চেঞ্জ

পরবর্তী পদক্ষেপটি আপনার অভ্যন্তরের পরিবর্তনের বিষয়ে পিতামাতার উপাদানকে অবহিত করা CustomInputComponent। এই যেখানে onChangeএবং onTouch(গ) থেকে ফাংশন উপরে থেকে খেলার মধ্যে আসা। এই ফাংশনগুলিতে কল করে আপনি আপনার উপাদানগুলির অভ্যন্তরের পরিবর্তনগুলি সম্পর্কে বাইরেকে অবহিত করতে পারেন। বাইরের মানটির পরিবর্তনগুলি প্রচার করার জন্য, আপনাকে আর্গুমেন্ট হিসাবে নতুন মান দিয়ে অন চেঞ্জ করতে হবে । উদাহরণস্বরূপ, যদি ব্যবহারকারী 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>

আরও বিশদ অফিশিয়াল ডক্স রেফ


"একাধিক" কারণে কি হয়েছিল? আমি দেখতে পাচ্ছি না যে আপনার কোড কীভাবে কোনও সমাধান করে, বা আসল সমস্যাটি কী। আপনার কোডটি স্বাভাবিক বুনিয়াদি ব্যবহার দেখায়।
লাজার লুবুবেনভিć
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.