Quick explanation: The trick is to add the provider NG_VALUE_ACCESSOR on the decorator of the class and implement ControlValueAccessor . Then we need to define the functions writeValue , registerOnChange and registerOnTouched . The two latter are directly called on creation of the component.
standalone: When set to true, the ngModel will not register itself with its parent form, and acts as if it's not in the form. Defaults to false. If no parent form exists, this option has no effect. updateOn: Defines the event upon which the form control value and validity update.
[(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.
If you really need [(ngModel)]
(which supports ngForm
, unlike [(myProp)]
approach),
I think this link will answer your question:
We need to implement two things to achieve that:
ControlValueAccessor
that will implement the bridge between this component and ngModel
/ 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
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;
}
<div class="my-input">
<input [(ngModel)]="model">
</div>
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();
}
}
Step 1: Add the providers
property below:
@Component({
selector: 'my-cool-element',
templateUrl: './MyCool.component.html',
styleUrls: ['./MyCool.component.css'],
providers: [{ // <================================================ ADD THIS
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCoolComponent),
multi: true
}]
})
Step 2: Implement ControlValueAccessor
:
export class MyCoolComponent implements ControlValueAccessor {
private _value: string;
// Whatever name for this (myValue) you choose here, use it in the .html file.
public get myValue(): string { return this._value }
public set myValue(v: string) {
if (v !== this._value) {
this._value = v;
this.onChange(v);
}
}
constructor() {}
onChange = (_) => { };
onTouched = () => { };
writeValue(value: any): void {
this.myValue = value;
}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
setDisabledState?(isDisabled: boolean): void {
throw new Error("Method not implemented.");
}
}
Step 3: In the html, bind whatever control you want to myValue
:
<my-cool-element [(value)]="myValue">
<!-- ..... -->
</my-cool-element>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With