How to implement ngModel on custom elements?
[(ngModel)]="item"
is a shorthand for [ngModel]="item" (ngModelChange)="item = $event"
That means that if you want to add a 2-way bind property to your component, for example
<app-my-control [(myProp)]="value"></app-my-control>
All you need to do in your component is add
@Input()myProp: string;// Output prop name must be Input prop name + 'Change'// Use in your component to write an updated value back out to the parent@Output()myPropChange = new EventEmitter<string>();
The @Input
will handle the write ins and to write a new value back out to the parent, just call this.myPropChange.emit("Awesome")
(You can put the emit in a setter for your property if you just want to make sure it is updated every time the value changes.)
You can read a more detailed explanation of how/why it works here.
If you want to use the name ngModel
(because there are extra directives that bind to elements with ngModel
), or this is for a FormControl
element rather than a component (AKA, for use in an ngForm
), then you will need to play with the ControlValueAccessor
. A detailed explanation for making your own FormControl
and why it works can be read here.
I think this link will answer your question:
We need to implement two things to achieve that:
- A component that provides the logic of your form component. It doesn't need an input since that will be provided by ngModel itself
- A custom
ControlValueAccessor
that will implement the bridge between this component andngModel
/ngControl
The previous link gives you a complete sample...
I implemented the ngModel
one time for input in my shared components and from then I can extend it very simple.
Only two lines of code:
providers: [createCustomInputControlValueAccessor(MyInputComponent)]
extends InputComponent
my-input.component.ts
import { Component, Input } from '@angular/core';import { InputComponent, createCustomInputControlValueAccessor } from '../../../shared/components/input.component';@Component({ selector: 'my-input', templateUrl: './my-input-component.component.html', styleUrls: ['./my-input-component.scss'], providers: [createCustomInputControlValueAccessor(MyInputComponent)]})export class MyInputComponent extends InputComponent { @Input() model: string;}
my-input.component.html
<div class="my-input"> <input [(ngModel)]="model"></div>
input.component.ts
import { Component, forwardRef, ViewChild, ElementRef, OnInit } from '@angular/core';import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';export function createCustomInputControlValueAccessor(extendedInputComponent: any) { return { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => extendedInputComponent), multi: true };}@Component({ template: ''})export class InputComponent implements ControlValueAccessor, OnInit { @ViewChild('input') inputRef: ElementRef; // The internal data model public innerValue: any = ''; // Placeholders for the callbacks which are later provided // by the Control Value Accessor private onChangeCallback: any; // implements ControlValueAccessor interface writeValue(value: any) { if (value !== this.innerValue) { this.innerValue = value; } } // implements ControlValueAccessor interface registerOnChange(fn: any) { this.onChangeCallback = fn; } // implements ControlValueAccessor interface - not used, used for touch input registerOnTouched() { } // change events from the textarea private onChange() { const input = <HTMLInputElement>this.inputRef.nativeElement; // get value from text area const newValue = input.value; // update the form this.onChangeCallback(newValue); } ngOnInit() { const inputElement = <HTMLInputElement>this.inputRef.nativeElement; inputElement.onchange = () => this.onChange(); inputElement.onkeyup = () => this.onChange(); }}