【问题标题】: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 的子输入元素?

【问题讨论】:

  • 输入输出??样本
  • 在下面查看我的答案

标签: angular ionic2 angular2-directives


【解决方案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。
    猜你喜欢
    • 2018-09-28
    • 1970-01-01
    • 2014-08-01
    • 2017-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多