3

I'm trying to create a custom form control component for a <select> element (I realize this isn't the most innovative use of creating a custom form control, but this is just for testing purposes). I'm following along with the tutorial @ http://blog.thoughtram.io/angular/2016/07/27/custom-form-controls-in-angular-2.html.

What I'm aiming for is to have the submit button disabled until a value has been selected from the <select-box> component, but I don't think I have the custom form control wired up properly as the value doesn't change when I select a different value nor does the validation work (validation = just a required HTML attribute on the custom component).

See below for what I have so far. Alternatively a plunker is available at http://plnkr.co/edit/TAxDyb8sHg158dXmyfwr?p=preview.

Thanks!

Main component

import {Component, NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {SelectBoxComponent} from "./select-box.component";
import {FormsModule} from "@angular/forms";

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <form #form="ngForm" (ngSubmit)="log(form.value)">
        <select-box name="someValue" [ngModel]="someValue" required></select-box>
        <br>
        <button type="submit" [disabled]="!form.valid">Submit</button>
      </form>
      <br>
      {{ form.value | json }}
    </div>
  `,
})
export class App {
  name:string;
  someValue: any = 1;

  log(str) {
    console.log(str);
  }
}

@NgModule({
  imports: [ BrowserModule, FormsModule ],
  declarations: [ App, SelectBoxComponent ],
  bootstrap: [ App ]
})
export class AppModule {}

Select Box Component

import { Component, forwardRef, Input } from "@angular/core";
import { SelectControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";

@Component({
  selector: "select-box",
  template: `
    <select onchange="onChanged(event.target.value)" [ngModel]="ngModel">
      <option disabled selected value></option>
      <option value="1">1</option>
      <option value="2">2</option>
    </select>
  `,
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => SelectBoxComponent),
      multi: true,
    }
  ],
})

export class SelectBoxComponent implements SelectControlValueAccessor {
  @Input() ngModel: any;

  onChanged(value) {
    this.ngModel = value;
    this.propagateChange(value);
  }

  writeValue(value: any) {
    if (value) this.value = value;
  }

  propagateChange = (_: any) => {};

  registerOnChange(fn) {
    this.propagateChange = fn;
  }

  registerOnTouched() {}
}
3
  • Inline all the needed content for answering your question, don't send us looking for them outside, thanks. Commented Oct 12, 2016 at 22:08
  • 1
    @gdoron sure, just added it to the question Commented Oct 12, 2016 at 22:13
  • 1
    What is window["onChanged"] = this.onChanged.bind(this); supposed to mean? You know that means you can't have two on the page, right? Commented Oct 13, 2016 at 2:11

1 Answer 1

9

Here a fix for your component: http://plnkr.co/edit/69SGnjYGBWhC4tEezc1G?p=preview

<select [(ngModel)]="selectValue">
  <option disabled selected value></option>
  <option value="1">1</option>
  <option value="2">2</option>
</select>

In .ts:

export class SelectBoxComponent implements ControlValueAccessor {
  private _selectValue: any = '';
  private _onTouchedCallback: () => {};
  private _onChangeCallback: (_:any) => {};

  hasValue: boolean;

  get selectValue(): any {
    return this._selectValue;
  }
  set selectValue(value: any) {
    if (value !== this._selectValue) {
      this._selectValue = value;
      this._onChangeCallback(value);
    }

    this.hasValue = (value != null && value.length > 0)
    
     this._onTouchedCallback();

  }


  
  //From ControlValueAccessor interface
  writeValue(value: any) {
    this._selectValue = value;
  }

  //From ControlValueAccessor interface
  registerOnChange(fn: any) {
    this._onChangeCallback = fn;
  }

  //From ControlValueAccessor interface
  registerOnTouched(fn: any) {
    this._onTouchedCallback = fn;
  }
}

In your select component, there are a few mistakes.

  1. you are not binding the model properly. Add getter/setter so it's possible for you to track changes and notify it with this._onChangeCallback and this._onTouchedCallback();
  2. You need to register registerOnTouched event and trigger it. That way, your model can become dirty and your form can detect changes whether valid/invalid.
Sign up to request clarification or add additional context in comments.

1 Comment

I noticed you used ControlValueAccessor as opposed to SelectControlValueAccessor which I had initially. I was under the impression that each type of input should be used with its corresponding value accessor, but perhaps this is wrong or there is some caveat that I'm unaware of?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.