3
votes

I'm using a custom directive and custom pipe to do currency formatting on text inputs. It works fine with any kind of direct user input (focus, blur, keydown). However I can't seem to capture the change event when the value is changed dynamically. I also can't find a reliable list of hostlistener events, and don't know of a way to capture any event coming to the input (and thus can't see what event, if any, is happening).

Dynamically, the value is being set with patchValue, and I've set emitEvent to true but this appears to do nothing (I assume it's true by default anyway):

myInput.patchValue({content: currentContent}, { emitEvent: true });

I could rewrite the currency formatting before the content value is set with patchValue, but this goes against reusability.

Here is my directive:

import { Directive, HostListener, ElementRef, OnInit } from '@angular/core';
import { CurrencyPipe } from '../pipes/currency.pipe';

@Directive({
    selector: '[appCurrency]'
})
export class CurrencyDirective implements OnInit {

constructor(
    private elementRef:ElementRef,
    private formatcurrencypipe:CurrencyPipe
) { }

ngOnInit(){
    //this.elementRef.nativeElement.value = this.formatcurrencypipe.transform(this.elementRef.nativeElement.value);
}

@HostListener("change", ["$event.target.value", "$event"]) onChange(value, event) {
    //this.elementRef.nativeElement.value = this.formatcurrencypipe.parse(value);
}

@HostListener("valueChange", ["$event.target.value", "$event"]) onValueChange(value, event) {
    console.log('in onValueChange');
    //doesn't trigger when the value is changed dynamically
}

@HostListener("focus",["$event.target.value","$event"]) onFocus(value,event) {
    console.log('in focus');
    this.elementRef.nativeElement.value = this.formatcurrencypipe.parse(value);
    if(event.which == 9)
    {
        return false;
    }
    this.elementRef.nativeElement.select();
}

@HostListener("blur", ["$event.target.value"]) onBlur(value) {
    console.log('in blur');
    this.elementRef.nativeElement.value = this.formatcurrencypipe.transform(value);
}

@HostListener('keydown', ['$event']) onKeyDown(event) {
    let e = <KeyboardEvent> event;
    console.log('e.keyCode: ', e.keyCode, e.ctrlKey, e.metaKey);
    //delete, backspace, tab, escape, enter, decimal, period, arrow left, arrow right
    if ([46, 8, 9, 27, 13, 110, 190, 37, 39].indexOf(e.keyCode) !== -1
    || (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) //CTRL + A
    || (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) //CTRL + C
    || (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) //CTRL + V
    || (e.keyCode === 88 && (e.ctrlKey || e.metaKey))) {  //CTRL + X
        //do nothing
        return;
    }

    // Check for number
    if ((e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) && (e.keyCode < 96 || e.keyCode > 105)) {
        e.preventDefault();
    }
}
}

I've added the stackblitz here: https://stackblitz.com/edit/angular-tys9cy

2
have you tried <input class="name" (change)="onChange($event)"> ? - NoHoney_k1ll
Just tried it and that doesn't do anything either. I may not have the correct event in hostlistener to capture it. - Katharine Osborne
In your directive you're method is right @HostListener("change", ["$event.target.value", "$event"]) onChange(value, event) {}. In your pipe you should put the transformations in transform() method, it changes automatically. - Gaspar
The transformations are in the transform() method, but whatever event is happening when the value is dynamically changed is not being captured by hostlistener. I don't know if it has something to do with patchValue. - Katharine Osborne
@KatharineOsborne Forked StackBlitz Have a look - Vikas

2 Answers

4
votes

Reactive form instances like FormGroup and FormControl have a valueChanges method that returns an observable that emits the latest values. It does not emit a DOM event.
Solution

Instead of valueChange bind to ngModelChange that will be triggered on both events i.e. when formControl is updated in View or via Model.

@HostListener("ngModelChange", [ "$event"]) onNgModelChange(value) {
         console.log(value)
    this.elementRef.nativeElement.value = this.formatcurrencypipe.transform(value);
}

Working StackBlitz

0
votes

Based on @vikas answer, I modified so that it only updates with ngModelChange when the value is dynamically set and not also when the user is editing (as ngModelChange is too inclusive for what I need):

@HostListener("ngModelChange", [ "$event"]) onNgModelChange(value) {
    //when value changes dynamically
    if (this.elementRef.nativeElement.dataset.isfocused == 'false') {
        console.log('is not focused');
        this.elementRef.nativeElement.value = this.formatcurrencypipe.transform(value);
    } else {
        console.log('is focused');
    }
}

@HostListener("focus",["$event.target.value","$event"]) onFocus(value,event) {
    this.elementRef.nativeElement.dataset.isfocused = true;
    console.log('isfocused: ', this.elementRef.nativeElement.dataset);
    this.elementRef.nativeElement.value = this.formatcurrencypipe.parse(value);
    if(event.which == 9)
    {
        return false;
    }
    this.elementRef.nativeElement.select();
}

@HostListener("blur", ["$event.target.value"]) onBlur(value) {
    this.elementRef.nativeElement.dataset.isfocused = false;
    this.elementRef.nativeElement.value = this.formatcurrencypipe.transform(value);
}

And the input:

<input appCurrency data-isfocused="false" type="text" class="form-control number" formControlName="myInput" />

It seems horridly un-angular to use a data-attribute :-(