6

I've put together a currency attribute directive on a ReactiveForm FormControl that uses @HostListener on an input event (onKeyDown) to remove all invalid characters (letters and symbols) as they are typed into the input, but allows numbers and decimals. BUT, if you type an invalid character (ie. a) into an empty input field and it gets removed by the directive, the model is not updated.

I've added a plunker setup using the currency directive. Steps to follow to understand my question:

  1. type 123a you don't get an a in the input since no letters are allowed, and the button is disabled since the form is invalid (good)
  2. type 123.456 you don't get a 6 in the input since only 2 decimal places are allowed, and the button is disabled since the form is invalid (good)
  3. type a you don't get a a in the input, BUT the butt is enabled since the model thinks it has an a in it even though the UI doesn't display it (bad)

You can verify the model is not update by clicking the button and looking in the console, which logs this.form.value, and displays { amount: 'a' }. If you type a valid character next the model will only contain that character and a will have been removed. So it is only in this case that the model is not updated properly.

This was an issue easily solved in AngularJS using ngModel validator and parser pipes, the modelValue, $setViewValue, and $render() to update and force AngularJS to run a $digest. How do you do this in Angular?

This is a snippet from my attribute directive that trims out the unwanted characters successfully:

@HostListener('input', ['$event'])
  onKeyDown(event: KeyboardEvent) {
    const input = event.target as HTMLInputElement;

    // Only numbers and decimals
    let trimmed = input.value.replace(/[^\d\.,]+/g, '');

    // Only a single decimal and choose the first one found
    if (trimmed.split('.').length > 2) {
      trimmed = trimmed.replace(/\.([^\.]*)$/, '$1');
    }

    // Cannot start with decimal typed or pasted
    if (trimmed.indexOf('.') === 0) { trimmed = ''; }

    // AngularJS "like" solution would be something like:
    // ngModelCtrl.$setViewValue(trimmed);
    // ngModelCtrl.$render();
    // Angular solution is???

    input.value = trimmed;
}

2 Answers 2

13

So I did figure out a solution for this using NgControl where I injected private ngControl: NgControl then accessed its control property this.ngControl.control.patchValue(newValue);, which updates the input fields model in a ReactiveForm in my onKeyDown event - see plunker

BUT based on the use of smart and dumb components the use of an EventEmitter is actually a better solution that passes up the value to the parent form from the input - plunker (thanks to Todd Motto and his Ultimate Angular courses)

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

Comments

1

With the recent angular 4 release a new directive is introduced to handle these scenarios

<input [name]="fullName" pattern="[a-zA-Z ]*" [(ngModel)]="...">

in the pattern you can specify any regular expression

Docs

Update: Based on comment, if you are trying to restrict the user from entering characters you can use custom Directive.

Use the below code for your directive function

  @HostListener('keydown') onKeydown() {
      let value= this.el.nativeElement.value;
     let key= value.charCodeAt(value.length -1])
     let strippedString ='';
     if(!((key > 64 && key < 91) || (key> 96 && key< 123) || key== 8 || key== 32 || (key>= 48 && key<= 57)){
       strippedString = this.el.nativeElement.value.substring(0,value.length-1)
       this.el.nativeElement.value = strippedString
     }

LIVE DEMO

6 Comments

Thanks for the reply. The pattern validator only says when something is wrong, but I'm preventing the input of the value not just warning that it is incorrect. Pattern has actually been around since ng1 and is also in ng2.
Yah, I stated in the question I'm using an attribute directive and that's where the code snippet using @HostListener I added at the end of the question is sourced.
Hi, I think you'll find my snippet does the stripping of the values from the input. To clarify what I'm asking about I've added a plunker.
I didn't understand what's your question
so you dont want this?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.