Search code examples
angularradio-buttonkendo-ui-angular2

Unable to use number input with radio button


Below shows that the string value works however if my input is a number it doesn't work. Any ideas aside from making a variable a string?

import { Component } from '@angular/core';

@Component({
    selector: 'my-app',
    template: `
    <p>Model: {{model | json}}</p>
    NOT WORKING<br/>
      <input type="radio" name="foo" value="1" [(ngModel)]="model.foo" kendoRadioButton />
      <input type="radio" name="foo" value="2" [(ngModel)]="model.foo" kendoRadioButton />
      <input type="radio" name="foo" value="3" [(ngModel)]="model.foo" kendoRadioButton />

    <br/>
      WORKING<br/>
      <input type="radio" name="bar" value="1" [(ngModel)]="model.bar" kendoRadioButton />
      <input type="radio" name="bar" value="2" [(ngModel)]="model.bar" kendoRadioButton />
      <input type="radio" name="bar" value="3" [(ngModel)]="model.bar" kendoRadioButton />

    `
})
export class AppComponent {
    public model = {
        foo: 3,
        bar: "2"
    };
}

Solution

  • Instead of using [(ngModel)] for two-way binding, you could implement the two-way binding yourself by stringifying the setting, and handling the change events.

    Each input for a numeric type would look like this:

    <input type="radio" name="foo" value="1" kendoRadioButton
      [ngModel]="model.foo.toString()" 
      (ngModelChange)="onFooChanged($event)" />
    

    Your event handler in your component would look like this:

    onFooChanged(value) {
      this.model.foo = parseInt(value);
    }
    

    Without knowing what's going on behind the scenes with [(ngModel)], I assume it is doing a strict equality check for boolean types such as radio buttons.

    DEMO: https://stackblitz.com/edit/angular-w3xwmx

    Creating a reusable input

    Creating an event handler per property would quickly get tedious if you have several of these. Alternatively you could create your own component that wraps this functionality.

    number-radio.input.component.ts

    import { Component, Input, forwardRef, ChangeDetectorRef } from '@angular/core';
    import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
    
    @Component({
      selector: 'number-radio-input',
      templateUrl: './number-radio-input.component.html',
      providers: [
        { provide: NG_VALUE_ACCESSOR, 
          useExisting: forwardRef(() => NumberRadioInputComponent), 
          multi: true }
      ]
    })
    export class NumberRadioInputComponent implements ControlValueAccessor {  
      @Input() name: string;
      @Input() value: string;
    
      model: string;
      private _value: number;
      private onChangeCallback: (value: number) => void = () => { };
    
      onModelChange(): void {
        this._value = parseInt(this.model, 10);
        this.onChangeCallback(this._value);
      }
    
      registerOnChange(fn: (value: number) => void): void {
        this.onChangeCallback = fn;
      }
    
      registerOnTouched(fn: any): void {}
    
      writeValue(val: number): void {
        this._value = val;
        this.model = val ? val.toString() : '';
      }
    }
    

    number-radio-input.component.html

    <input type="radio" [name]="name"
      [value]="value"
      [(ngModel)]="model" (ngModelChange)="onModelChange()" />
    

    component.html

    <number-radio-input [(ngModel)]="model.foo" value="1" name="foo">
    </number-radio-input>
    

    You would need to pass in additional properties if you wanted to configure the input differently, but at least this will allow you to bind to your model directly.