首页 文章

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

提问于
浏览
1

Update :最初问题出在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

    虽然ControlAccessorValue方法适用于普通的 <input> 元素,但我无法使该指令充当 <ion-input> 指令的 <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;
                }
            }
        }
    }
    
  • 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

相关问题