1

I have an input which connected to model. Also, the input has directive which $watch the model.

There are 2 ways that the model will change.

  1. The user will type in the textbox.
  2. The code will change it (no matter what is the reason)

My question is

Is there a way to find out who change the model, the user interaction or the code, in the directive?

Example:

angular.module('app', [])
.controller('ctrl', function($scope) {

})
.directive('dir', function($rootScope){
  return {
    require: 'ngModel',
    link: function(scope, element, attrs) {
      $rootScope.logs = [];
      scope.$watch(attrs.ngModel, function() {
        // here I need to check if the change was from the UI or from the controller
        
        $rootScope.logs.push('change');
      });
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
  <input type="text" data-ng-model="model" data-dir="" />
  <button data-ng-click="model = 'asd'">Set "model" to defferent value</button>

  {{model}}
  <hr />
  <h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
  <ul>
    <li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
  </ul>
</div>

http://jsbin.com/vufawur/edit?html,js,output

Update

Example2:

angular.module('app', [])
.controller('ctrl', function($scope, $timeout) {
  $timeout(function() {
      $scope.model = 'asd';
  }, 3000);
})
.directive('dir', function($rootScope){
  return {
    require: 'ngModel',
    link: function(scope, element, attrs) {
      $rootScope.logs = [];
      scope.$watch(attrs.ngModel, function() {
        // here I need to check if the change was from the UI or from the controller

        $rootScope.logs.push('change');
      });
    }
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">
  ...wait until data "return from the server"<br />
  <input type="text" data-ng-model="model" data-dir="" />
  
  {{model}}
  <hr />
  <h3>console <button data-ng-click="$root.logs = []">clear console</button></h3>
  <ul>
    <li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
  </ul>
</div>
Mosh Feu
  • 28,354
  • 16
  • 88
  • 135

2 Answers2

2

ext-change External Change Directive for ng-model

Use a $viewChangeListener to save the last user input and have the watch handler compare that to discriminate external changes to the model from user input changes to the model.

.directive('extChange', function(){
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, modelCtrl) {
        var lastUserInput = modelCtrl.$viewValue;
        modelCtrl.$viewChangeListeners.push(function() {
            lastUserInput = modelCtrl.$viewValue;
        });
        scope.$watch(attrs.ngModel, function watchHandler (value) {
            if (value!==lastUserInput) {
                scope.$eval(attrs.extChange, {$value:value});
            }                
        });
    }
  }
});

The example directive saves that last user input. When the watch handler gets a value that is different, it invokes the Angular expression defined by the ext-change attribute. The value of the change is exposed as $value.

<input ng-model="someInput"
       ng-change="userInput=someInput"
       ext-change="extInput=$value">

The ext-change directive works with the ng-model directive and complements the ng-change directive.

In this example, the ext-change directive only updates the extInput variable on external changes to the model. The ng-change directive only updates the userInput variable for user changes.

The DEMO on JSFiddle


The directive can also be used to invoke functions.

<input ng-model="someInput"
       ng-change="userEvent(someInput)"
       ext-change="externalEvent($value)">
georgeawg
  • 48,608
  • 13
  • 72
  • 95
1

Do not use $watch. You should not use it, you have to not use it, you are going to have trouble if you use $watch, you are already in trouble, don't use it.

Use control flow and events. It is possible that you already have a lot of watcher and scope soup, it is not too late, refactor as soon as possible, it is for your best.

angular.module('app', [])
  .controller('ctrl', function($scope) {

  })
  .directive('dir', function($rootScope) {
    return {
      require: 'ngModel',
      link: function($scope, element, attrs) {
        $rootScope.logs = [];

        $scope.modelChange = function(reason) {
          $rootScope.logs.push(reason);
        };

        $scope.modelChangedFromInput = function(model) {
          $scope.modelChange('From input');
        };

        $scope.buttonClick = function() {
          $scope.model = 'asd';
          $scope.modelChange('From button');
        };
      }
    }
  });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div data-ng-app="app" data-ng-controller="ctrl">

  <input type="text" data-ng-model="model" data-dir="" data-ng-change="modelChangedFromInput()" />

  <button data-ng-click="buttonClick()">Set "model" to different value</button>

  {{model}}
  <hr />
  <h3>console <button data-ng-click="$root.logs = []">clear console</button>
  </h3>
  <ul>
    <li data-ng-repeat="log in $root.logs track by $index" data-ng-bind="log"></li>
  </ul>
</div>
Community
  • 1
  • 1
Cyril Gandon
  • 16,830
  • 14
  • 78
  • 122
  • Thanks for your answer! This is a nice solution for my question BUT the real case is a little bit complex. The model not changed by user interaction but in the controller by data which return from the server (with AJAX) So I can't use this trick. I was updated my question with a better example. – Mosh Feu Mar 02 '16 at 14:59