Why does the ngModel validator code seem to run before the scope.$watch changes?

I am attempting to create an AngularJS directive with a custom validator so that I can show error messages based on the validator. However, I am running into an error because it seems that the validator is running prior to the scope.$watch() per the console.log() messages I’ve input.

Here is the directive:

angular
  .module('app')
  .directive('validateRefundAmount', validateRefundAmount);
validateRefundAmount.$inject = [ 'AmountConversionService', '$q' ];

function validateRefundAmount(AmountConversionService, $q) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function (scope, element, attrs, control) {
      scope.$watch('orderDetails.refundAmount', function(newValue, oldValue) {
        if (newValue === oldValue) {
          console.log(newValue, oldValue);
          return;
        }
        
        // Get Already Refunded Amount
        var refundedAmount = scope.orderDetails.refundedAmount;

        // Converts Amount To Pure Integers, Removing Decimal
        var totalPaymentAmount = AmountConversionService.prepareAmountForCalculations(scope.orderDetails.paymentAmount, 10);
        var totalRefundAmount = AmountConversionService.prepareAmountForCalculations(newValue || 0);

        // Add Already Refunded Amount to Previously Refunded Amount to Get Total Refund
        if (refundedAmount) {
          totalRefundAmount += AmountConversionService.prepareAmountForCalculations(refundedAmount);
        }

        control.$validators.refundAmountTooHigh = function() {
          if (totalRefundAmount > totalPaymentAmount) {
            return false
          } 
          return true;
        }
     });
    }
  };
}

The element that this is applied to is an text input box that starts with no value. When i type ‘1’ into the field the validator doesn’t run. When I add a ‘2’ to the field, making ’12’, the validator runs with ‘1’ as the input. The same thing occurs when I add a ‘3’ to the field making ‘123’ — it runs with ’12’, instead of the new value of ‘123’.

When I’ve inserted console.log() statements to see what is occurring when, it looks like the validator code runs and then the scope.watch() runs creating the new value after the validator has run but I don’t know why or what to search for to find out.

There may be another way to do what I’m trying to do — what I want to do is run the validator everytime the element value changes. I can’t use bind on keypress due to keypress not detecting backspaces and may be able to use bind on keydown — not sure.

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

Please check doc for $setViewValue, apparently $validators is used before ngModelValue update.

And your scope.$watch will only be invoked after $modelValue is updated.

https://docs.angularjs.org/api/ng/type/ngModel.NgModelController#$setViewValue

When $setViewValue is called, the new value will be staged for committing through the $parsers and $validators pipelines. If there are no special ngModelOptions specified then the staged value is sent directly for processing through the $parsers pipeline. After this, the $validators and $asyncValidators are called and the value is applied to $modelValue. Finally, the value is set to the expression specified in the ng-model attribute and all the registered change listeners, in the $viewChangeListeners list are called.

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply