कोणीय 2 कस्टम फॉर्म इनपुट


93

मैं कस्टम घटक कैसे बना सकता हूं जो मूल <input>टैग की तरह काम करेगा ? मैं अपना कस्टम फ़ॉर्म नियंत्रण ngControl, ngForm, [(ngModel)] का समर्थन करने में सक्षम बनाना चाहता हूं।

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

इसके अलावा, ऐसा लगता है कि ngForm निर्देशक केवल <input>टैग के लिए बांधता है , क्या यह सही है? मैं उससे कैसे निपट सकता हूं?


मुझे समझाएं कि मुझे इसकी आवश्यकता क्यों है। मैं उन्हें एक इनपुट के रूप में एक साथ काम करने में सक्षम बनाने के लिए कई इनपुट तत्वों को लपेटना चाहता हूं। क्या इससे निपटने का कोई और तरीका है? एक बार और: मैं इस नियंत्रण को मूल निवासी की तरह बनाना चाहता हूं। वैधता, एनजीफ़ॉर्म, एनकोडेल दो तरह से बाध्यकारी और अन्य।

ps: मैं टाइपस्क्रिप्ट का उपयोग करता हूं।


1
अधिकांश उत्तर वर्तमान कोणीय संस्करणों के संबंध में पुराने हैं। पर एक नज़र डालें stackoverflow.com/a/41353306/2176962
hgoebl

जवाबों:


84

वास्तव में, लागू करने के लिए दो चीजें हैं:

  • एक घटक जो आपके प्रपत्र घटक का तर्क प्रदान करता है। यह एक इनपुट नहीं है क्योंकि यह ngModelस्वयं द्वारा प्रदान किया जाएगा
  • एक रिवाज ControlValueAccessorजो इस घटक और ngModel/ के बीच पुल को लागू करेगाngControl

चलो एक नमूना लेते हैं। मैं एक घटक को लागू करना चाहता हूं जो किसी कंपनी के लिए टैग की सूची का प्रबंधन करता है। घटक टैग जोड़ने और हटाने की अनुमति देगा। मैं यह सुनिश्चित करने के लिए एक सत्यापन जोड़ना चाहता हूं कि टैग सूची खाली नहीं है। मैं नीचे वर्णित के रूप में इसे अपने घटक में परिभाषित करूंगा:

(...)
import {TagsComponent} from './app.tags.ngform';
import {TagsValueAccessor} from './app.tags.ngform.accessor';

function notEmpty(control) {
  if(control.value == null || control.value.length===0) {
    return {
      notEmpty: true
    }
  }

  return null;
}

@Component({
  selector: 'company-details',
  directives: [ FormFieldComponent, TagsComponent, TagsValueAccessor ],
  template: `
    <form [ngFormModel]="companyForm">
      Name: <input [(ngModel)]="company.name"
         [ngFormControl]="companyForm.controls.name"/>
      Tags: <tags [(ngModel)]="company.tags" 
         [ngFormControl]="companyForm.controls.tags"></tags>
    </form>
  `
})
export class DetailsComponent implements OnInit {
  constructor(_builder:FormBuilder) {
    this.company = new Company('companyid',
            'some name', [ 'tag1', 'tag2' ]);
    this.companyForm = _builder.group({
       name: ['', Validators.required],
       tags: ['', notEmpty]
    });
  }
}

TagsComponentघटक जोड़ सकते हैं और में तत्वों को दूर करने के तर्क को परिभाषित करता है tagsसूची।

@Component({
  selector: 'tags',
  template: `
    <div *ngIf="tags">
      <span *ngFor="#tag of tags" style="font-size:14px"
         class="label label-default" (click)="removeTag(tag)">
        {{label}} <span class="glyphicon glyphicon-remove"
                        aria-  hidden="true"></span>
      </span>
      <span>&nbsp;|&nbsp;</span>
      <span style="display:inline-block;">
        <input [(ngModel)]="tagToAdd"
           style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-ok" aria-hidden="true" 
            (click)="addTag(tagToAdd)"></em>
      </span>
    </div>
  `
})
export class TagsComponent {
  @Output()
  tagsChange: EventEmitter;

  constructor() {
    this.tagsChange = new EventEmitter();
  }

