Inherent error
The ngModel
directive uses @Input
and @Output
to obtain the value and emit the change event. So binding a function like setter or getter to a directive like [ngModel]
with default change detection strategy would trigger the function for each change detection.
To replicate this error, plug in a console.log
in the getter, type some value and keep pressing backspace in the <input>
and let go. You could see the console.log
messages keeps getting printed.
Error replication: Stackblitz
Why isn't 'DEFAULT' inserted on Ctrl+a+Backspace
As said before, the ngModel
directive uses @Input
and correspondingly the ngOnChanges()
hook to fetch any changes to the value from <input>
tag. One important thing to note is ngOnChanges()
won't be triggered if the value hasn't changed(1).
We could try to split the two-way binding [(ngModel)]
to [ngModel]
input and (ngModelChange)
output.
export class AppComponent {
public testString: string;
onChange(value: string) {
this.testString = value === '' ? 'DEFAULT' : value;
}
}
<input type="text" [ngModel]="testString" (ngModelChange)="onChange($event)" />
Still error prone: Stackblitz
Here the Ctrl+a+Backspace any string other than 'DEFAULT' would insert 'DEFAULT' as expected. But Ctrl+a+Backspace 'DEFAULT' would result in empty <input>
box due the ngOnChanges()
issue discussed above.
Solution
One way to achieve expected behavior is to not depend on the ngModel
directive's ngOnChanges()
hook to set the value, but to do it ourselves manually. For that we could use an Angular template reference variable in the <input>
and send it to the component for further manipulation.
export class AppComponent {
public testString: string;
public onChange(value: string, inputElem: HTMLInputElement) {
this.testString = value === '' ? 'DEFAULT' : value;
inputElem.value = this.testString;
}
}
<input
type="text"
#myInput
ngModel
(ngModelChange)="onChange($event, myInput)"
/>
Working example: Stackblitz
Here the #myInput
is the template reference variable. If this feels hacky, you'd need to know using ngModel
to manipulate <input>
is in itself called template driven forms. And it's quite legal to manipulate the template ref directly.
With all that said, IMO in cases where such input manipulation is needed, I'd recommend you to use the Angular Reactive Forms instead. It provides a more granular control over the input.
(1) The ngOnChanges()
won't be triggered if the previousValue
and currentValue
of the respective @Input
variable's SimpleChange
object hasn't changed.