Maksim Fomin Maksim Fomin - 1 year ago 282
TypeScript Question

Angular 2 custom form input

How can I create custom component which would work just like native

tag? I want to make my custom form control be able to support ngControl, ngForm, [(ngModel)].

As I understand, I need to implement some interfaces to make my own form control work just like native one.

Also, seems like ngForm directive binds only for
tag, is this right? How can i deal with that?

Let me explain why I need this at all. I want to wrap several input elements to make them able to work together as one single input. Is there other way to deal with that?
One more time: I want to make this control just like native one. Validation, ngForm, ngModel two way binding and other. Any ideas?

ps: I use Typescript.

ps: Sorry for bad English :/

Answer Source

In fact, there are two things to implement:

  • A component that provides the logic of your form component. It doesn't an input since it will be provided by ngModel itself
  • A custom ControlValueAccessor that will implement the bridge between this component and ngModel / ngControl

Let's take a sample. I want to implement a component that manages a list of tags for a company. The component will allow to add and remove tags. I want to add a validation to ensure that the tags list isn't empty. I will define it in my component as described below:

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;

  selector: 'company-details',
  directives: [ FormFieldComponent, TagsComponent, TagsValueAccessor ],
  template: `
    <form [ngFormModel]="companyForm">
      Name: <input [(ngModel)]=""
      Tags: <tags [(ngModel)]="company.tags" 
export class DetailsComponent implements OnInit {
  constructor(_builder:FormBuilder) { = new Company('companyid',
            'some name', [ 'tag1', 'tag2' ]);
    this.companyForm ={
       name: ['', Validators.required],
       tags: ['', notEmpty]

The TagsComponent component defines the logic to add and remove elements in the tags list.

  selector: 'tags',
  template: `
    <div *ngIf="tags">
      <span *ngFor="#tag of tags" style="font-size:14px"
         class="label label-default" (click)="removeTag(tag)">
        {{label}} <span class="glyphicon glyphicon-remove"
                        aria-  hidden="true"></span>
      <span style="display:inline-block;">
        <input [(ngModel)]="tagToAdd"
           style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-ok" aria-hidden="true" 
export class TagsComponent {
  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);

  addLabel(label:string) {
    this.tagToAdd = '';

As you can see, there is no input in this component but a setValue one (the name isn't important here). We use it later to provide the value from the ngModel to the component. This component define an event to notify when the state of the component (the tags list) is updated.

Let's implement now the link between this component and ngModel / ngControl. This corresponds to a directive that implements the ControlValueAccessor interface. A provider must be defined for this value accessor against the NG_VALUE_ACCESSOR token (don't forget to use forwardRef since the directive is defined after).

The directive will attach an event listener on the tagsChange event of the host (i.e. the component the directive is attached on, i.e. the TagsComponent). The onChange method will be called when the event occurs. This method corresponds to the one registered by Angular2. This way it will be aware of changes and updates accordingly the associated form control.

The writeValue is called when the value bound in the ngForm is updated. After having injected the component attached on (i.e. TagsComponent), we will be able to call it to pass this value (see the previous setValue method).

Don't forget to provide the CUSTOM_VALUE_ACCESSOR in the bindings of the directive.

Here is the complete code of the custom ControlValueAccessor:

import {TagsComponent} from './app.tags.ngform';

  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => TagsValueAccessor), multi: true}));

  selector: 'tags',
  host: {'(tagsChange)': 'onChange($event)'},
export class TagsValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};

  constructor(private host: TagsComponent) { }

  writeValue(value: any): void {;

  registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
  registerOnTouched(fn: () => void): void { this.onTouched = fn; }

This way when I remove all the tags of the company, the valid attribute of the companyForm.controls.tags control becomes false automatically.

See this article (section "NgModel-compatible component") for more details:

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download