Angular 2, difficulties with data validation and input mask
Asked Answered
F

2

6

I have realized form validation in accordance with https://auth0.com/blog/angular-2-series-part-5-forms-and-custom-validation/

 <input class="form-control"
               type="text"
               name="phone"
               autocomplete="off"
               placeholder="(XXX)-XXX-XXXX"
               mask=""
               [disabled]="disabled"
               [(ngModel)]="candidate.phone"
               ngControl="phone"/>

...

...

static phone(control: Control): ValidationResult {

    let URL_REGEXP = /^\(\d{3}\)-\d{3}-\d{4}$/i;

    if (control.value && (control.value.length <= 5 || !URL_REGEXP.test(control.value))) {

        return {"phone": true};
    }

    return null;
}

plus for this element I have realized the input mask directory: http://pastebin.com/wRzHSsVy

The following problem occurs: when a telephone number is entered the validation acts first and then the input mask directory. So the data checked by the validator and the data formated by the input mask directory differ. For instance, the phone number on the validator is (888)-888-88882 and the mask turns the number to the following format (888)-888-8888, but the validator has already worked and pointed the mistake before the mask activation.

Ferebee answered 22/8, 2016 at 13:35 Comment(0)
D
0

I have a very similar problem, also with a phone mask/validator. My initial thought was to somehow re-invoke validation after calling "this.control.valueAccessor.writeValue(...)" in my mask.

My attempt was to add:

this.control.control.updateValueAndValidity(
{ 
  onlySelf: true, 
  emitEvent: false 
});

This appears to fire form validation again, but the value submitted to the validator is still the incorrect (pre-masked) value.

However, I'm not using [(ngModel)] in my input, so you may be a candidate to use something like this post suggests

Dempsey answered 14/9, 2016 at 15:25 Comment(0)
D
0

After a little more research, I found a workaround. Granted, it feels a little dirty.

My mask class was attaching to (input) and (keyup.backspace) events:

...
host: {
  '(input)': 'onInputChange($event.target.value)',
  '(keyup.backspace)': 'onInputChange($event.target.value, true)'
}
...

Instead of doing this, I attached to the (blur) and (focus) events.

...
host: {
    '(blur)': 'onInputChange($event.target.value)',
    '(focus)': 'removeMask($event.target.value)'
}
...

On focus, I remove the mask, then on blur I add it back in. This ensures that the validator will get the correct value whenever it changes, without the masking interfering. I then changed the FormControl to use a numeric validator, rather than a phone validator as the value being validated won't have the mask applied.

phone mask:

@Directive({
    selector: '[phoneMask]',
    host: {
        '(blur)': 'onInputChange($event.target.value)',
        '(focus)': 'removeMask($event.target.value)'
    }
})
export class PhoneMask {

    constructor(public control: NgControl) { }

    onInputChange(value) {
        // remove all mask characters (keep only numeric)
        var newVal = value.replace(/\D/g, ''); // non-digits

        // set the new value
        this.control.valueAccessor.writeValue(PhoneMask.applyMask(newVal));        
    }

    removeMask(value) {
        this.control.valueAccessor.writeValue(value.replace(/\D/g, ''));
    }

    static applyMask(value: string): string {
        if (value.length == 0) {
            value = '';
        } else if (value.length <= 3) {
            value = value.replace(/^(\d{0,3})/, '($1)');
        } else if (value.length <= 6) {
            value = value.replace(/^(\d{0,3})(\d{0,3})/, '($1) $2');
        } else {
            value = value.replace(/^(\d{0,3})(\d{0,3})(.*)/, '($1) $2-$3');
        }
        return value;
    }
}

numeric validator:

export function validateNumeric(control: FormControl) {
    let regex = /[0-9]+/;
    return !control.value || regex.test(control.value) ? null : { numeric: { valid: false } };
}
Dempsey answered 14/9, 2016 at 18:23 Comment(1)
I've seen a similar implementation, but it tied to (ngModelChange) and (keydown.backspace). The backspace was to handle the masking changes so that backspace would delete numbers on each stroke. Here it is: https://mcmap.net/q/268439/-mask-for-an-input-to-allow-phone-numbersAnticipate

© 2022 - 2024 — McMap. All rights reserved.