mirror of
https://github.com/zhigang1992/angular.js.git
synced 2026-04-22 10:49:24 +08:00
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
This commit is contained in:
@@ -73,6 +73,16 @@
|
||||
* When the `on` expression value changes and an animation is triggered then each of the elements within
|
||||
* will all animate without the block being applied to child elements.
|
||||
*
|
||||
* ## Are animations run when the application starts?
|
||||
* No they are not. When an application is bootstrapped Angular will disable animations from running to avoid
|
||||
* a frenzy of animations from being triggered as soon as the browser has rendered the screen. For this to work,
|
||||
* Angular will wait for two digest cycles until enabling animations. From there on, any animation-triggering
|
||||
* layout changes in the application will trigger animations as normal.
|
||||
*
|
||||
* In addition, upon bootstrap, if the routing system or any directives or load remote data (via $http) then Angular
|
||||
* will automatically extend the wait time to enable animations once **all** of the outbound HTTP requests
|
||||
* are complete.
|
||||
*
|
||||
* <h2>CSS-defined Animations</h2>
|
||||
* The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
|
||||
* 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'])
|
||||
}
|
||||
|
||||
$provide.decorator('$animate',
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document) {
|
||||
['$delegate', '$$q', '$injector', '$sniffer', '$rootElement', '$$asyncCallback', '$rootScope', '$document', '$templateRequest',
|
||||
function($delegate, $$q, $injector, $sniffer, $rootElement, $$asyncCallback, $rootScope, $document, $templateRequest) {
|
||||
|
||||
var globalAnimationCounter = 0;
|
||||
$rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
|
||||
|
||||
// disable animations during bootstrap, but once we bootstrapped, wait again
|
||||
// for another digest until enabling animations. The reason why we digest twice
|
||||
// is because all structural animations (enter, leave and move) all perform a
|
||||
// post digest operation before animating. If we only wait for a single digest
|
||||
// to pass then the structural animation would render its animation on page load.
|
||||
// (which is what we're trying to avoid when the application first boots up.)
|
||||
$rootScope.$$postDigest(function() {
|
||||
$rootScope.$$postDigest(function() {
|
||||
rootAnimateState.running = false;
|
||||
});
|
||||
});
|
||||
// for another digest until enabling animations. Enter, leave and move require
|
||||
// a follow-up digest so having a watcher here is enough to let both digests pass.
|
||||
// However, when any directive or view templates are downloaded then we need to
|
||||
// handle postpone enabling animations until they are fully completed and then...
|
||||
var watchFn = $rootScope.$watch(
|
||||
function() { return $templateRequest.totalPendingRequests; },
|
||||
function(val, oldVal) {
|
||||
if (oldVal === 0) {
|
||||
if (val === 0) {
|
||||
$rootScope.$$postDigest(onApplicationReady);
|
||||
}
|
||||
} else if(val === 0) {
|
||||
// ...when the template has been downloaded we digest twice again until the
|
||||
// animations are set to enabled (since enter, leave and move require a
|
||||
// follow-up).
|
||||
$rootScope.$$postDigest(function() {
|
||||
$rootScope.$$postDigest(onApplicationReady);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function onApplicationReady() {
|
||||
rootAnimateState.running = false;
|
||||
watchFn();
|
||||
}
|
||||
|
||||
var globalAnimationCounter = 0;
|
||||
var classNameFilter = $animateProvider.classNameFilter();
|
||||
var isAnimatableClassName = !classNameFilter
|
||||
? function() { return true; }
|
||||
|
||||
@@ -427,10 +427,7 @@ describe('ngClass animations', function() {
|
||||
});
|
||||
inject(function($compile, $rootScope, $browser, $rootElement, $animate, $timeout, $document) {
|
||||
|
||||
// Enable animations by triggering the first item in the postDigest queue
|
||||
digestQueue.shift()();
|
||||
|
||||
// wait for the 2nd animation bootstrap digest to pass
|
||||
// Animations are enabled right away since there are no remote HTTP template requests
|
||||
$rootScope.$digest();
|
||||
digestQueue.shift()();
|
||||
|
||||
|
||||
@@ -50,6 +50,48 @@ describe("ngAnimate", function() {
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable animations for two digests until all pending HTTP requests are complete during bootstrap", function() {
|
||||
var animateSpy = jasmine.createSpy();
|
||||
module(function($animateProvider, $compileProvider) {
|
||||
$compileProvider.directive('myRemoteDirective', function() {
|
||||
return {
|
||||
templateUrl : 'remote.html'
|
||||
};
|
||||
});
|
||||
$animateProvider.register('.my-structrual-animation', function() {
|
||||
return {
|
||||
enter : animateSpy,
|
||||
leave : animateSpy
|
||||
};
|
||||
});
|
||||
});
|
||||
inject(function($rootScope, $compile, $animate, $rootElement, $document, $httpBackend) {
|
||||
|
||||
$httpBackend.whenGET('remote.html').respond(200, '<strong>content</strong>');
|
||||
|
||||
var element = $compile('<div my-remote-directive class="my-structrual-animation">...</div>')($rootScope);
|
||||
$rootElement.append(element);
|
||||
jqLite($document[0].body).append($rootElement);
|
||||
|
||||
// running this twice just to prove that the dual post digest is run
|
||||
$rootScope.$digest();
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.enter(element, $rootElement);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(animateSpy).not.toHaveBeenCalled();
|
||||
|
||||
$httpBackend.flush();
|
||||
$rootScope.$digest();
|
||||
|
||||
$animate.leave(element);
|
||||
$rootScope.$digest();
|
||||
|
||||
expect(animateSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
//we use another describe block because the before/after operations below
|
||||
//are used across all animations tests and we don't want that same behavior
|
||||
|
||||
Reference in New Issue
Block a user