如何为离子输入实现货币输入指令

时间:2017-04-21 21:54:52

标签: angular ionic2 angular2-directives

更新最初虽然问题出在ControlValueAccessor的实现中,但后来确定问题是将ControlValueAccessor应用于子元素。编辑问题以反映。

我想提供一个属性指令,该指令将以“美元”格式显示货币值(例如10.00),但会以美分(例如1000)的形式存储在基础模型中。

<!-- cost = 1000 would result in text input value of 10.00
<input type="text" [(ngModel)]="cost" name="cost" currencyInput>
<!-- or in Ionic 2 -->
<ion-input type="text" [(ngModel)]="cost" name="cost-ionic" currencyInput>

以前在AngularJS 1.x中我会在指令链接函数中使用parse和render,如下所示:

(function() {
    'use strict';

    angular.module('app.directives').directive('ndDollarsToCents', ['$parse', function($parse) {
        return {
            require: 'ngModel',
            link: function(scope, element, attrs, ctrl) {
                var listener = function() {
                    element.val((value/100).toFixed(2));
                };

                ctrl.$parsers.push(function(viewValue) {
                    return Math.round(parseFloat(viewValue) * 100);
                });

                ctrl.$render = function() {
                    element.val((ctrl.$viewValue / 100).toFixed(2));
                };

                element.bind('change', listener);
            }
        };
    }]);
})();

在Ionic 2 / Angular 2中,我使用ControlValueAccessor接口实现了这个:

import { Directive, Renderer, ElementRef, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

const CURRENCY_VALUE_ACCESSOR = {
  provide: NG_VALUE_ACCESSOR,
  useExisting: forwardRef(() => CurrencyInputDirective),
  multi: true
}

@Directive({
    selector: '[currencyInput]',
    host: {
        '(input)': 'handleInput($event.target.value)'
     },
     providers: [ CURRENCY_VALUE_ACCESSOR ]
})
export class CurrencyInputDirective implements ControlValueAccessor, AfterViewInit
{
    onChange = (_: any) => {};
    onTouched = () => {};
    inputElement: HTMLInputElement = null;

    constructor(private renderer: Renderer, private elementRef: ElementRef) {}

    ngAfterViewInit()
    {
        let element = this.elementRef.nativeElement;

        if(element.tagName === 'INPUT')
        {
            this.inputElement = element;
        }
        else
        {
             this.inputElement = element.getElementsByTagName('input')[0];
        }
    }

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

    handleInput(value : string)
    {
        if (value)
        {
            value = String(Math.round(parseFloat(value) * 100));
        }

        this.onChange(value);
    }


    writeValue(value: any): void
    {
        if (value)
        {
            value = (parseInt(value) / 100).toFixed(2);
        }

        this.renderer.setElementProperty(this.inputElement, 'value', value);
    }
}

虽然当应用于离子输入时应用于直接输入元素时这可以正常工作但它不起作用。有没有办法让ControlValueAccessor应用于离子输入的子输入元素?

2 个答案:

答案 0 :(得分:2)

虽然ControlAccessorValue方法适用于普通<input>元素,但我无法使该指令充当<input>指令的<ion-input>子元素的控件访问器(对此感兴趣)解决方案)。

或者,以下指令在应用于<ion-input>或普通<input>时达到了预期的结果:

import { Directive, Renderer, ElementRef, Input, Output, EventEmitter, AfterViewInit } from '@angular/core';

@Directive({
    selector: '[currencyInput]',
    host: {
        '(input)': 'handleInput($event.target.value)'
    },
})
export class CurrencyInputDirective implements AfterViewInit
{

    @Input('currencyInput') currency: number;
    @Output('currencyInputChange') currencyChange: EventEmitter<number> = new EventEmitter<number>();
    inputElement: HTMLInputElement = null;

    constructor(private renderer: Renderer, private el: ElementRef) { }

    ngAfterViewInit()
    {
        let element = this.el.nativeElement;

        if(element.tagName === 'INPUT')
        {
            this.inputElement = element;
        }
        else
        {
            this.inputElement = element.getElementsByTagName('input')[0];
        }

        setTimeout(() => {
            this.renderer.setElementProperty(this.inputElement, 'value', (this.currency/100).toFixed(2));
        }, 150);
    }

    handleInput(value: string)
    {
        let v : number = Math.round(parseFloat(value) * 100)
        this.currencyChange.next(v);
    }
}

然后将其应用于元素,如下所示:

<ion-input type="text" name="measured_cost" [(currencyInput)]="item.cost">

setTimeout需要确保输入字段由CurrencyInputDirective初始化而不是离子(我欢迎更好的选择)。

这样可以正常工作,但它只提供单向流,即如果item.cost在输入元素之外被更改,则它不会反映在输入元素值中。可以使用currencyInput的setter方法解决此问题,如下面这种性能较低的解决方案所示:

import { Directive, Renderer, ElementRef, Input, Output, EventEmitter, AfterViewInit } from '@angular/core';
@Directive({
    selector: '[currencyInput]',
    host: {
        '(input)': 'handleInput($event.target.value)'
    },
})
export class CurrencyInputDirective implements AfterViewInit
{
    _cents: number;
    myUpdate: boolean = false;
    inputElement: HTMLInputElement = null;
    @Input('currencyInput')
    set cents(value: number) {
        if(value !== this._cents)
        {
            this._cents = value;
            this.updateElement();
        }
    }
    @Output('currencyInputChange') currencyChange: EventEmitter<number> = new EventEmitter<number>();

    constructor(private renderer: Renderer, private el: ElementRef) { }

    ngAfterViewInit()
    {
        let element = this.el.nativeElement;

        if(element.tagName === 'INPUT')
        {
            this.inputElement = element;
        }
        else
        {
            this.inputElement = element.getElementsByTagName('input')[0];
        }

        setTimeout(() => {
            this.renderer.setElementProperty(this.inputElement, 'value', (this._cents/100).toFixed(2));
        }, 150);
    }

    handleInput(value: string)
    {
        let v : number = Math.round(parseFloat(value) * 100);
        this.myUpdate = true;
        this.currencyChange.next(v);
    }

    updateElement()
    {
        if(this.inputElement)
        {
            let startPos = this.inputElement.selectionStart;
            let endPos = this.inputElement.selectionEnd;

            this.renderer.setElementProperty(this.inputElement, 'value', (this._cents/100).toFixed(2));
            if(this.myUpdate)
            {
                this.inputElement.setSelectionRange(startPos, endPos);
                this.myUpdate = false;
            }
        }
    }
}

答案 1 :(得分:0)

您无需使用ControlValueAccessor来实现此目的。使用以下代码

import { Directive, HostListener, Renderer2, ElementRef } from '@angular/core';
@Directive({
    selector: '[currency]'
})
export class CurrencyDirective{

    constructor(
        private renderer: Renderer2,
        private el: ElementRef
    ){}

    @HostListener('keyup') onKeyUp() {
      this.el.nativeElement.value=this.el.nativeElement.value/100;
      console.log(this.el.nativeElement.value)
     console.log('some thing key upped')

    }
}

<强> LIVE DEMO