From bf69ca0a45a7723eb22320d1cec3f429584c8f82 Mon Sep 17 00:00:00 2001
From: Gonzalo Ruiz de Villa <gonzalo.ruizdevilla@adesis.com>
Date: Tue, 3 Sep 2013 17:12:09 -0500
Subject: [PATCH 1/3] fix(rootScope): $watchCollection returns in oldCollection
 a copy of the former array data

related to #1751
When watching arrays, $watchCollection returned the new data both in the newCollection
and the oldCollection arguments of the listener
---
 src/ng/rootScope.js      |  6 +++++-
 test/ng/rootScopeSpec.js | 22 ++++++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js
index d3b2762d8c01..22e7787c9707 100644
--- a/src/ng/rootScope.js
+++ b/src/ng/rootScope.js
@@ -362,6 +362,7 @@ function $RootScopeProvider(){
       $watchCollection: function(obj, listener) {
         var self = this;
         var oldValue;
+        var oldArray;
         var newValue;
         var changeDetected = 0;
         var objGetter = $parse(obj);
@@ -371,6 +372,7 @@ function $RootScopeProvider(){
 
         function $watchCollectionWatch() {
           newValue = objGetter(self);
+          oldArray = null;
           var newLength, key;
 
           if (!isObject(newValue)) {
@@ -386,6 +388,8 @@ function $RootScopeProvider(){
               changeDetected++;
             }
 
+            oldArray = oldValue.slice(0);
+
             newLength = newValue.length;
 
             if (oldLength !== newLength) {
@@ -439,7 +443,7 @@ function $RootScopeProvider(){
         }
 
         function $watchCollectionAction() {
-          listener(newValue, oldValue, self);
+          listener(newValue, oldArray || oldValue, self);
         }
 
         return this.$watch($watchCollectionWatch, $watchCollectionAction);
diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js
index 656385e9681c..83b49bead4c1 100644
--- a/test/ng/rootScopeSpec.js
+++ b/test/ng/rootScopeSpec.js
@@ -510,6 +510,28 @@ describe('Scope', function() {
           $rootScope.$digest();
           expect(arrayLikelog).toEqual(['x', 'y']);
         });
+
+        it('should return a new array with old values', function(){
+          var watchArgs;
+          $rootScope.$watchCollection('obj', function (newValues, oldValues) {
+            watchArgs = {
+              newValues: newValues,
+              oldValues: oldValues
+            };
+          });
+
+          $rootScope.obj = ['a'];
+          $rootScope.$digest();
+
+          expect(watchArgs.newValues).toEqual($rootScope.obj);
+          expect(watchArgs.oldValues).toEqual([]);
+
+          $rootScope.obj.push('b');
+          $rootScope.$digest();
+
+          expect(watchArgs.newValues).toEqual(['a', 'b']);
+          expect(watchArgs.oldValues).toEqual(['a']);
+        })
       });
 
 

From 5d1e2e2f0eb602fb80b67038c20024e05e13826d Mon Sep 17 00:00:00 2001
From: Gonzalo Ruiz de Villa <gonzalo.ruizdevilla@adesis.com>
Date: Tue, 3 Sep 2013 19:08:29 -0500
Subject: [PATCH 2/3] fix(select): select multiple now uses watchCollection on
 attr.ngModel

With the fix on watchCollection, this code needed to be updated to run
all previous tests on select.
---
 src/ng/directive/select.js | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js
index a62f706c52da..296491bcef4f 100644
--- a/src/ng/directive/select.js
+++ b/src/ng/directive/select.js
@@ -266,7 +266,6 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {
       }
 
       function Multiple(scope, selectElement, ctrl) {
-        var lastView;
         ctrl.$render = function() {
           var items = new HashMap(ctrl.$viewValue);
           forEach(selectElement.find('option'), function(option) {
@@ -274,13 +273,10 @@ var selectDirective = ['$compile', '$parse', function($compile,   $parse) {
           });
         };
 
-        // we have to do it on each watch since ngModel watches reference, but
-        // we need to work of an array, so we need to see if anything was inserted/removed
-        scope.$watch(function selectMultipleWatch() {
-          if (!equals(lastView, ctrl.$viewValue)) {
-            lastView = copy(ctrl.$viewValue);
-            ctrl.$render();
-          }
+        scope.$watchCollection(attr.ngModel, function selectMultipleWatch(newValue, oldValue) {
+          ctrl.$viewValue = newValue;
+          requiredValidator && requiredValidator(newValue);
+          ctrl.$render();
         });
 
         selectElement.on('change', function() {

From fc9171b484f99babcf54c4261d98187add9441bc Mon Sep 17 00:00:00 2001
From: Gonzalo Ruiz de Villa <gonzalo.ruizdevilla@adesis.com>
Date: Tue, 3 Sep 2013 19:16:53 -0500
Subject: [PATCH 3/3] fix(input): ngList is updated when array model values are
 changed
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

fixes #1751

When the model referenced the same same array and the array values
where changed, the list wasn't updated.
Now watchCollection is used to detect changes in NgModelController.

isEmpty now detects empty arrays

BEHAVIOUR CHANGE
There is a change in the behaviour of ngList when typing a list.
When “a , b” is typed is automatically changed to “a, b”.
---
 src/ng/directive/input.js      |  8 +++++---
 test/ng/directive/inputSpec.js | 24 +++++++++++++++++++++++-
 2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 6f3919dfb174..a4e09f0dc588 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -385,7 +385,9 @@ var inputType = {
 
 
 function isEmpty(value) {
-  return isUndefined(value) || value === '' || value === null || value !== value;
+  return isUndefined(value) || value === '' || value === null ||
+    (value.length === 0 && isArrayLike(value)) ||
+    value !== value;
 }
 
 
@@ -1082,11 +1084,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
   // model -> value
   var ctrl = this;
 
-  $scope.$watch(function ngModelWatch() {
+  $scope.$watchCollection($attr.ngModel, function ngModelWatch(newValue, oldValue) {
     var value = ngModelGet($scope);
 
     // if scope model value and ngModel value are out of sync
-    if (ctrl.$modelValue !== value) {
+    if (!equals(ctrl.$modelValue, oldValue)) {
 
       var formatters = ctrl.$formatters,
           idx = formatters.length;
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index facc2b806bc9..814c72ec9a3a 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -986,7 +986,7 @@ describe('input', function() {
       expect(scope.list).toEqual(['a']);
 
       changeInputValueTo('a , b');
-      expect(inputElm.val()).toEqual('a , b');
+      expect(inputElm.val()).toEqual('a, b');
       expect(scope.list).toEqual(['a', 'b']);
     });
 
@@ -1019,6 +1019,28 @@ describe('input', function() {
       changeInputValueTo('a,b: c');
       expect(scope.list).toEqual(['a', 'b', 'c']);
     });
+
+    it("should detect changes in the values of an array", function () {
+      var list = ['x', 'y', 'z'];
+      compileInput('<input type="text" ng-model="list" ng-list />');
+      scope.$apply(function() {
+        scope.list = list;
+      });
+      expect(inputElm.val()).toBe('x, y, z');
+      scope.$apply(function() {
+        list.unshift('w');
+      });
+      expect(inputElm.val()).toBe('w, x, y, z');
+    });
+
+    it('should be invalid if empty', function() {
+      compileInput('<input name="namesInput" ng-model="list" ng-list required/>');
+      changeInputValueTo('a');
+      expect(inputElm).toBeValid();
+      changeInputValueTo('');
+      expect(inputElm).toBeInvalid();
+    });
+
   });
 
   describe('required', function() {