Immissione di moduli personalizzati angolari 2

Come posso creare un componente personalizzato che funzioni come il tag nativo? Voglio che il mio controllo del modulo personalizzato sia in grado di supportare ngControl, ngForm, [(ngModel)].

Come ho capito, ho bisogno di implementare alcune interfacce per far funzionare il mio controllo dei moduli proprio come quello nativo.

Inoltre, sembra che la direttiva ngForm leghi solo per il tag , è giusto? Come posso affrontarlo?


Lascia che ti spieghi perché ne ho bisogno. Voglio racchiudere diversi elementi di input per renderli in grado di lavorare insieme come un singolo input. C’è un altro modo per affrontarlo? Ancora una volta: voglio fare questo controllo proprio come quello nativo. Validazione, ngForm, ngModel binding bidirezionale e altro.

ps: io uso Typescript.

In realtà, ci sono due cose da implementare:

  • Un componente che fornisce la logica del componente del modulo. Non è un input dato che sarà fornito da ngModel stesso
  • Un ControlValueAccessor personalizzato che implementerà il bridge tra questo componente e ngModel / ngControl

Prendiamo un campione. Voglio implementare un componente che gestisce un elenco di tag per un’azienda. Il componente consentirà di aggiungere e rimuovere tag. Voglio aggiungere una convalida per garantire che l’elenco dei tag non sia vuoto. Lo definirò nel mio componente come descritto di seguito:

 (...) 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: ` 
Name: Tags:
` }) 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] }); } }

Il componente TagsComponent definisce la logica per aggiungere e rimuovere elementi nell’elenco di tags .

 @Component({ selector: 'tags', template: ` 
{{label}}  | 
` }) 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 = ''; } }

Come puoi vedere, non vi è alcun input in questo componente ma uno setValue (il nome non è importante qui). Lo usiamo in seguito per fornire il valore da ngModel al componente. Questo componente definisce un evento da notificare quando lo stato del componente (l’elenco dei tag) viene aggiornato.

Implementiamo ora il collegamento tra questo componente e ngModel / ngControl . Ciò corrisponde a una direttiva che implementa l’interfaccia ControlValueAccessor . Un provider deve essere definito per questo valore accessor contro il token NG_VALUE_ACCESSOR (non dimenticare di utilizzare forwardRef poiché la direttiva viene definita dopo).

La direttiva allegherà un listener di tagsChange all’evento tagsChange dell’host (ovvero il componente su cui è collegata la direttiva, ovvero il TagsComponent ). Il metodo onChange verrà chiamato quando si verifica l’evento. Questo metodo corrisponde a quello registrato da Angular2. In questo modo sarà a conoscenza delle modifiche e aggiornerà di conseguenza il controllo del modulo associato.

writeValue viene chiamato quando il valore associato a ngForm viene aggiornato. Dopo aver iniettato il componente collegato (es. TagComponent), saremo in grado di chiamarlo per passare questo valore (vedere il precedente metodo setValue ).

Non dimenticare di fornire CUSTOM_VALUE_ACCESSOR nei binding della direttiva.

Ecco il codice completo del ControlValueAccessor personalizzato:

 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; } } 

In questo modo quando rimuovo tutti i tags della società, l’attributo valid del controllo companyForm.controls.tags diventa false automaticamente.

Vedi questo articolo (sezione “Componente compatibile con NgModel”) per maggiori dettagli:

Non capisco perché ogni esempio che trovo su internet debba essere così complicato. Quando spieghiamo un nuovo concetto, penso che sia sempre meglio avere l’esempio più semplice e funzionante ansible. L’ho distillato un po ‘:

HTML per modulo esterno utilizzando il componente che implementa ngModel:

 EmailExternal=  

Componente autonomo (nessuna class “accessor” separata – forse mi manca il punto):

 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: ``, 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; } } 

In effetti, ho appena astratto tutte queste cose in una class astratta che ora estendo a tutti i componenti di cui ho bisogno per utilizzare ngModel. Per me questo è un sacco di codice di overhead e boilerplate di cui posso fare a meno.

Modifica: Ecco qui:

 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 }; } 

Ecco un componente che lo utilizza: (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:

 

C’è un esempio in questo collegamento per la versione 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: `
`, 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; } }

Siamo quindi in grado di utilizzare questo controllo personalizzato come segue:

 
Enter data:

L’esempio di Thierry è utile. Ecco le importazioni necessarie per TagValueAccessor da eseguire …

 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'; 

Questo è abbastanza facile da fare con ControlValueAccessor NG_VALUE_ACCESSOR .

È ansible leggere questo articolo per creare un campo personalizzato semplice Crea componente campo di input personalizzato con angular

Puoi anche risolverlo con una direttiva @ViewChild. Ciò fornisce al genitore pieno accesso a tutte le variabili membro e alle funzioni di un bambino iniettato.

Vedi: Come accedere ai campi di input del componente del modulo iniettato

Perché creare un nuovo valore accessor quando è ansible utilizzare il ngModel interno. Ogni volta che crei un componente personalizzato che ha un input [ngModel], stiamo già creando un’istanza di ControlValueAccessor. E questa è l’accessor di cui abbiamo bisogno.

modello:

 

Componente:

 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; } } 

Usare come: