Directives can be used as interceptors on the input before the model changes. Here is an example of such a directive:
@Directive({
selector: '[myValueChangeInterceptor]',
providers: [NgModel]
})
export class InputInterceptorDirective {
@Input() public myValueChangeInterceptor: (value: string) => string;
@Output() public valueInput: EventEmitter<string> = new EventEmitter();
constructor(private model: NgModel, private element: ElementRef) {
}
@HostListener('ngModelChange', ['$event'])
public onModelChange($event: string): void {
const cursorPosition: number = this.element.nativeElement.selectionStart;
if (this.myValueChangeInterceptor) {
const newValue: string = this.myValueChangeInterceptor($event);
this.model.valueAccessor.writeValue(newValue);
this.valueInput.emit(newValue);
if (newValue.length !== $event.length) {
this.element.nativeElement.selectionStart = this.element.nativeElement.selectionEnd = cursorPosition - 1;
}
} else {
this.valueInput.emit($event);
}
}
}
And, accordingly, the template will look something like this:
<input matInput
type="{{inputType}}"
placeholder="{{placeholder}}"
[ngModel]="value"
[myValueChangeInterceptor]="toNumericFormat"
(blur)="onBlur()"
(focus)="onFocus()"
(valueInput)="onValueInput($event)"
(keydown.enter)="onKeyDownEnter()"
[readonly]="disabled"
autocomplete="off">
and of course, with this interceptor, we can bring the string value to something that looks like number:
public toNumericFormat(value: string): string {
const inputValue: string = value;
let dotCount: number = 0;
let signCount: number = 0;
return inputValue.replace(/\D/g, (char: string) => {
if (char === '-') {
return (++signCount > 1 || inputValue.indexOf(char) !== 0) ? '' : char;
}
if (char === '.') {
return (++dotCount > 1) ? '' : char;
}
return '';
});
}