Skip to content

Commit 614891b

Browse files
committed
Merge branch 'master' of https://github.com/angular/angular.js into 6749
* 'master' of https://github.com/angular/angular.js: fix($rootScope): ng-repeat can't handle NaN values. angular#4605 test(ngMock): workaround issue with negative timestamps fix(select): avoid checking option element selected properties in render fix(orderBy): support string predicates containing non-ident characters fix(ngCookie): convert non-string values to string fix(ngTouch): update workaround for desktop Webkit quirk fix($httpBackend): don't error when JSONP callback called with no parameter fix($$RAFProvider): check for webkitCancelRequestAnimationFrame docs(tutorial/step_05): fix services link docs(tutorial/step_05): removed stray "a" style(ngMocks): remove ws feat(ngMock.$httpBackend): added support for function as URL matcher feat($compile): add support for $observer deregistration fix(Scope): $watchCollection should call listener with oldValue chore(log): add `log.empty()` method to the testing logger
2 parents 111ad4f + fb6062f commit 614891b

File tree

20 files changed

+449
-223
lines changed

20 files changed

+449
-223
lines changed

docs/content/tutorial/step_05.ngdoc

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88

99
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
10-
from our server using one of Angular's built-in {@link guide/dev_guide.services services} called {@link
10+
from our server using one of Angular's built-in {@link guide/services services} called {@link
1111
ng.$http $http}. We will use Angular's {@link guide/di dependency
1212
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
1313

@@ -20,7 +20,6 @@ You should now see a list of 20 phones.
2020
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-4...step-5):
2121

2222
## Data
23-
a
2423
The `app/phones/phones.json` file in your project is a dataset that contains a larger list of phones
2524
stored in the JSON format.
2625

src/ng/compile.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
775775
* @param {function(interpolatedValue)} fn Function that will be called whenever
776776
the interpolated value of the attribute changes.
777777
* See the {@link guide/directive#Attributes Directives} guide for more info.
778-
* @returns {function()} the `fn` parameter.
778+
* @returns {function()} Returns a deregistration function for this observer.
779779
*/
780780
$observe: function(key, fn) {
781781
var attrs = this,
@@ -789,7 +789,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
789789
fn(attrs[key]);
790790
}
791791
});
792-
return fn;
792+
793+
return function() {
794+
arrayRemove(listeners, fn);
795+
};
793796
}
794797
};
795798

src/ng/directive/select.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,12 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
394394
value = valueFn(scope, locals);
395395
}
396396
}
397+
// Update the null option's selected property here so $render cleans it up correctly
398+
if (optionGroupsCache[0].length > 1) {
399+
if (optionGroupsCache[0][1].id !== key) {
400+
optionGroupsCache[0][1].selected = false;
401+
}
402+
}
397403
}
398404
ctrl.$setViewValue(value);
399405
});
@@ -531,7 +537,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
531537
lastElement.val(existingOption.id = option.id);
532538
}
533539
// lastElement.prop('selected') provided by jQuery has side-effects
534-
if (lastElement[0].selected !== option.selected) {
540+
if (existingOption.selected !== option.selected) {
535541
lastElement.prop('selected', (existingOption.selected = option.selected));
536542
}
537543
} else {

src/ng/filter/orderBy.js

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ function orderByFilter($parse){
7474
predicate = predicate.substring(1);
7575
}
7676
get = $parse(predicate);
77+
if (get.constant) {
78+
var key = get();
79+
return reverseComparator(function(a,b) {
80+
return compare(a[key], b[key]);
81+
}, descending);
82+
}
7783
}
7884
return reverseComparator(function(a,b){
7985
return compare(get(a),get(b));

src/ng/httpBackend.js

+31-28
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,13 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
4949
var callbackId = '_' + (callbacks.counter++).toString(36);
5050
callbacks[callbackId] = function(data) {
5151
callbacks[callbackId].data = data;
52+
callbacks[callbackId].called = true;
5253
};
5354

5455
var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
55-
function() {
56-
if (callbacks[callbackId].data) {
57-
completeRequest(callback, 200, callbacks[callbackId].data);
58-
} else {
59-
completeRequest(callback, status || -2);
60-
}
61-
callbacks[callbackId] = angular.noop;
56+
callbackId, function(status, text) {
57+
completeRequest(callback, status, callbacks[callbackId].data, "", text);
58+
callbacks[callbackId] = noop;
6259
});
6360
} else {
6461

@@ -158,33 +155,39 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
158155
}
159156
};
160157

