Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 9a49432

Browse files
committed
feat(ngModel): support object equality watch via ngModelOptions
1 parent b34769e commit 9a49432

File tree

2 files changed

+81
-25
lines changed

2 files changed

+81
-25
lines changed

src/ng/directive/ngModel.js

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -806,43 +806,67 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
806806
}
807807
};
808808

809-
// model -> value
810-
// Note: we cannot use a normal scope.$watch as we want to detect the following:
811-
// 1. scope value is 'a'
812-
// 2. user enters 'b'
813-
// 3. ng-change kicks in and reverts scope value to 'a'
814-
// -> scope value did not change since the last digest as
815-
// ng-change executes in apply phase
816-
// 4. view should be changed back to 'a'
817-
$scope.$watch(function ngModelWatch() {
809+
this.$$setupModelWatch = function() {
810+
// model -> value
811+
// Note: we cannot use a normal scope.$watch as we want to detect the following:
812+
// 1. scope value is 'a'
813+
// 2. user enters 'b'
814+
// 3. ng-change kicks in and reverts scope value to 'a'
815+
// -> scope value did not change since the last digest as
816+
// ng-change executes in apply phase
817+
// 4. view should be changed back to 'a'
818+
819+
// options.deepWatch
820+
// options.collection
821+
822+
getModelValueSetter();
823+
$scope.$watch(ngModelWatch);
824+
};
825+
826+
var modelValueSetter = function modelValueSetterDefault (modelValue) {
827+
return modelValue;
828+
};
829+
830+
function getModelValueSetter() {
831+
if (ctrl.$options && ctrl.$options.deepWatch) {
832+
modelValueSetter = function modelValueSetterCopy (modelValue) {
833+
return copy(modelValue);
834+
};
835+
}
836+
}
837+
838+
function ngModelWatch() {
818839
var modelValue = ngModelGet($scope);
819840

820841
// if scope model value and ngModel value are out of sync
821-
// TODO(perf): why not move this to the action fn?
822842
if (modelValue !== ctrl.$modelValue &&
823843
// checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
824844
(ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
825845
) {
826-
ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
827-
parserValid = undefined;
846+
modelToViewAction(modelValue);
847+
}
848+
return modelValue;
849+
}
828850

829-
var formatters = ctrl.$formatters,
830-
idx = formatters.length;
851+
function modelToViewAction(modelValue) {
852+
ctrl.$modelValue = ctrl.$$rawModelValue = modelValueSetter(modelValue);
853+
parserValid = undefined;
831854

832-
var viewValue = modelValue;
833-
while (idx--) {
834-
viewValue = formatters[idx](viewValue);
835-
}
836-
if (ctrl.$viewValue !== viewValue) {
837-
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
838-
ctrl.$render();
855+
var formatters = ctrl.$formatters,
856+
idx = formatters.length;
839857

840-
ctrl.$$runValidators(modelValue, viewValue, noop);
841-
}
858+
var viewValue = modelValue;
859+
while (idx--) {
860+
viewValue = formatters[idx](viewValue);
842861
}
862+
if (ctrl.$viewValue !== viewValue) {
863+
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
864+
ctrl.$render();
865+
866+
ctrl.$$runValidators(modelValue, viewValue, noop);
867+
}
868+
}
843869

844-
return modelValue;
845-
});
846870
}];
847871

848872

@@ -1032,6 +1056,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
10321056
formCtrl = ctrls[1] || modelCtrl.$$parentForm;
10331057

10341058
modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
1059+
modelCtrl.$$setupModelWatch();
10351060

10361061
// notify others, especially parent forms
10371062
formCtrl.$addControl(modelCtrl);

test/ng/directive/ngModelSpec.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ describe('ngModel', function() {
3030

3131
//Assign the mocked parentFormCtrl to the model controller
3232
ctrl.$$parentForm = parentFormCtrl;
33+
ctrl.$$setupModelWatch();
3334
}));
3435

3536

@@ -1790,6 +1791,19 @@ describe('ngModelOptions attributes', function() {
17901791

17911792
var helper, $rootScope, $compile, $timeout, $q;
17921793

1794+
beforeEach(module(function($compileProvider) {
1795+
$compileProvider.directive('formatObject', function() {
1796+
return {
1797+
require: 'ngModel',
1798+
link: function(scope, element, attrs, ngModelCtrl) {
1799+
ngModelCtrl.$formatters.push(function(value) {
1800+
return value.a + value.b;
1801+
});
1802+
}
1803+
};
1804+
});
1805+
}));
1806+
17931807
beforeEach(function() {
17941808
helper = getInputCompileHelper(this);
17951809
});
@@ -2402,4 +2416,21 @@ describe('ngModelOptions attributes', function() {
24022416
expect($rootScope.value).toBe('modelValue');
24032417
expect($rootScope.changed).toHaveBeenCalledOnce();
24042418
});
2419+
2420+
2421+
it('should watch the model with object equality if deepWatch is true', function() {
2422+
$rootScope.value = {
2423+
a: 'alpha',
2424+
b: 'beta',
2425+
};
2426+
2427+
var input = helper.compileInput('<input type="text" format-object ng-model="value" ' +
2428+
'ng-model-options="{deepWatch: true}" >');
2429+
2430+
expect(input.val()).toBe('alphabeta');
2431+
2432+
$rootScope.value.b = 'gamma';
2433+
$rootScope.$digest();
2434+
expect(input.val()).toBe('alphagamma');
2435+
});
24052436
});

0 commit comments

Comments
 (0)