0

I'm using Angular 8 for my client application. I want an error message to be displayed under the input box for the user when she inserts a wrong input. I'm using this inside my HTML:

  <!-- Score input -->
  <div class="form-group">
    <label for="score">Score</label>
    <input type="number" class="form-control"
           placeholder="0.0 ÷ 10.0"
           min="0.0" max="10.0" step="0.1" name="score"
           id="score"
           [(ngModel)]="model.score"
           #score="ngModel">
    <div [hidden]="score.valid" class="alert alert-danger">
      Insert a value between 0.0 and 10.0
    </div>
  </div>

I would expect it to be displayed if the user was to input "asdf" for example instead of a number like "6.7". But it never appears. I tried removing [hidden]="score.valid" and it gets correctly displayed. Why is Angular considering "asdf" a valid input even if I set all those attributes: type="number" min="0.0" max="10.0" step="0.1" ?

Robb1
  • 4,587
  • 6
  • 31
  • 60
  • 1
    If the input type is number, how is it possible to enter the string "asdf"? – Nicholas K Feb 18 '20 at 18:15
  • @NicholasK that's a good question... I don't know! I'll post a picture, wait! – Robb1 Feb 18 '20 at 18:16
  • 1
    Could you include a very minimal stackblitz rather than posting a picture for replication? – Nicholas K Feb 18 '20 at 18:17
  • 1
    @NicholasK: I believe it is by design. I am able to enter text to any of the `` fields [here](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/number). – ruth Feb 18 '20 at 18:23
  • I wasn't able to.. – Nicholas K Feb 18 '20 at 18:39
  • 1
    Please see this [bug report 1398528](https://bugzilla.mozilla.org/show_bug.cgi?id=1398528). So, for eg., Chrome doesn't allow non-number characters, but Firefox does. – ruth Feb 18 '20 at 18:49
  • @Michael D, you are right. MS Edge(v80) does not allow. But Firefox does allow alpha. – rickz Feb 18 '20 at 18:59
  • why let the user even type letters? use a directive e.g [this](https://stackoverflow.com/questions/41465542/angular2-input-field-to-accept-only-numbers) – Andrew Allen Feb 18 '20 at 20:16

3 Answers3

1

Input fields of type number allow users to type in characters that are not part of a number. See here for details.

I do not see how you handle setting the value of score.valid variable. However, it can be achieved by using CSS pseudo classes.

input:invalid+span:after {
  content: 'Insert a value between 0.0 and 10.0';
  padding-left: 5px;
}
<!-- Score input -->
<div class="form-group">
  <label for="score">Score</label>
  <input id="input-validator" type="number" class="form-control"
         placeholder="0.0 ÷ 10.0"
         min="0.0" max="10.0" step="0.1" name="score"
         id="score"
         [(ngModel)]="model.score"
         #score="ngModel">
  <span class="alert alert-danger"></span>
</div>

Update: To include 'Submit' button.

Template:

<form  #scoreValue="ngForm" (ngSubmit)="submitScore(scoreValue.value)">

  <label>Score: </label>
  <input type="number" class="form-control" (keyup)="onKeyUp($event)" 
    placeholder="0.0 ÷ 10.0"
    [min]="min" [max]="max" [step]="step" name="score"
    id="score"
    [(ngModel)]="score"/>
  <span class="alert alert-danger"></span>
  <br /><br />
  <span> Does not work as expected -> </span>
  <button [disabled]="scoreValue.invalid">Submit</button>
  <br /><br />
  <span> Works as expected -> </span>
  <button [disabled]="disableSubmit">Submit</button>

</form>

Component:

public onKeyUp(event){
  let score = event.currentTarget.value;
  if ((score === '') || ((score.split('.')[1] || []).length > 1)) {
    this.disableSubmit = true;
  } else {
    score = Number(score);
    if ((score >= this.min) && (score <= this.max)) {
      this.disableSubmit = false;
    } else {
      this.disableSubmit = true;
    }
  }
}

Working example: Stackblitz

The min and max attributes set only the minimum and maximum values of the stepper arrows. That is why scoreValue.invalid does not work as expected.

ruth
  • 29,535
  • 4
  • 30
  • 57
  • Thanks for your answer! It works nicely but then I don't know how to disable the "submit" button for the form. Before I was checking if the form was valid like this: ``. With your approach how can I do that? – Robb1 Feb 18 '20 at 18:44
  • Thanks again for your prompt response :) I'm working using your code as reference currently. I noticed that if I input "8.7222" the error message is correctly displayed, but the button is not disabled. Why? – Robb1 Feb 19 '20 at 15:05
  • Also I can't reproduce even the correct display of the message error (as it works in your code) in case of wrong decimal value. Can you underline with comments where is this constraint defined? – Robb1 Feb 19 '20 at 15:22
  • 1
    "8.7222" didn't disable the button because we weren't checking for the length of the decimal part. I've updated the code, please have a look. Specifically this part: `(score.split('.')[1] || []).length > 1`. It fails if the length of decimal part is more than 1. – ruth Feb 20 '20 at 00:39
  • I am not exactly sure why the error message is not displayed correctly. At the moment we use [constraint validation](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) to display the message. I could test it across major browsers (except IE). More info about the pseudo-class: [:invalid](https://developer.mozilla.org/en-US/docs/Web/CSS/:invalid) – ruth Feb 20 '20 at 00:42
0

Why let the browser decide the behaviour?
Use a directive

<input appDecimalOnly />
/**
 * Credit: omeralper stackoverflow user
 * https://stackoverflow.com/questions/41465542/angular2-input-field-to-accept-only-numbers
 */
@Directive({
  selector: "[appDecimalOnly]"
})
export class DecimalOnlyDirective {
  constructor(private el: ElementRef) {}

  @HostListener("keydown", ["$event"]) onKeyDown(event) {
    let e = <KeyboardEvent>event;

    // console.log(e.keyCode)

    if (
      [46, 8, 9, 27, 13, 110, 190].indexOf(e.keyCode) !== -1 ||
      // Allow: Ctrl+A
      (e.keyCode === 65 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+C
      (e.keyCode === 67 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+V
      (e.keyCode === 86 && (e.ctrlKey || e.metaKey)) ||
      // Allow: Ctrl+X
      (e.keyCode === 88 && (e.ctrlKey || e.metaKey)) ||
      // Allow: home, end, left, right
      (e.keyCode >= 35 && e.keyCode <= 39)
    ) {
      // let it happen, don't do anything
      return;
    }
    // Ensure that it is a number and stop the keypress
    if (
      (e.shiftKey || (e.keyCode < 48 || e.keyCode > 57)) &&
      (e.keyCode < 96 || e.keyCode > 105)
    ) {
      e.preventDefault();
    }
  }
}

Stackblitz

Andrew Allen
  • 6,512
  • 5
  • 30
  • 73
  • Really nicely done! Thanks :) The only problem is that this doesn't prevent the user from inserting an invalid input like `12..99.0`! I would like only input of type `x.y` to be allowed :) – Robb1 Feb 19 '20 at 11:34
  • @Robb1 easy enough fix...https://stackblitz.com/edit/angular-cuyp6k – Andrew Allen Feb 19 '20 at 20:51
0

For better validation, you have to use FormControl in .ts file, also you could take a look at official doc https://angular.io/guide/form-validation.

FormControl more flexible than ngModel - worth trying:)

scoreControl = new FormControl('',
 Validators.compose([
   Validators.required,
   Validators.pattern('^[0-9]*$'),
   Validators.max(10)
 ])
);

and modify your template with it:

  <div class="form-group">
    <label for="score">Score</label>
    <input type="number" class="form-control"
           placeholder="0.0 ÷ 10.0"
           min="0.0" max="10.0" step="0.1" name="score"
           id="score"
           [formControl]="scoreControl">

    <div *ngIf="score.invalid" class="alert alert-danger">
      Insert a value between 0.0 and 10.0
    </div>

   </div>

Regarding only numbers input you could check here: https://stackoverflow.com/a/41479077/9178709

The best approach is using directive

Dmitry Sobolevsky
  • 1,171
  • 6
  • 12