  setValue(value) {
    this.tags = value;
  }

  removeLabel(tag:string) {
    var index = this.tags.indexOf(tag, 0);
    if (index != undefined) {
      this.tags.splice(index, 1);
      this.tagsChange.emit(this.tags);
    }
  }

  addLabel(label:string) {
    this.tags.push(this.tagToAdd);
    this.tagsChange.emit(this.tags);
    this.tagToAdd = '';
  }
}

जैसा कि आप देख सकते हैं, इस घटक में कोई इनपुट नहीं है, लेकिन setValueएक (नाम यहाँ महत्वपूर्ण नहीं है)। हम ngModelघटक से मूल्य प्रदान करने के लिए बाद में इसका उपयोग करते हैं। यह घटक घटक की स्थिति (टैग सूची) अद्यतन होने पर सूचित करने के लिए एक घटना को परिभाषित करता है।

आइए अब इस घटक और ngModel/ के बीच की कड़ी को लागू करें ngControl। यह एक निर्देश के अनुरूप है जो ControlValueAccessorइंटरफ़ेस को लागू करता है। एक प्रदाता को NG_VALUE_ACCESSORटोकन के खिलाफ इस मूल्य अभिगमकर्ता के लिए परिभाषित किया जाना चाहिए ( forwardRefनिर्देश के बाद परिभाषित होने के बाद से उपयोग करना न भूलें )।

निर्देश tagsChangeहोस्ट की घटना पर एक ईवेंट श्रोता संलग्न करेगा (यानी वह घटक जो निर्देश पर संलग्न है, अर्थात TagsComponent)। onChangeजब घटना होती विधि बुलाया जाएगा। यह विधि Angular2 द्वारा पंजीकृत एक से मेल खाती है। इस तरह यह संबंधित प्रपत्र नियंत्रण के अनुसार परिवर्तनों और अद्यतनों से अवगत होगा।

writeValueजब में बंधे मूल्य कहा जाता है ngFormअद्यतन किया जाता है। (अर्थात TagsComponent) पर संलग्न घटक को इंजेक्ट करने के बाद, हम इसे इस मान को पास करने में सक्षम होंगे (पिछली setValueविधि देखें )।

CUSTOM_VALUE_ACCESSORनिर्देश के बाइंडिंग में प्रदान करने के लिए मत भूलना ।

यहाँ कस्टम का पूरा कोड है ControlValueAccessor:

import {TagsComponent} from './app.tags.ngform';

const CUSTOM_VALUE_ACCESSOR = CONST_EXPR(new Provider(
  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));

@Directive({
  selector: 'tags',
  host: {'(tagsChange)': 'onChange($event)'},
  providers: [CUSTOM_VALUE_ACCESSOR]
})
export class TagsValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private host: TagsComponent) { }

  writeValue(value: any): void {
    this.host.setValue(value);
  }

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

इस तरह जब मैं tagsकंपनी के सभी को हटा देता हूं , validतो companyForm.controls.tagsनियंत्रण की विशेषता falseस्वतः हो जाती है ।

अधिक विवरण के लिए यह लेख (अनुभाग "NgModel- संगत घटक") देखें:


धन्यवाद! तुम कमाल हो! आप कैसे सोचते हैं - क्या यह तरीका वास्तव में ठीक है? मेरा मतलब है: इनपुट तत्वों का उपयोग न करें और जैसे: <textfield>, <dropdown>? क्या यह "कोणीय" तरीका है?
Maksim फोमिन

1
मैं कहूंगा कि यदि आप अपने स्वयं के क्षेत्र को फॉर्म (कुछ कस्टम) में लागू करना चाहते हैं, तो इस दृष्टिकोण का उपयोग करें। अन्यथा मूल HTML तत्वों का उपयोग करें। यदि आप इनपुट / टेक्स्टारिया / सेलेक्ट (बूटस्ट्र्रा के साथ उदाहरण के लिए) प्रदर्शित करने के तरीके को संशोधित करना चाहते हैं, तो आप गैर-सामग्री का लाभ उठा सकते हैं। इस उत्तर को देखें: stackoverflow.com/questions/34950950/…
Thierry Templier

