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

Commit 4bca4c4

Browse files
committed
fix($animate): ensure guarded animations consider AJAX requests upon bootstrap
Prior to this fix when an Angular application is bootstrapped it would only place an animation guard to prevent animations from running when the application starts for the first two digest cycles. However, if any controllers or directives, that are executed upon boostrap, trigger any remote code to be downloaded (via $http) then the guard does not put that into consideration. This fix now properly addresses that circumstance and removes the guard once all outbound HTTP requests are complete when an Angular application is bootstrapped. Closes #8275 Closes #5262
1 parent a70e283 commit 4bca4c4

File tree

3 files changed

+82
-17
lines changed

3 files changed

+82
-17
lines changed

src/ngAnimate/animate.js

+39-13
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@
7373
* When the `on` expression value changes and an animation is triggered then each of the elements within
7474
* will all animate without the block being applied to child elements.
7575
*
76+
* ## Are animations run when the application starts?
77+
* No they are not. When an application is bootstrapped Angular will disable animations from running to avoid
78+
* a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work,
79+
* Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering
80+
* layout changes in the application will trigger animations as normal.
81+
*
82+
* In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular
83+
* will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests
84+
* are complete.
85+
*
7686
* <h2>CSS-defined Animations</h2>
7787
* The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
7888
* are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
@@ -396,24 +406,40 @@ angular.module('ngAnimate', ['ng'])
396406
}
397407

398408
$provide.decorator('$animate',
399-
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
400-
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
409+
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
410+
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
401411

402-
var globalAnimationCounter = 0;
403412
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
404413

405414
// disable animations during bootstrap, but once we bootstrapped, wait again
406-
// for another digest until enabling animations. The reason why we digest twice
407-
// is because all structural animations (enter, leave and move) all perform a
408-
// post digest operation before animating. If we only wait for a single digest
409-
// to pass then the structural animation would render its animation on page load.
410-
// (which is what we're trying to avoid when the application first boots up.)
411-
$rootScope.$$postDigest(function() {
412-
$rootScope.$$postDigest(function() {
413-
rootAnimateState.running = false;
414-
});
415-
});
415+
// for another digest until enabling animations. Enter, leave and move require
416+
// a follow-up digest so having a watcher here is enough to let both digests pass.
417+
// However, when any directive or view templates are downloaded then we need to
418+
// handle postpone enabling animations until they are fully completed and then...
419+
var watchFn = $rootScope.$watch(
420+
function() { return $templateRequest.totalPendingRequests; },
421+
function(val, oldVal) {
422+
if (oldVal === 0) {
423+
if (val === 0) {
424+
$rootScope.$$postDigest(onApplicationReady);
425+
}
426+
} else if(val === 0) {
427+
// ...when the template has been downloaded we digest twice again until the
428+
// animations are set to enabled (since enter, leave and move require a
429+
// follow-up).
430+
$rootScope.$$postDigest(function() {
431+
$rootScope.$$postDigest(onApplicationReady);
432+
});
433+
}
434+
}
435+
);
416436

437+
function onApplicationReady() {
438+
rootAnimateState.running = false;
439+
watchFn();
440+
}
441+
442+
var globalAnimationCounter = 0;
417443
var classNameFilter = $animateProvider.classNameFilter();
418444
var isAnimatableClassName = !classNameFilter
419445
? function() { return true; }

test/ng/directive/ngClassSpec.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,7 @@ describe('ngClass animations', function() {
427427
});
428428
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $document) {
429429

430-
// Enable animations by triggering the first item in the postDigest queue
431-
digestQueue.shift()();
432-
433-
// wait for the 2nd animation bootstrap digest to pass
430+
// Animations are enabled right away since there are no remote HTTP template requests
434431
$rootScope.$digest();
435432
digestQueue.shift()();
436433

test/ngAnimate/animateSpec.js

+42
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,48 @@ describe("ngAnimate", function() {
5050
});
5151
});
5252

53+
it("should disable animations for two digests until all pending HTTP requests are complete during bootstrap", function() {
54+
var animateSpy = jasmine.createSpy();
55+
module(function($animateProvider, $compileProvider) {
56+
$compileProvider.directive('myRemoteDirective', function() {
57+
return {
58+
templateUrl : 'remote.html'
59+
};
60+
});
61+
$animateProvider.register('.my-structrual-animation', function() {
62+
return {
63+
enter : animateSpy,
64+
leave : animateSpy
65+
};
66+
});
67+
});
68+
inject(function($rootScope, $compile, $animate, $rootElement, $document, $httpBackend) {
69+
70+
$httpBackend.whenGET('remote.html').respond(200, '<strong>content</strong>');
71+
72+
var element = $compile('<div my-remote-directive class="my-structrual-animation">...</div>')($rootScope);
73+
$rootElement.append(element);
74+
jqLite($document[0].body).append($rootElement);
75+
76+
// running this twice just to prove that the dual post digest is run
77+
$rootScope.$digest();
78+
$rootScope.$digest();
79+
80+
$animate.enter(element, $rootElement);
81+
$rootScope.$digest();
82+
83+
expect(animateSpy).not.toHaveBeenCalled();
84+
85+
$httpBackend.flush();
86+
$rootScope.$digest();
87+
88+
$animate.leave(element);
89+
$rootScope.$digest();
90+
91+
expect(animateSpy).toHaveBeenCalled();
92+
});
93+
});
94+
5395

5496
//we use another describe block because the before/after operations below
5597
//are used across all animations tests and we don't want that same behavior

0 commit comments

Comments
 (0)