20

What is the easiest way to combine ng-changed and ng-blur?

I've found this post: How to let ng-model not update immediately?

However, this does no longer work in angluar 1.2+ Is there any way to achieve the same behavior?

I guess I have to store a copy of the old value myself and compare the new value to that on blur if I try to do the same, or is there any easier way ?

Community
  • 1
  • 1
Roger Johansson
  • 22,764
  • 18
  • 97
  • 193
  • what kind of behavior are you trying to accomplish? a plunkr would be very helpful here – Chris Montgomery Dec 07 '13 at 15:18
  • I don't wan't to trigger the event on each key press, I want to trigger the event on blur, but I don't want to trigger if the value hasn't changed.. KnockoutJS has options for this, Angular seems a bit trigger happy on changed events IMO – Roger Johansson Dec 07 '13 at 15:29
  • still not clear what event you are talking about or what higher level issue is – charlietfl Dec 07 '13 at 15:32
  • There is a now not working example of it here: http://jsfiddle.net/carpasse/JYTUB/3/ this demo does update the label on each keypress which it isn't supposed to do, the ngModelOnBlur did take care of that in earlier versions of angular – Roger Johansson Dec 07 '13 at 15:41
  • 1
    FYI- Looks like this feature is targeted for Angular 1.2.5: https://github.com/angular/angular.js/pull/2129 – KayakDave Dec 07 '13 at 17:56

9 Answers9

48

Use ng-model options. Like this, ng-change will only trigger when the input is blurred.

<input type="text" 
       ng-model="a.b" 
       ng-model-options="{updateOn: 'blur'}" 
       ng-change="onchange()"/>
Niels Steenbeek
  • 4,692
  • 2
  • 41
  • 50
  • 1
    ng-change executes all the time! – Yogesh Manware Feb 18 '15 at 14:01
  • @MYN what is 'all the time!'? Every key-down or every blur or ... See documentation here: https://docs.angularjs.org/api/ng/directive/ngModelOptions [Here is a working example](http://plnkr.co/edit/S6WQvpbYjnNtabzNdz7z?p=preview) – Niels Steenbeek Feb 18 '15 at 14:51
  • I realized my mistake. I am using 1.2.X version and ng-model-options is introduced in 1.3. it works fine with 1.3. Thanks. – Yogesh Manware Feb 18 '15 at 17:48
  • @Niels Steenbeek I type ABCD in text its calling onchange function, then i am delete D and then again type D its again calling onchange function – deadend Feb 07 '20 at 12:41
15

This does what I want. It stores the value on focus, and compares it to the new value on blur, if changed, it triggers the expression in the attribute.

 app.directive('changeOnBlur', function() {
            return {
                restrict: 'A',
                require: 'ngModel',
                link: function(scope, elm, attrs, ngModelCtrl) {
                    if (attrs.type === 'radio' || attrs.type === 'checkbox') 
                        return;

                    var expressionToCall = attrs.changeOnBlur;

                    var oldValue = null;
                    elm.bind('focus',function() {
                        scope.$apply(function() {
                            oldValue = elm.val();
                            console.log(oldValue);
                        });
                    })
                    elm.bind('blur', function() {
                        scope.$apply(function() {
                            var newValue = elm.val();
                            console.log(newValue);
                            if (newValue !== oldValue){
                                scope.$eval(expressionToCall);
                            }
                                //alert('changed ' + oldValue);
                        });         
                    });
                }
            };
        });

usage:

 <input ng-model="foo" change-on-blur="someFunc()" />
Derek
  • 3,295
  • 3
  • 24
  • 31
Roger Johansson
  • 22,764
  • 18
  • 97
  • 193
4

How about this solution. Works for me:

<input ng-init="oldValue = value" ng-model="value"
       ng-blur="oldValue != value && callYourFunc(foo)">
Michael
  • 51
  • 1
  • Original but doesn't work at the 2nd edit/focuson of the input element or if the model is changed from the controller. – Derek Feb 03 '16 at 12:22
4

I am using AngularJs 1.2.x and stumbled upon the ng-change issue of firing on each change. ng-blur can be used but it fires even though there is no change in the value. So both cannot be used efficiently.

With Angularjs 1.3.x, things are easier using ng-model-options like below

to invoke change function "onBlur"

ng-change="ctrl.onchange()" ng-model-options="{updateOn: 'blur'}"

And

to delay invocation of change function by 500ms

ng-change="ctrl.onchange()" ng-model-options='{ debounce: 500 }'"

Now coming to back to the question of getting such things with AngularJs 1.2.x

to invoke change function "onBlur"

html

<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange()" /> or

<input type="text" ng-model="ctrl.a.c" sd-change-on-blur="ctrl.onchange(ctrl.a.c)" />

JS