3
उपरोक्त कोड गायब है और कुछ विसंगतियां हैं, जैसे 'removeLabel' के बजाय 'removeLabel'। पूरी तरह से काम करने के उदाहरण के लिए यहां देखें । वहाँ प्रारंभिक उदाहरण डालने के लिए थिएरी धन्यवाद!
ब्लू

1
यह पाया, @ कोणीय / आम के बजाय @ कोणीय / रूपों से आयात करता है और यह काम करता है। '@ कोणीय / रूपों' से {NG_VALUE_ACCESSOR, ControlValueAccessor} आयात करें;
कैगटै सिविसी

1
यह लिंक भी मददगार होना चाहिए ..
रिफ्लेक्टर

110

मुझे समझ नहीं आ रहा है कि इंटरनेट पर मुझे मिलने वाला हर उदाहरण इतना जटिल क्यों है। एक नई अवधारणा की व्याख्या करते समय, मुझे लगता है कि सबसे सरल, सबसे अच्छा काम करना संभव है। मैंने इसे थोड़ा सा नीचे कर दिया है:

एनकाउंटर लागू करने वाले घटक का उपयोग करते हुए बाहरी रूप के लिए HTML

EmailExternal=<input [(ngModel)]="email">
<inputfield [(ngModel)]="email"></inputfield>

स्व-निहित घटक (कोई अलग 'एक्सेसर' वर्ग नहीं - शायद मैं इस बिंदु को याद कर रहा हूँ):

import {Component, Provider, forwardRef, Input} from "@angular/core";
import {ControlValueAccessor, NG_VALUE_ACCESSOR, CORE_DIRECTIVES} from "@angular/common";

const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {
    useExisting: forwardRef(() => InputField),
    multi: true
  });

@Component({
  selector : 'inputfield',
  template: `<input [(ngModel)]="value">`,
  directives: [CORE_DIRECTIVES],
  providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class InputField implements ControlValueAccessor {
  private _value: any = '';
  get value(): any { return this._value; };

  set value(v: any) {
    if (v !== this._value) {
      this._value = v;
      this.onChange(v);
    }
  }

    writeValue(value: any) {
      this._value = value;
      this.onChange(value);
    }

    onChange = (_) => {};
    onTouched = () => {};
    registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
    registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

वास्तव में, मैंने इस सब सामान को एक अमूर्त वर्ग के लिए सार कर दिया है, जिसे मैं अब हर घटक के साथ बढ़ाता हूं जिसे मुझे ngModel का उपयोग करने की आवश्यकता है। मेरे लिए यह ओवरहेड और बॉयलरप्लेट कोड का एक टन है जो मैं बिना कर सकता हूं।

संपादित करें: यहाँ यह है:

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

export abstract class AbstractValueAccessor implements ControlValueAccessor {
    _value: any = '';
    get value(): any { return this._value; };
    set value(v: any) {
      if (v !== this._value) {
        this._value = v;
        this.onChange(v);
      }
    }

    writeValue(value: any) {
      this._value = value;
      // warning: comment below if only want to emit on user intervention
      this.onChange(value);
    }

    onChange = (_) => {};
    onTouched = () => {};
    registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
    registerOnTouched(fn: () => void): void { this.onTouched = fn; }
}

export function MakeProvider(type : any){
  return {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => type),
    multi: true
  };
}

यहाँ एक घटक है जो इसका उपयोग करता है: (TS):

import {Component, Input} from "@angular/core";
import {CORE_DIRECTIVES} from "@angular/common";
import {AbstractValueAccessor, MakeProvider} from "../abstractValueAcessor";

@Component({
  selector : 'inputfield',
  template: require('./genericinput.component.ng2.html'),
  directives: [CORE_DIRECTIVES],
  providers: [MakeProvider(InputField)]
})
export class InputField extends AbstractValueAccessor {
  @Input('displaytext') displaytext: string;
  @Input('placeholder') placeholder: string;
}

HTML:

<div class="form-group">
  <label class="control-label" >{{displaytext}}</label>
  <input [(ngModel)]="value" type="text" placeholder="{{placeholder}}" class="form-control input-md">
</div>

1
दिलचस्प बात यह है कि स्वीकार किए गए उत्तर ने आरसी 2 के बाद से काम करना बंद कर दिया है, मैंने इस दृष्टिकोण की कोशिश की और यह काम करता है, यकीन नहीं कि हालांकि क्यों।
3urdoch

1
@ 3urdoch ज़रूर, एक सेकंड
डेविड

6
बनाने के लिए यह नई के साथ काम @angular/formsसिर्फ अद्यतन आयात: import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
ulfryk

6
प्रदाता () Angular2 फाइनल में समर्थित नहीं है। इसके बजाय, MakeProvider () रिटर्न {प्रदान करें: NG_VALUE_ACCESSOR, useExisting: ForwardRef () (> = प्रकार), बहु: true};
डीएसओए

2
Angular2 के फाइनल के बाद से आपको डिफ़ॉल्ट रूप से प्रदान किए जाने के बाद से CORE_DIRECTIVESउन्हें आयात और जोड़ने की आवश्यकता नहीं है @Component। हालांकि, मेरी आईडीई के अनुसार, "व्युत्पन्न वर्गों के लिए कन्स्ट्रक्टर्स में एक 'सुपर' कॉल होना चाहिए।", इसलिए मुझे super();अपने घटक के निर्माता से जोड़ना पड़ा ।
जोसेफ वेबर

16

RC5 संस्करण के लिए इस लिंक में एक उदाहरण है: http://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel

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

const noop = () => {
};

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputComponent),
    multi: true
};

