I want to have an input that accepts numbers only, and start with a specific number. To force the input to start with a number from a local variable, I tried, with no success yet, the following:
<input matInput autocomplete="off" formControlName="start" minlength="3" maxlength="3"
type="text" (input)=forceStart($event)>
In the .ts
file:
starter: number = 4;
forceStart(e) {
e.value = e.value.replace('/[^' this.starter '][^0-9]/g', '');
}
How to force the input to start with the starter variable?
EDIT1
The reason of not using type="number"
is to avoid the auto up/down arrows to increment/decrement the value.
EDIT2
I used a function instead of inline javascript code.
CodePudding user response:
You should use an input
of type number
, this reduces the risk of non-digit characters in the input and unecessary validators. To hide the arrows follow this w3schools tutorial.
Solution 1 - Custom Validator with error message
In your components HTML would look like this:
<form [formGroup]="form">
<mat-form-field>
<input matInput type="number" formControlName="start">
<mat-error *ngIf="(form.controls['start'].touched
|| form.controls['start'].dirty)
&& !form.controls['start'].valid
&& form.controls['start'].hasError('numberStartingWith')">
Number must start with {{starter}}
</mat-error>
</mat-form-field>
</form>
In your components TS, you want to use a reactive form with a custom async validator:
private starter: number = 4;
public form = new FormGroup({
start: new FormControl(undefined, {
asyncValidators: numberStartingWith(this.starter),
updateOn: 'change'
})
})
In a separate file or outside the components class create the following function:
import { AbstractControl, ValidationErrors, ValidatorFn } from "@angular/forms";
export const numberStartingWith = (starter: number): ValidatorFn => {
return (c: AbstractControl): Observable<ValidationErrors | null> =>
new RegExp(`^${starter}`).test(c.value) ?
of(null) :
of({numberStartingWith: {value: c.value}})
}
Edit: Updated the solution - This way the user could type in any number at first, but will see an error message. i.e.
- user types
4
in first place, no error message displayed - user types
3
in first place, error message will be displayed - user corrects
3
in first place to4
, error message will disappear
Solution 2 - using matPrefix
<mat-form-field>
<input matInput type="number" formControlName="start">
<div matPrefix>{{starter}}</div>
</mat-form-field>
The matPrefix
will display the starter number but won't be used in validation or such. On form submition you'd need to add the starter value programmatically in your typescript code.
Please Note: Never change a users input in the UI. It makes the UI/UX unclear to the user and might cause frustration. Use validators where possible or edit the values before submition.
CodePudding user response:
I suggest the following file input.ts:
var starter: number = 4;
var re = new RegExp('[^' starter ']{1}[0-9]{2}|[^0-9]{3}|[0-9]{1}[^0-9]{2}|[0-9]{2}[^0-9]{1}');
function repl(input: string) {
return input.replace(re, '');
}
compile:
tsc input.ts
The test html file is:
<html>
<head>
<script src="input.js"></script>
</head>
<body>
<form>
<input matInput autocomplete="off" formControlName="start" minlength="3" maxlength="3" type="text" oninput="this.value=repl(this.value);">
</form>
</body>
</html>
Try it!
CodePudding user response:
The solution was in two steps:
1-Put the variable inside a regex:
starter: string = '4';
regex = new RegExp("^" this.starter "{1}[0-9]{0,2}$");
2-Use the regex to ensure the value is matching the pattern, otherwise remove the last character.
forceStart(e) {
if (this.regex.test(e.srcElement.value)) {
}
else {
e.srcElement.value = e.srcElement.value.slice(0, -1);
}