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

Posted

技术标签:

【中文标题】如何为离子输入实现货币输入指令【英文标题】:How to implement a currency input directive for ion-input 【发布时间】:2017-09-19 00:55:05 【问题描述】:

更新最初虽然问题出在 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 中,我会在指令链接函数中使用解析和渲染,如下所示:

(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 应用于 ion-input 的子输入元素?

【问题讨论】:

输入输出??样本 在下面查看我的答案 【参考方案1】:

虽然 ControlAccessorValue 方法适用于普通的 &lt;input&gt; 元素,但我无法让指令充当 &lt;ion-input&gt; 指令的 &lt;input&gt; 子元素的控制访问器(对此感兴趣的解决方案)。

或者,以下指令在应用于&lt;ion-input&gt; 或普通&lt;input&gt; 时达到预期结果:

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 而不是 ionic 初始化(我欢迎更好的选择)。

这很好用,但它只提供一种方式流程,即如果 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;
            
        
    

【讨论】:

【参考方案2】:

您无需使用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

【讨论】:

当您在字段中键入时,它并不能完全提供所需的功能,它会从您下方更改值。我所追求的是初始化文本字段的值设置为模型/100 的值,随后当文本字段值直接更改时,模型值设置为文本字段值 * 100。跨度> 我没听懂你。当初始化你需要一些东西并改变你需要一些其他的东西?或聚焦或模糊 模型值将以美分格式从数据库中提取,即 1000。初始化文本字段时,其值将设置为 10.00。从那里,文本字段中的任何按键事件都会将模型值设置为文本字段值乘以 100。

以上是关于如何为离子输入实现货币输入指令的主要内容,如果未能解决你的问题,请参考以下文章

如何为自定义单元格中的动态附加输入字段添加约束

Ionic 4 离子输入只允许数字,限制字母和特殊字符

AngularJS – 如何在 Jasmine 中为输入事件指令编写单元测试

如何为 jquery inputmask 设置 autoUnmask?

如何为角度示意图输入数组类型值

如何为自定义输入迭代器定义指针