@Component({
    selector: 'custom-input',
    template: `<div class="form-group">
                    <label>
                        <ng-content></ng-content>
                        <input [(ngModel)]="value"
                                class="form-control"
                                (blur)="onBlur()" >
                    </label>
                </div>`,
    providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CustomInputComponent implements ControlValueAccessor {

    //The internal data model
    private innerValue: any = '';

    //Placeholders for the callbacks which are later providesd
    //by the Control Value Accessor
    private onTouchedCallback: () => void = noop;
    private onChangeCallback: (_: any) => void = noop;

    //get accessor
    get value(): any {
        return this.innerValue;
    };

    //set accessor including call the onchange callback
    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    //Set touched on blur
    onBlur() {
        this.onTouchedCallback();
    }

    //From ControlValueAccessor interface
    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }

    //From ControlValueAccessor interface
    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    //From ControlValueAccessor interface
    registerOnTouched(fn: any) {
        this.onTouchedCallback = fn;
    }

}

फिर हम निम्नानुसार इस कस्टम नियंत्रण का उपयोग करने में सक्षम हैं:

<form>
  <custom-input name="someValue"
                [(ngModel)]="dataModel">
    Enter data:
  </custom-input>
</form>

4
हालांकि यह लिंक प्रश्न का उत्तर दे सकता है, लेकिन उत्तर के आवश्यक भागों को शामिल करना और संदर्भ के लिए लिंक प्रदान करना बेहतर है। लिंक-केवल उत्तर अमान्य हो सकते हैं यदि लिंक किए गए पृष्ठ बदल जाते हैं।
मैक्सिमिलियन एस्ट

5

थियरी का उदाहरण सहायक है। यहां वे आयात हैं जो TagsValueAccessor को चलाने के लिए आवश्यक हैं ...

import {Directive, Provider} from 'angular2/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR } from 'angular2/common';
import {CONST_EXPR} from 'angular2/src/facade/lang';
import {forwardRef} from 'angular2/src/core/di';

1

मैंने एक पुस्तकालय लिखा जो इस मामले के लिए कुछ बॉयलरप्लेट को कम करने में मदद करता है s-ng-utils:। कुछ अन्य उत्तर एकल रूप नियंत्रण को लपेटने का उदाहरण दे रहे हैं । का उपयोग करना s-ng-utilsहै कि बहुत बस किया जा सकता है का उपयोग करते हुए WrappedFormControlSuperclass:

@Component({
    template: `
      <!-- any fancy wrapping you want in the template -->
      <input [formControl]="formControl">
    `,
    providers: [provideValueAccessor(StringComponent)],
})
class StringComponent extends WrappedFormControlSuperclass<string> {
  // This looks unnecessary, but is required for Angular to provide `Injector`
  constructor(injector: Injector) {
    super(injector);
  }
}

अपनी पोस्ट में आप उल्लेख करते हैं कि आप एक ही घटक में कई प्रपत्र नियंत्रण लपेटना चाहते हैं। यहां एक पूर्ण उदाहरण दिया गया है FormControlSuperclass

import { Component, Injector } from "@angular/core";
import { FormControlSuperclass, provideValueAccessor } from "s-ng-utils";

interface Location {
  city: string;
  country: string;
}

@Component({
  selector: "app-location",
  template: `
    City:
    <input
      [ngModel]="location.city"
      (ngModelChange)="modifyLocation('city', $event)"
    />
    Country:
    <input
      [ngModel]="location.country"
      (ngModelChange)="modifyLocation('country', $event)"
    />
  `,
  providers: [provideValueAccessor(LocationComponent)],
})
export class LocationComponent extends FormControlSuperclass<Location> {
  location!: Location;

  // This looks unnecessary, but is required for Angular to provide `Injector`
  constructor(injector: Injector) {
    super(injector);
  }

  handleIncomingValue(value: Location) {
    this.location = value;
  }

  modifyLocation<K extends keyof Location>(field: K, value: Location[K]) {
    this.location = { ...this.location, [field]: value };
    this.emitOutgoingValue(this.location);
  }
}

इसके बाद आप उपयोग कर सकते हैं <app-location>के साथ [(ngModel)], [formControl], कस्टम प्रमाणकों - सब कुछ आप बॉक्स से बाहर नियंत्रण के साथ कोणीय समर्थन कर सकते हैं।


-1

आप इसे @ दृश्य निर्देश के साथ भी हल कर सकते हैं। यह माता-पिता को एक इंजेक्शन वाले बच्चे के सभी सदस्य चर और कार्यों तक पूरी पहुंच प्रदान करता है।

देखें: इंजेक्ट किए गए फॉर्म घटक के इनपुट फ़ील्ड तक कैसे पहुंचें


हैक की तरह लगता है :(
realappie

-1

जब आप आंतरिक ngModel का उपयोग कर सकते हैं तो एक नया मान एक्सेसर क्यों बनाएं। जब भी आप एक कस्टम कंपोनेंट बना रहे होते हैं, जिसमें एक इनपुट [ngModel] होता है, हम पहले से ही एक ControlValueAccessor को इंस्टैंट कर रहे हैं। और वह एक्सेसर है जिसकी हमें आवश्यकता है।

टेम्पलेट:

<div class="form-group" [ngClass]="{'has-error' : hasError}">
    <div><label>{{label}}</label></div>
    <input type="text" [placeholder]="placeholder" ngModel [ngClass]="{invalid: (invalid | async)}" [id]="identifier"        name="{{name}}-input" />    
</div>

घटक:

export class MyInputComponent {
    @ViewChild(NgModel) innerNgModel: NgModel;

    constructor(ngModel: NgModel) {
        //First set the valueAccessor of the outerNgModel
        this.outerNgModel.valueAccessor = this.innerNgModel.valueAccessor;

        //Set the innerNgModel to the outerNgModel
        //This will copy all properties like validators, change-events etc.
        this.innerNgModel = this.outerNgModel;
    }
}

इस रूप में उपयोग करें:

<my-input class="col-sm-6" label="First Name" name="firstname" 
    [(ngModel)]="user.name" required 
    minlength="5" maxlength="20"></my-input>

जब से यह होनहार लग रहा है, जब से आप सुपर बुला रहे हैं, एक लापता "फैली हुई है"
डेव नट

1
हाँ, मैंने यहाँ अपना पूरा कोड कॉपी नहीं किया और सुपर () को हटाना भूल गया।
निशांत

9
इसके अलावा, आउटरगनमॉडल कहां से आता है? यह उत्तर बेहतर कोड के साथ पूरा किया जाएगा
डेव नट

Angular.io/docs/ts/latest/api/core/index/… के अनुसार innerNgModelपरिभाषित किया गया हैngAfterViewInit
माटेओ

2
यह बिल्कुल काम नहीं करता है। innerNgModel को कभी भी आरंभिक नहीं किया जाता है, externalNgModel को कभी घोषित नहीं किया जाता है, और निर्माण के लिए दिया गया ngModel कभी उपयोग नहीं किया जाता है।
user2350838

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.