3

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

12
  • have you tried <input class="name" (change)="onChange($event)"> ? Commented Oct 2, 2018 at 13:53
  • Just tried it and that doesn't do anything either. I may not have the correct event in hostlistener to capture it. Commented Oct 2, 2018 at 13:58
  • 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. Commented Oct 2, 2018 at 14:02
  • 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. Commented Oct 2, 2018 at 14:04
  • 1
    @KatharineOsborne Forked StackBlitz Have a look Commented Oct 2, 2018 at 15:49

2 Answers 2

5

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

Sign up to request clarification or add additional context in comments.

4 Comments

ngModelChange is firing after keydown so you can't type more than one key before it transforms again.
@KatharineOsborne you can use Rxjs debounceTime for it give it a try if you are not able to figure out the solution I will help u out
Yeah I can't figure out how to use debounceTime. I'm not sure it's appropriate either as the user may pause before finishing typing (there's a lot of mental math they may need to do in the context of the project). I set a data value to capture the focus state, and while it's doing that, I can no longer apply the pipe correctly. Anyway, it feels very un-angular.
OMG I just realised I had set my focus flag to true on blur as well as on focus facepalm
0

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 :-(

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.