3

I've got a field for a product that its quantity is dependant on another product's quantity (cant be less than 70%, or more than 100%). Thing is, it evaluates it so quiclky that if the main field is '100', I cant enter 75 on the other field, because I first need to enter the '7', and my code considers it less than 70% and instantly changes it to the 70% value.

I've already tried using a self-made 'sleep' function, that makes a promise take some time to resolve. setInterval and setTimeout also do not work as I intend for some reason (only evaluates the dependent field when I press enter, and it is not after the stablished time). This is not consistent with the rest of the table, so it is not a suitable solution.

This is the angular bit that controls this input

<div class="input-field">
    <input class="input" type="number" [integerInput]  ="true"
    [disabled]       ="item.deshabilitado( ) || !editable" 
    [(ngModel)]     ="item.cantidad"
    [ngModelOptions]="{standalone: true}"
    (keyup) ="setCantidad( item, $event.target.value )"
    max="9999" min="1" value="1"  >
</div>

Sadly I cant get a minimal and working example. I need the dependent field to be able to evaluate its value automatically (without pressing enter or clicking on another field) without automatically correcting my input when I press only one character.

dizzydizzy
  • 147
  • 10
  • I'm not familiar with Angular so can't give you the exact code to fix this, but it sounds like you want to trigger on the `blur` event instead of `keyup` – Robin Zigmond Aug 27 '19 at 18:13

5 Answers5

1

Use blur() method instead of keyup(). I guess you are validating the input with keyup() and each time you enter value it validates. For instance you are trying to enter 70 but when you enter first character, 7 it is invalid. The blur() fires your method and validates your input when you are done with inputting value.

<div class="input-field">
    <input class="input" type="number" [integerInput]="true"
    [disabled]="item.deshabilitado( ) || !editable" 
    [(ngModel)]="item.cantidad"
    [ngModelOptions]="{standalone: true}"
    (focusout)="setCantidad( item, $event.target.value )"
    max="9999" min="1" value="1"  >
</div>

In addition, you can use keyup.enter if a user is done with inputting value and presses enter. The value updates when the enter key is pressed or clicked somewhere else.

Maihan Nijat
  • 9,054
  • 11
  • 62
  • 110
  • Thank you for your answer. This seems to work pretty decently, even though I still have to click away for the value to validate. It is pretty consistent, though. Thank you – dizzydizzy Aug 27 '19 at 18:53
  • @dizzydizzy You are welcome. If you have only one input then use `setTimeout()` and validate after few milliseconds. Or your best way would be to apply invalid class to your input when it's invalid that user fix it. 7 red box, 70 turns into green. – Maihan Nijat Aug 27 '19 at 18:55
1

UPDATE:

I was able to solve this and get it to work like I intended at the beginning. The resulting code for the field I tried to validate would be

debounce (afterValueChanged)="setCantidad( item )"

It behaves in a way that does not need me to click outside the field for it to start validating, instead of the (blur) or (focusout)

dizzydizzy
  • 147
  • 10
0

In the other field where you are trying to enter 75, you can add an ngModelOptions configuration to update only on blur.

[ngModelOptions]="{ updateOn: 'blur' }"

By default it updates on 'change', which explains the current behavior.

Update

You could also debounce the input using RXJS. This is probably closer to what you were trying to do with setInterval / setTimeout. This is also a use case where Reactive Forms really shines over Template Driven Forms imo, since you'll need to use RXJS operators.

For reactive driven forms, you just pipe an operator before subscribing to the valueChanges observable of the formControl

this.myInput.valueChanges
  .pipe(
    debounceTime(1000),      // add 1 second of delay
    distinctUntilChanged()   // optional but recommended - only trigger for new values
  )
  .subscribe(val=> {
    console.log('value', val);
  });

It is possible to accomplish the same behavior in template driven forms but there is some setup involved. See this SO question/answer

Chris Newman
  • 3,152
  • 1
  • 16
  • 17
  • This seems to let me type it freely, but I still have to press enter or click away for the changes to make effect. Is there any way you're aware of that doesnt require this so it's behavior is consistent with the rest of the table? Thank you very much, if anything this is a temporary solution – dizzydizzy Aug 27 '19 at 18:23
  • Updated my answer with some information related to debouncing – Chris Newman Aug 27 '19 at 18:41
  • Thank you very much. Im not able to tell if this work yet because im having trouble implementating it, but I'll keep at it. At least im a lot closer now. – dizzydizzy Aug 27 '19 at 18:54
0

Try to use the debounce function

Import debounceTime from rxjs and use it to add some delay :)

Check out this example

Amardeep Singh
  • 413
  • 4
  • 6
0

As per the information given, you might want to debounce your function call.

Debounced functions do not execute when invoked, they wait for a pause of invocations over a configurable duration before executing; each new invocation restarts the timer.

<div class="input-field">
    <input class="input" type="number" [integerInput]  ="true"
    [disabled]       ="item.deshabilitado( ) || !editable" 
    [ngModel]     ="item.cantidad"
    [ngModelOptions]="{standalone: true}"
    (keyup) ="setCantidad( item, $event.target.value )"
    max="9999" min="1" value="1"  >
</div>

In your component class:

  timer;
  setCantidad(item,value){
    if(this.timer){
      clearTimeout(this.timer);
    }
    this.timer = setTimeout(() => {
      this.item.cantidad = value;
      //perform any operations here

      console.log(item, value);
    },1000);

  }

This will wait for the user to stop typing and execute the operation after the specified time in the timeout.

Note: One additional change, if you want to render the value in the same input field where you are typing, consider changing the [(ngModel)] -> [ngModel]. This will just perform a property binding and not event binding.

Demo stackblitz link : https://stackblitz.com/edit/angular-cu9gss

Manoj Singh
  • 1,911
  • 1
  • 20
  • 22