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

Commit

Permalink
fix(ngAnimate): skip animation on first render
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko authored and mhevery committed Apr 11, 2013
1 parent 5476cb6 commit 1351ba2
Show file tree
Hide file tree
Showing 8 changed files with 241 additions and 60 deletions.
33 changes: 30 additions & 3 deletions src/ng/animator.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
*
* The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
*
* Keep in mind that, by default, **all** initial animations will be skipped until the first digest cycle has fully
* passed. This helps prevent any unexpected animations from occurring while the application or directive is initializing. To
* override this behavior, you may pass "animateFirst: true" into the ngAnimate attribute expression.
*
* <h2>CSS-defined Animations</h2>
* By default, ngAnimate attaches two CSS3 classes per animation event to the DOM element to achieve the animation.
* This is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions.
Expand Down Expand Up @@ -140,6 +144,30 @@ var $AnimatorProvider = function() {
*/
var AnimatorService = function(scope, attrs) {
var ngAnimateAttr = attrs.ngAnimate;

// avoid running animations on start
var animationEnabled = false;
var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);

if (!animationEnabled) {
if(isObject(ngAnimateValue) && ngAnimateValue['animateFirst']) {
animationEnabled = true;
} else {
var enableSubsequent = function() {
removeWatch();
scope.$evalAsync(function() {
animationEnabled = true;
});
};
var removeWatch = noop;

if (scope.$$phase) {
enableSubsequent();
} else {
removeWatch = scope.$watch(enableSubsequent);
}
}
}
var animator = {};

/**
Expand Down Expand Up @@ -214,7 +242,6 @@ var $AnimatorProvider = function() {
return animator;

function animateActionFactory(type, beforeFn, afterFn) {
var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);
var className = ngAnimateAttr
? isObject(ngAnimateValue) ? ngAnimateValue[type] : ngAnimateValue + '-' + type
: '';
Expand All @@ -233,7 +260,8 @@ var $AnimatorProvider = function() {
var startClass = className + '-start';

return function(element, parent, after) {
if (!globalAnimationEnabled || !$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart) {
if (!animationEnabled || !globalAnimationEnabled ||
(!$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart)) {
beforeFn(element, parent, after);
afterFn(element, parent, after);
return;
Expand Down Expand Up @@ -268,7 +296,6 @@ var $AnimatorProvider = function() {
0,
duration);
});

$window.setTimeout(done, duration * 1000);
} else {
done();
Expand Down
4 changes: 3 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,9 @@ angular.mock.createMockWindow = function() {
if (setTimeoutQueue.length > 0) {
return {
process: function() {
setTimeoutQueue.shift().fn();
var tick = setTimeoutQueue.shift();
expect(tick.delay).toEqual(delay);
tick.fn();
}
};
} else {
Expand Down
30 changes: 27 additions & 3 deletions test/ng/animatorSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,21 @@

describe("$animator", function() {

var element;
var body, element;

function html(html) {
body.html(html);
element = body.children().eq(0);
return element;
}

beforeEach(function() {
// we need to run animation on attached elements;
body = jqLite(document.body);
});

afterEach(function(){
dealoc(element);
dealoc(body);
});

describe("enable / disable", function() {
Expand Down Expand Up @@ -120,6 +131,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{enter: \'custom\'}'
});
$rootScope.$digest(); // re-enable the animations;
expect(element.contents().length).toBe(0);
animator.enter(child, element);
window.setTimeout.expect(1).process();
Expand All @@ -129,6 +141,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{leave: \'custom\'}'
});
$rootScope.$digest();
element.append(child);
expect(element.contents().length).toBe(1);
animator.leave(child, element);
Expand All @@ -140,6 +153,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{move: \'custom\'}'
});
$rootScope.$digest();
var child1 = $compile('<div>1</div>')($rootScope);
var child2 = $compile('<div>2</div>')($rootScope);
element.append(child1);
Expand All @@ -154,6 +168,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{show: \'custom\'}'
});
$rootScope.$digest();
element.css('display','none');
expect(element.css('display')).toBe('none');
animator.show(element);
Expand All @@ -166,6 +181,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{hide: \'custom\'}'
});
$rootScope.$digest();
element.css('display','block');
expect(element.css('display')).toBe('block');
animator.hide(element);
Expand All @@ -181,6 +197,8 @@ describe("$animator", function() {
ngAnimate : '"custom"'
});

$rootScope.$digest();

//enter
animator.enter(child, element);
expect(child.attr('class')).toContain('custom-enter-setup');
Expand Down Expand Up @@ -222,6 +240,7 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{show: \'setup-memo\'}'
});
$rootScope.$digest();
expect(element.text()).toEqual('');
animator.show(element);
window.setTimeout.expect(1).process();
Expand All @@ -234,6 +253,8 @@ describe("$animator", function() {
animator = $animator($rootScope, {
ngAnimate : '{show: \'setup-memo\'}'
});
$rootScope.$digest();

element.text('123');
expect(element.text()).toBe('123');
animator.show(element);
Expand Down Expand Up @@ -262,11 +283,13 @@ describe("$animator", function() {
it("should skip animations if disabled and run when enabled",
inject(function($animator, $rootScope, $compile, $sniffer) {
$animator.enabled(false);
element = $compile('<div style="transition: 1s linear all">1</div>')($rootScope);
element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope);
var animator = $animator($rootScope, {
ngAnimate : '{show: \'inline-show\'}'
});

$rootScope.$digest(); // skip no-animate on first digest.

element.css('display','none');
expect(element.css('display')).toBe('none');
animator.show(element);
Expand All @@ -289,6 +312,7 @@ describe("$animator", function() {
it("should throw an error when an invalid ng-animate syntax is provided", inject(function($compile, $rootScope) {
expect(function() {
element = $compile('<div ng-repeat="i in is" ng-animate=":"></div>')($rootScope);
$rootScope.$digest();
}).toThrow("Syntax Error: Token ':' not a primary expression at column 1 of the expression [:] starting at [:].");
}));
});
31 changes: 24 additions & 7 deletions test/ng/directive/ngIncludeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,24 @@ describe('ngInclude', function() {
});

describe('ngInclude ngAnimate', function() {
var element, vendorPrefix, window;
var vendorPrefix, window;
var body, element;

function html(html) {
body.html(html);
element = body.children().eq(0);
return element;
}

beforeEach(function() {
// we need to run animation on attached elements;
body = jqLite(document.body);
});

afterEach(function(){
dealoc(body);
dealoc(element);
});

beforeEach(module(function($animationProvider, $provide) {
$provide.value('$window', window = angular.mock.createMockWindow());
Expand All @@ -300,12 +317,12 @@ describe('ngInclude ngAnimate', function() {

$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(
element = $compile(html(
'<div ' +
'ng-include="tpl" ' +
'ng-animate="{enter: \'custom-enter\'}">' +
'</div>'
)($rootScope);
))($rootScope);
$rootScope.$digest();

//if we add the custom css stuff here then it will get picked up before the animation takes place
Expand All @@ -332,12 +349,12 @@ describe('ngInclude ngAnimate', function() {
inject(function($compile, $rootScope, $templateCache, $sniffer) {
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(
element = $compile(html(
'<div ' +
'ng-include="tpl" ' +
'ng-animate="{leave: \'custom-leave\'}">' +
'</div>'
)($rootScope);
))($rootScope);
$rootScope.$digest();

//if we add the custom css stuff here then it will get picked up before the animation takes place
Expand Down Expand Up @@ -367,12 +384,12 @@ describe('ngInclude ngAnimate', function() {
inject(function($compile, $rootScope, $templateCache, $sniffer) {
$templateCache.put('enter', [200, '<div>data</div>', {}]);
$rootScope.tpl = 'enter';
element = $compile(
element = $compile(html(
'<div ' +
'ng-include="tpl" ' +
'ng-animate="{enter: \'custom-enter\'}">' +
'</div>'
)($rootScope);
))($rootScope);
$rootScope.$digest();

//if we add the custom css stuff here then it will get picked up before the animation takes place
Expand Down
43 changes: 30 additions & 13 deletions test/ng/directive/ngRepeatSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,24 @@ describe('ngRepeat', function() {
});

describe('ngRepeat ngAnimate', function() {
var element, vendorPrefix, window;
var vendorPrefix, window;
var body, element;

function html(html) {
body.html(html);
element = body.children().eq(0);
return element;
}

beforeEach(function() {
// we need to run animation on attached elements;
body = jqLite(document.body);
});

afterEach(function(){
dealoc(body);
dealoc(element);
});

beforeEach(module(function($animationProvider, $provide) {
$provide.value('$window', window = angular.mock.createMockWindow());
Expand All @@ -522,20 +539,18 @@ describe('ngRepeat ngAnimate', function() {
};
}));

afterEach(function(){
dealoc(element);
});

it('should fire off the enter animation + add and remove the css classes',
inject(function($compile, $rootScope, $sniffer) {

element = $compile(
element = $compile(html(
'<div><div ' +
'ng-repeat="item in items" ' +
'ng-animate="{enter: \'custom-enter\'}">' +
'{{ item }}' +
'</div></div>'
)($rootScope);
))($rootScope);

$rootScope.$digest(); // re-enable the animations;

$rootScope.items = ['1','2','3'];
$rootScope.$digest();
Expand Down Expand Up @@ -572,13 +587,13 @@ describe('ngRepeat ngAnimate', function() {
it('should fire off the leave animation + add and remove the css classes',
inject(function($compile, $rootScope, $sniffer) {

element = $compile(
element = $compile(html(
'<div><div ' +
'ng-repeat="item in items" ' +
'ng-animate="{leave: \'custom-leave\'}">' +
'{{ item }}' +
'</div></div>'
)($rootScope);
))($rootScope);

$rootScope.items = ['1','2','3'];
$rootScope.$digest();
Expand Down Expand Up @@ -612,13 +627,13 @@ describe('ngRepeat ngAnimate', function() {

it('should fire off the move animation + add and remove the css classes',
inject(function($compile, $rootScope, $sniffer) {
element = $compile(
element = $compile(html(
'<div>' +
'<div ng-repeat="item in items" ng-animate="{move: \'custom-move\'}">' +
'{{ item }}' +
'</div>' +
'</div>'
)($rootScope);
))($rootScope);

$rootScope.items = ['1','2','3'];
$rootScope.$digest();
Expand Down Expand Up @@ -666,13 +681,15 @@ describe('ngRepeat ngAnimate', function() {
it('should catch and use the correct duration for animation',
inject(function($compile, $rootScope, $sniffer) {

element = $compile(
element = $compile(html(
'<div><div ' +
'ng-repeat="item in items" ' +
'ng-animate="{enter: \'custom-enter\'}">' +
'{{ item }}' +
'</div></div>'
)($rootScope);
))($rootScope);

$rootScope.$digest(); // re-enable the animations;

$rootScope.items = ['a','b'];
$rootScope.$digest();
Expand Down
Loading

0 comments on commit 1351ba2

Please sign in to comment.