0
votes

I am trying to make a custom form input in Angular 4. This is a simple example, but I cannot get the component to bind to the ngModel. I can pass this component a default value from the parent controller, but when I change a value in this input, the UI registers the change, but the model in the controller is not showing the new value.

Here is my custom component:

import { Component, Input, forwardRef, Output, EventEmitter, ElementRef, Renderer } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl } from '@angular/forms';

@Component({
    selector: 'input-test2',
    template:
        `
           <input type="text" class="form-control" placeholder="Symbol"  [(ngModel)]="value" [maxlength]="5" >     
        `,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => InputTestComponent),
            multi: true
        },
    ]
})
export class InputTestComponent implements ControlValueAccessor {
    @Input('value') value = false;
    
    private propagateChange = (_: any) => { };
    private propagateTouch = (_: any) => { };
    
    writeValue(value: any): void {
        if (value) {
            this.value = value;
        }
    }
    
    registerOnChange(fn: (_: any) => void): void {
        this.propagateChange = fn;
    }
    registerOnTouched(fn: () => void): void {
        this.propagateTouch = fn;
    }
    
    private onChange(event : any) {
        this.propagateChange(this.value);
    }

And my usage of the component: .. inside a form

<input-test2 id="coin" name="coin" [(ngModel)]="manualEntry.coin" #coin="ngModel" required (change)="x($event)" > </input-test2>

Does anyone have a simple example of how to do this custom form input? All examples I found are either complex and leave out functionality. I just want a default input type="text" to start with and then I can add to this.

1
You never call propagateChange and propagateTouch. You must call the former every time the value changes, and the latter when the input is blurred. You should also handle the disabled state. - JB Nizet
you must add (input)="onChange() in the template of the custom form control - Eliseo
blur and change seem to trigger at the same time, I cannot get one to trigger without the other, is this expected? - a2ron44

1 Answers

0
votes

Thanks for the comments above. Now working as expected. This seems to be a good snippet that provides a basic implementation of input type="text":

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

@Component({
    selector: 'input-test2',
    template:
        `
           <input type="text" class="form-control" placeholder="Symbol"  [(ngModel)]="value" [maxlength]="5" (change)="onChange($event)" [disabled]="isDisabled" >     
        `,
    providers: [
        {
            provide: NG_VALUE_ACCESSOR,
            useExisting: forwardRef(() => InputTestComponent),
            multi: true
        },
    ]
})
export class InputTestComponent implements ControlValueAccessor {
    @Input('value') value = false;
    
    private propagateChange = (_: any) => { };
    private propagateTouch = (_: any) => { };
    private isDisabled: boolean = false;
    
    constructor(){

    }
    writeValue(value: any): void {
        if (value) {
            this.value = value;
        }
    }
    
    registerOnChange(fn: (_: any) => void): void {
        this.propagateChange = fn;
    }
    registerOnTouched(fn: () => void): void {
        this.propagateTouch = fn;
    }
    
    private onChange(event : any) {
        this.propagateChange(this.value);
    }

    private onTouch(event : any){
        this.propagateTouch(event);
    }

    setDisabledState(isDisabled: boolean): void {
        this.isDisabled = isDisabled;
      }



}

And usage of this component can be used in a form like this:

<input-test2 
       type="text"  
       id="coin" name="coin"  
       [(ngModel)]="manualEntry.coin"  
       #coin="ngModel" 
       required [disabled]="false" 
       (change)="test($event)">
</input-test2>

I wish there were better instructions on this as I feel like this is a fairly common practice to create custom/reusable components in forms.