I am having a hard time implementing the equivalent of what used to be parsers and formatters from AngularJS. My use case is not directly a parser/formatter, but here is what I am trying to accomplish.
I have a dynamic list of keys. For each of those keys, the user can enter a value 0-100. If the user inputs (keyboard or paste) any characters, they are stripped. If the user inputs any number greater than 100, it is capped to 100.
For that, I created a directive that filters out what I need.
The directive works properly on the input
element, but the underlying form
is not getting the proper value as shown below.
The form seems to get my last input, no matter what.
How can I prevent the update of the model for invalid values?
I created a StackBlitz for this. The gist of the code is:
app.component.html
<form #form="ngForm">
<div *ngFor="let key of list">
<input name="{{key}}" ngModel appFilter >
</div>
</form>
<pre>
form:
{{ form.value | json }}
</pre>
app.component.ts
export class AppComponent {
readonly list = ['foo', 'bar'];
}
filter.directive.ts
@Directive({
selector: 'input[appFilter]',
})
export class FilterDirective {
// TODO: remove directive?
@Output()
ngModelChange: EventEmitter<any> = new EventEmitter();
value: string;
@HostListener('input', ['$event'])
onInputChange($event: TextInput) {
const filtered = $event.target.value.replace(/[^0-9]/gi, '');
if (filtered.match(/^[0-9]/)) {
// starts with a number, must be a number
const number = +filtered.replace(/\*/gi, '');
$event.target.value = `${Math.min(100, number)}`;
} else {
// it must be empty string
$event.target.value = '';
}
this.ngModelChange.emit($event.target.value);
}
constructor() {}
}
interface TextInput {
target: {
value: string;
};
}
By using $event.target.value
you adapt the input
's value, but not the value of the underlying Angular NgControl
. Luckily, NgControl
can be injected into your directive whenever the input
with the appFilter
attribute also has an associated ngModel
, formControl
or formControlName
attribute.
Inject NgControl
into your FilterDirective
and use it to set the value:
import { NgControl } from '@angular/forms';
constructor(private ngControl: NgControl) { }
@HostListener('input', ['$event'])
onInputChange($event: TextInput) {
const filtered = $event.target.value.replace(/[^0-9]/gi, '');
if (filtered.match(/^[0-9]/)) {
// starts with a number, must be a number
const number = +filtered.replace(/\*/gi, '');
this.ngControl.control.setValue(`${Math.min(100, number)}`);
} else {
// it must be empty string
this.ngControl.control.setValue('');
}
}
Should you expect cases where your FilterDirective
is used without the ngModel
, formControl
or formControlName
attribute; then make the injected NgControl @Optional()
.