How to implement ngModel on custom elements? How to implement ngModel on custom elements? angular angular

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 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:

  1. providers: [createCustomInputControlValueAccessor(MyInputComponent)]

  2. 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();    }}