5

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?

Lorenzo Montanari
  • 958
  • 1
  • 15
  • 19
2ooom
  • 1,760
  • 1
  • 23
  • 37

3 Answers3

4

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!

Community
  • 1
  • 1
Tomer
  • 4,382
  • 5
  • 38
  • 48
  • Thanks for the solution and good article. So far works perfectly. Though using $parsers and $formatters for event-firing still seems like exploiting Angular. Can't see a reason why they didn't built it into framework or at least added $intercepters collection to ngModelController – 2ooom Aug 18 '14 at 15:03
3

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
});
Benny Bottema
  • 11,111
  • 10
  • 71
  • 96
  • Good point. This works [perfectly](http://plnkr.co/edit/QrNGJQ). Native JS is always better even if you have to write some extra wrapper code to add those properties to json response from the server. And the lack of declarity in html is absolutely fine here for me. The only unfortunate thing that now my boss would not understand why we should loose support for old browsers in advantage of writing neat code :) – 2ooom Aug 18 '14 at 14:42
-1

Of course use:

<input type="number" />

This will create you spinner =)

sss
  • 1,259
  • 9
  • 23
  • Would be perfect solution if you can add custom increment (depending on the value) and refresh UI via ajax the same time. Oh, and style those arrows for all browsers and platforms with CSS. – 2ooom Aug 18 '14 at 13:54
  • have you tried directive then, i think what you really wanted is isolate the scope to make it more declarative like using '=attr'. – sss Aug 18 '14 at 14:05
  • Ideally I would expect ng-change to work with model updates from code so I can manipulate view-model by specifying callback in it's context – 2ooom Aug 18 '14 at 14:23
  • and it didn't? try another method. – sss Aug 18 '14 at 14:29
  • Nope, ng-change supports only user initiated changes. So the whole question is actually about other options:) – 2ooom Aug 18 '14 at 14:48