giwook giwook - 1 month ago 23
Javascript Question

Creating custom form controls in Angular 2

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() {}
}

Answer

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) => {};

  get selectValue(): any {
    return this._selectValue;
  }
  set selectValue(value: any) {
    if (value !== this._selectValue) {
      this._inputValue = 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.
Comments