Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way in AngularJS to react on model changes without using $watch?

I'm implementing a simple spinner control in AngularJS and I want to react both on user input and changes from +/- buttons. Here is my HTML:

<input type='button' ng-click="MyProperty = MyProperty - 1" value="-">
<input type='text' ng-model="MyProperty" ng-change="log('changed from ngChange')">
<input type='button' ng-click="MyProperty = MyProperty + 1" value="+">

But this will track only 'user-changes' far as ngChange supports only user-interaction updates as per documentaiton

So now I'm looking at $scope.$watch as Frederik recommends:

$scope.$watch('MyProperty', function() {
  $scope.log('changed from $watch');
});

See plunker demo

But this doesn't seem right enogh.

  1. First it's not declarative and you have to search the code for MyTestProperty to find this binding.
  2. If you want would like to place $scope.log in a separate Model you have to either inject $scope or to do the binding in controller. And as far as I understand both ways are not considered to be the best practicies.

Some people think that $watch is a bad thing in general for a number of other reasons. But the solution advised there (which would be calling log in ngClick directly) doesn't make too much diference to me. Basicly you have to manually track all the changes and if new actor comes you have to copy your logic there.

So the questions would be: is there a way that allows you to automaticly keep track of model updates without $watch? And how bad is the idea to implement your own derective for this if there is now such way?

like image 753
2ooom Avatar asked Aug 18 '14 12:08

2ooom


2 Answers

There are couple of ways to do this, but the most elegant way is requiring the ngModel of the input and then use it to view / manipulate the value.

Here is your updated Plunker.

.directive('outputIt', function() {
    return {
      restrict: 'A',
      scope: {
        outputIt: '&'
      },
      require: '?ngModel',
      link: function(scope, element, attrs, ngModelCtrl) {
        ngModelCtrl.$parsers.push(function(val) {
          //console.log("the model was changed by input")
          scope.outputIt()(val);
        });
        ngModelCtrl.$formatters.push(function(viewValue) {
          //console.log("the model was changed from outside.");
          scope.outputIt()(viewValue);
          return viewValue;
        });
      }
    }
  })


To find out more about it, here is a very cool article about it: Atricle

Good luck!

like image 174
Tomer Avatar answered Oct 18 '22 10:10

Tomer


Have you looked into the ES5 way? It's essentially javascript's native $watch functionality. The difference is you keep the set/get functions are encapsulated with the property, whereas a $watch can be applied externally anywhere.

var MyCtrl = function(){
  this._selectedItem = null;
};

Object.defineProperty(MyCtrl.prototype, "selectedItem", {
    get: function () {
        return this._selectedItem;
    },
    set: function (newValue) {
        this._selectedItem = newValue;

        //Call method on update
        this.onSelectedItemChange(this._selectedItem);
    },
    enumerable: true,
    configurable: true
});
like image 23
Benny Bottema Avatar answered Oct 18 '22 11:10

Benny Bottema