161-
function jsonpReq(url, done) {
158+
function jsonpReq(url, callbackId, done) {
162159
// we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
163160
// - fetches local scripts via XHR and evals them
164161
// - adds and immediately removes script elements from the document
165-
var script = rawDocument.createElement('script'),
166-
doneWrapper = function() {
167-
script.onreadystatechange = script.onload = script.onerror = null;
168-
rawDocument.body.removeChild(script);
169-
if (done) done();
170-
};
171-
172-
script.type = 'text/javascript';
162+
var script = rawDocument.createElement('script'), callback = null;
163+
script.type = "text/javascript";
173164
script.src = url;
174-
175-
if (msie && msie <= 8) {
176-
script.onreadystatechange = function() {
177-
if (/loaded|complete/.test(script.readyState)) {
178-
doneWrapper();
165+
script.async = true;
166+
167+
callback = function(event) {
168+
removeEventListenerFn(script, "load", callback);
169+
removeEventListenerFn(script, "error", callback);
170+
rawDocument.body.removeChild(script);
171+
script = null;
172+
var status = -1;
173+
var text = "unknown";
174+
175+
if (event) {
176+
if (event.type === "load" && !callbacks[callbackId].called) {
177+
event = { type: "error" };
179178
}
180-
};
181-
} else {
182-
script.onload = script.onerror = function() {
183-
doneWrapper();
184-
};
185-
}
179+
text = event.type;
180+
status = event.type === "error" ? 404 : 200;
181+
}
182+
183+
if (done) {
184+
done(status, text);
185+
}
186+
};
186187

188+
addEventListenerFn(script, "load", callback);
189+
addEventListenerFn(script, "error", callback);
187190
rawDocument.body.appendChild(script);
188-
return doneWrapper;
191+
return callback;
189192
}
190193
}

src/ng/raf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ function $$RAFProvider(){ //rAF
88

99
var cancelAnimationFrame = $window.cancelAnimationFrame ||
1010
$window.webkitCancelAnimationFrame ||
11-
$window.mozCancelAnimationFrame;
11+
$window.mozCancelAnimationFrame ||
12+
$window.webkitCancelRequestAnimationFrame;
1213

1314
var rafSupported = !!requestAnimationFrame;
1415
var raf = rafSupported

src/ng/rootScope.js

+46-9
Original file line numberDiff line numberDiff line change
@@ -398,30 +398,40 @@ function $RootScopeProvider(){
398398
* {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the
399399
* collection will trigger a call to the `listener`.
400400
*
401-
* @param {function(newCollection, oldCollection, scope)} listener a callback function that is
402-
* fired with both the `newCollection` and `oldCollection` as parameters.
403-
* The `newCollection` object is the newly modified data obtained from the `obj` expression
404-
* and the `oldCollection` object is a copy of the former collection data.
405-
* The `scope` refers to the current scope.
401+
* @param {function(newCollection, oldCollection, scope)} listener a callback function called
402+
* when a change is detected.
403+
* - The `newCollection` object is the newly modified data obtained from the `obj` expression
404+
* - The `oldCollection` object is a copy of the former collection data.
405+
* Due to performance considerations, the`oldCollection` value is computed only if the
406+
* `listener` function declares two or more arguments.
407+
* - The `scope` argument refers to the current scope.
406408
*
407409
* @returns {function()} Returns a de-registration function for this listener. When the
408410
* de-registration function is executed, the internal watch operation is terminated.
409411
*/
410412
$watchCollection: function(obj, listener) {
411413
var self = this;
412-
var oldValue;
414+
// the current value, updated on each dirty-check run
413415
var newValue;
416+
// a shallow copy of the newValue from the last dirty-check run,
417+
// updated to match newValue during dirty-check run
418+
var oldValue;
419+
// a shallow copy of the newValue from when the last change happened
420+
var veryOldValue;
421+
// only track veryOldValue if the listener is asking for it
422+
var trackVeryOldValue = (listener.length > 1);
414423
var changeDetected = 0;
415424
var objGetter = $parse(obj);
416425
var internalArray = [];
417426
var internalObject = {};
427+
var initRun = true;
418428
var oldLength = 0;
419429

420430
function $watchCollectionWatch() {
421431
newValue = objGetter(self);
422432
var newLength, key;
423433

424-
if (!isObject(newValue)) {
434+
if (!isObject(newValue)) { // if primitive
425435
if (oldValue !== newValue) {
426436
oldValue = newValue;
427437
changeDetected++;
@@ -443,7 +453,9 @@ function $RootScopeProvider(){
443453
}
444454
// copy the items to oldValue and look for changes.
445455
for (var i = 0; i < newLength; i++) {
446-
if (oldValue[i] !== newValue[i]) {
456+
var bothNaN = (oldValue[i] !== oldValue[i]) &&
457+
(newValue[i] !== newValue[i]);
458+
if (!bothNaN && (oldValue[i] !== newValue[i])) {
447459
changeDetected++;
448460
oldValue[i] = newValue[i];
449461
}
@@ -487,7 +499,32 @@ function $RootScopeProvider(){
487499
}
488500

489501
function $watchCollectionAction() {
490-
listener(newValue, oldValue, self);
502+
if (initRun) {
503+
initRun = false;
504+
listener(newValue, newValue, self);
505+
} else {
506+
listener(newValue, veryOldValue, self);
507+
}
508+
509+
// make a copy for the next time a collection is changed
510+
if (trackVeryOldValue) {
511+
if (!isObject(newValue)) {
512+
//primitive
513+
veryOldValue = newValue;
514+
} else if (isArrayLike(newValue)) {
515+
veryOldValue = new Array(newValue.length);
516+
for (var i = 0; i < newValue.length; i++) {
517+
veryOldValue[i] = newValue[i];
518+
}
519+
} else { // if object
520+
veryOldValue = {};
521+
for (var key in newValue) {
522+
if (hasOwnProperty.call(newValue, key)) {
523+
veryOldValue[key] = newValue[key];
524+
}
525+
}
526+
}
527+
}
491528
}
492529

493530
return this.$watch($watchCollectionWatch, $watchCollectionAction);

src/ngCookies/cookies.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,10 @@ angular.module('ngCookies', ['ng']).
9595
for(name in cookies) {
9696
value = cookies[name];
9797
if (!angular.isString(value)) {
98-
if (angular.isDefined(lastCookies[name])) {
99-
cookies[name] = lastCookies[name];
100-
} else {
101-
delete cookies[name];
102-
}
103-
} else if (value !== lastCookies[name]) {
98+
value = '' + value;
99+
cookies[name] = value;
100+
}
101+
if (value !== lastCookies[name]) {
104102
$browser.cookies(name, value);
105103
updated = true;
106104
}

0 commit comments

Comments
 (0)