The directive below will format the value as you type:
- the
input method is triggered when the user enters the value (view -> model, aka $parser in AngularJS). It makes sure the view gets formatted view (via render method) while the model gets raw numeric value (via propagateChange method).
- the
writeValue method is triggered when the model is updated (model -> view, aka $formatter in AngularJS). It makes sure the view gets formatted view (via render method).
- the
format method contains the logic to format the view. We need some extra conditions there to make sure the user can not only input characters but also remove them one by one.
To use the directive register it in the module and link it to the input, e.g.:
<input appFormattedNumber formControlName= .../>
(I have tested this with reactive forms only)
import {Directive, ElementRef, forwardRef, HostListener, Renderer2} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
@Directive({
selector: '[appFormattedNumber]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormattedNumberDirective),
multi: true
}
]
})
export class FormattedNumberDirective implements ControlValueAccessor {
propagateChange;
constructor( private renderer: Renderer2, private element: ElementRef ) {}
@HostListener('input', [ '$event.target.value' ])
input( value ) {
const canonical = this.removeNonNumericCharacters(value);
const formatted = this.format(canonical);
this.render(formatted);
this.propagateChange(canonical);
}
writeValue( value: any ): void {
const canonical = this.removeNonNumericCharacters(value.toString());
const formatted = this.format(canonical);
this.render(formatted);
}
registerOnChange( fn: any ): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {}
setDisabledState(isDisabled: boolean): void {}
private removeNonNumericCharacters(value): string {
return value.replace(/\D/g, '');
}
private format(value): string {
if (value.length < 5) {
return value;
}
const int = value.substring(0, 1);
const areaCode = value.substring(1, 4);
const ext1 = value.substring(4, 7);
const ext2 = value.substring(7, 11);
const ext = ext2 ? ` ${ext1}-${ext2}` : `${ext1}`;
return `${int} (${areaCode}) ${ext}`;
}
private render(value) {
const element = this.element.nativeElement;
this.renderer.setProperty(element, 'value', value);
}
}