app.directive('sdChangeOnBlur', function() {
  return {
    restrict: 'A',
    scope: {
      sdChangeOnBlur: '&'
    },
    link: function(scope, elm, attrs) {
      if (attrs.type === 'radio' || attrs.type === 'checkbox')
        return;

      var parameters = getParameters(attrs.sdChangeOnBlur);

      var oldValue = null;
      elm.bind('focus', function() {
        scope.$apply(function() {
          oldValue = elm.val();
        });
      })
    
      elm.bind('blur', function() {
        scope.$apply(function() {
          if (elm.val() != oldValue) {
            var params = {};
            if (parameters && parameters.length > 0) {
              for (var n = 0; n < parameters.length; n++) {
                params[parameters[n]] = scope.$parent.$eval(parameters[n]);
              }
            } else {
              params = null;
            }

            if (params == null) {
              scope.sdChangeOnBlur();
            } else {
              scope.sdChangeOnBlur(params)
            }
          }
        });
      });
    }
  };
});

function getParameters(functionStr) {
  var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
  var params;
  if (paramStr) {
    params = paramStr.split(",");
  }
  var paramsT = [];
  for (var n = 0; params && n < params.length; n++) {
    paramsT.push(params[n].trim());
  }
  return paramsT;
}

to delay invocation of change function by 500ms

html

<input type="text" ng-model="name" sd-change="onChange(name)" sd-change-delay="300"/>

OR

<input type="text" ng-model="name" sd-change="onChange()" sd-change-delay="300"/>

JS

app.directive('sdChange', ['$timeout',
  function($timeout) {
    return {
      restrict: 'A',
      scope: {
        sdChange: '&',
        sdChangeDelay: '@' //optional
      },
      link: function(scope, elm, attr) {
        if (attr.type === 'radio' || attr.type === 'checkbox') {
          return;
        }

        if (!scope.sdChangeDelay) {
          scope.sdChangeDelay = 500; //defauld delay
        }

        var parameters = getParameters(attr.sdChange);

        var delayTimer;
        elm.bind('keydown keypress', function() {
          if (delayTimer !== null) {
            $timeout.cancel(delayTimer);
          }

          delayTimer = $timeout(function() {
            var params = {};
            if (parameters && parameters.length > 0) {
              for (var n = 0; n < parameters.length; n++) {
                params[parameters[n]] = scope.$parent.$eval(parameters[n]);
              }
            } else {
              params = null;
            }

            if (params == null) {
              scope.sdChange();
            } else {
              scope.sdChange(params)
            }
            delayTimer = null;
          }, scope.sdChangeDelay);

          scope.$on(
            "$destroy",
            function(event) {
              $timeout.cancel(delayTimer);
              console.log("Destroyed");
            }
          );
        });
      }
    };
  }
]);

function getParameters(functionStr) {
  var paramStr = functionStr.slice(functionStr.indexOf('(') + 1, functionStr.indexOf(')'));
  var params;
  if (paramStr) {
    params = paramStr.split(",");
  }
  var paramsT = [];
  for (var n = 0; params && n < params.length; n++) {
    paramsT.push(params[n].trim());
  }
  return paramsT;
}

plnkrs for both approaches are

http://plnkr.co/edit/r5t0KwMtNeOhgnaidKhS?p=preview

http://plnkr.co/edit/9PGbYGCDCtB52G8bJkjx?p=info

Community
  • 1
  • 1
Yogesh Manware
  • 1,843
  • 1
  • 22
  • 25
3

how about this plunkr?

using angular's built in ng-blur, update your "persisted value" on blur

<input type="text" ng-model="boxValue" ng-blur="doneEditing(boxValue)">

when saving, verify the value is different

$scope.doneEditing = function(v) {
  if (v !== $scope.persistedValue) // only save when value is different
    $scope.persistedValue=v;
}

There is no special option on ng-blur for pre-checking equality that I'm aware of. A simple if seems to do the trick

Chris Montgomery
  • 2,344
  • 2
  • 19
  • 30
2

Newer versions of AngularJS (now in 1.3 beta) supports this natively. See my answer here

Community
  • 1
  • 1
manikanta
  • 8,100
  • 5
  • 59
  • 66
2

The solution that worked for me was as follows:

<input id="fieldId" type="text"
ng-model="form.fields.thisField"
ng-model-options="{ updateOn: 'blur' }"
ng-change="form.save()" />

I found this solution here

That page says it requires AngularJS version 1.3.0+ . I tested it with AngularJS version 1.5.11 and it works in that version for me.

Kmeixner
  • 1,664
  • 4
  • 22
  • 32
2

In app.html

<input type="text" name="name" ng-model="$ctrl.user.name" ng-blur="$ctrl.saveChanges()" ng-change="$ctrl.onFieldChange()"/>

in app.ts

 public onFieldChange() {
    this.isValuesModified = true;
}

//save changes on blur event
public saveChanges() {
    //check if value is modified
    if(this.isValuesModified){

     //Do your stuff here
    }
}
0

This is how I resolved this.

<input ng-focus="oldValue=value" ng-model="value" ng-blur="oldValue!=value && youFunction()" />
Ghulam Murtaza
  • 111
  • 1
  • 4