angular watch object

by Max Kovalov

HTML

<div class='test' ng-app="app" ng-controller="TestWatch">
  prop1: {{prop1}} <br>
  prop2: {{prop2}} <br>
  prop3 (unwatched): {{prop3}} <br>
  <button ng-click="test1()">
    Simple props change
  </button>
  <button ng-click="test2()">
    Async props change
  </button>
  <button ng-click="test3()">
    Async props change with apply
  </button>
</div>

JavaScript

// TEST app code

var app = angular.module('app', ['watch_utils']);

app.controller('TestWatch', ['$scope', 'TestService', 'WatchObj', TestWatchCtrl]);
function TestWatchCtrl($scope, testService, watch){
	$scope.prop1 = testService.prop1;
  $scope.prop2 = testService.prop2;
  $scope.prop3 = testService.prop3;
	watch(testService, ['prop1', 'prop2'], $scope, $scope);
  $scope.test1 = function(){
  	testService.test1();
  };
  $scope.test2 = function(){
  	testService.test2();
  };
  $scope.test3 = function(){
  	testService.test3();
  };
}

app.service('TestService', ['apply', TestService]);
function TestService(apply){
	this.apply = apply;
	this.reset();
}
TestService.prototype.reset = function(){
	this.prop1 = 'unchenged';
  this.prop2 = 'unchenged2';
  this.prop3 = 'unchenged3';
}
TestService.prototype.test1 = function(){
	this.prop1 = 'changed_test_1';
  this.prop2 = 'changed2_test_1';
  this.prop3 = 'changed3_test_1';
}
TestService.prototype.test2 = function(){
	setTimeout(function(){
    this.prop1 = 'changed_test_2';
    this.prop2 = 'changed2_test_2';
    this.prop3 = 'changed3_test_2';
  }.bind(this));
}
TestService.prototype.test3 = function(){
	setTimeout(function(){
    this.prop1 = 'changed_test_3';
    this.prop2 = 'changed2_test_3';
    this.prop3 = 'changed3_test_3';
    this.apply();
  }.bind(this));
}
//END TEST APP CODE

//WATCH UTILS
var mod = angular.module('watch_utils', []);

mod.service('apply', ['$timeout', ApplyService]);
function ApplyService($timeout){
    return function apply(){
        $timeout(function(){});
    };
}

mod.service('WatchObj', ['$rootScope', WatchObjService]);
function WatchObjService($rootScope){
		// target not always equals $scope, for example when using bindToController syntax in directives
    return function watch_obj(obj, fields, target, $scope){
    		// if $scope is not provided, $rootScope is used
        $scope = $scope || $rootScope;
        var watched = fields.map(function(field){
            return...