Skip to content

Commit ee23d70

Browse files
committed
fix(input): listen on "change" for radio and checkbox
input[radio] and inout[checkbox] now listen on the change event instead of the click event. This fixes issue with 3rd party libraries that trigger a change event on inputs, e.g. Bootstrap 3 custom checkbox / radio button toggles. It also makes it easier to prevent specific events that can cause a checkbox / radio to change, e.g. click events. Previously, this was difficult because the custom click handler had to be registered before the input directive's click handler. It is possible that radio and checkbox listened to click because IE8 has broken support for listening on change, see http://www.quirksmode.org/dom/events/change.html Closes angular#4516 Closes angular#14667 BREAKING CHANGE: input[radio] and input[checkbox] now need to be attached to the document to propagate events correctly. This should only be of concern in unit-tests that compile input elements and trigger click events on them. This is because we now listen to the change event which gets automatically triggered by browsers when a checkbox or radio is clicked. However, this may fail in some browsers when the elements are not attached to the document. Before: ```js it('should update the model', inject(function($compile, $rootScope) { var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope); browserTrigger(inputElm[0], 'click'); expect($rootScope.checkbox).toBe(true); }); ``` With this patch, `$rootScope.checkbox` might not be true, because the click event hasn't triggered the change event. To make the test, work append the inputElm to the app's $rootElement, and the $rootElement to the $document: After: ```js it('should update the model', inject(function($compile, $rootScope, $rootElement, $document) { var inputElm = $compile('<input type="checkbox" ng-model="checkbox" />')($rootScope); $rootElement.append(inputElm); $document.append($rootElement); browserTrigger(inputElm[0], 'click'); expect($rootScope.checkbox).toBe(true); }); ```
1 parent 4d5f5ff commit ee23d70

File tree

3 files changed

+26
-5
lines changed

3 files changed

+26
-5
lines changed

src/ng/directive/input.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1472,7 +1472,7 @@ function radioInputType(scope, element, attr, ctrl) {
14721472
}
14731473
};
14741474

1475-
element.on('click', listener);
1475+
element.on('change', listener);
14761476

14771477
ctrl.$render = function() {
14781478
var value = attr.value;
@@ -1505,7 +1505,7 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
15051505
ctrl.$setViewValue(element[0].checked, ev && ev.type);
15061506
};
15071507

1508-
element.on('click', listener);
1508+
element.on('change', listener);
15091509

15101510
ctrl.$render = function() {
15111511
element[0].checked = ctrl.$viewValue;

test/helpers/testabilityPatch.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ function generateInputCompilerHelper(helper) {
359359
};
360360
});
361361
});
362-
inject(function($compile, $rootScope, $sniffer) {
362+
inject(function($compile, $rootScope, $sniffer, $document, $rootElement) {
363363

364364
helper.compileInput = function(inputHtml, mockValidity, scope) {
365365

@@ -381,6 +381,9 @@ function generateInputCompilerHelper(helper) {
381381
// Compile the lot and return the input element
382382
$compile(helper.formElm)(scope);
383383

384+
$rootElement.append(helper.formElm);
385+
jqLite($document[0].body).append($rootElement);
386+
384387
spyOn(scope.form, '$addControl').and.callThrough();
385388
spyOn(scope.form, '$$renameControl').and.callThrough();
386389

test/ng/directive/inputSpec.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -2932,7 +2932,7 @@ describe('input', function() {
29322932

29332933
describe('radio', function() {
29342934

2935-
it('should update the model', function() {
2935+
they('should update the model on $prop event', ['click', 'change'], function(event) {
29362936
var inputElm = helper.compileInput(
29372937
'<input type="radio" ng-model="color" value="white" />' +
29382938
'<input type="radio" ng-model="color" value="red" />' +
@@ -2948,7 +2948,8 @@ describe('input', function() {
29482948
expect(inputElm[1].checked).toBe(true);
29492949
expect(inputElm[2].checked).toBe(false);
29502950

2951-
browserTrigger(inputElm[2], 'click');
2951+
if (event === 'change') inputElm[2].checked = true;
2952+
browserTrigger(inputElm[2], event);
29522953
expect($rootScope.color).toBe('blue');
29532954
});
29542955

@@ -3005,6 +3006,23 @@ describe('input', function() {
30053006
});
30063007

30073008

3009+
they('should update the model on $prop event', ['click', 'change'], function(event) {
3010+
var inputElm = helper.compileInput('<input type="checkbox" ng-model="checkbox" />');
3011+
3012+
expect(inputElm[0].checked).toBe(false);
3013+
3014+
$rootScope.$apply("checkbox = true");
3015+
expect(inputElm[0].checked).toBe(true);
3016+
3017+
$rootScope.$apply("checkbox = false");
3018+
expect(inputElm[0].checked).toBe(false);
3019+
3020+
if (event === 'change') inputElm[0].checked = true;
3021+
browserTrigger(inputElm[0], event);
3022+
expect($rootScope.checkbox).toBe(true);
3023+
});
3024+
3025+
30083026
it('should format booleans', function() {
30093027
var inputElm = helper.compileInput('<input type="checkbox" ng-model="name" />');
30103028

0 commit comments

Comments
 (0)