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

Commit 1351ba2

Browse files
matskomhevery
authored andcommitted
fix(ngAnimate): skip animation on first render
1 parent 5476cb6 commit 1351ba2

8 files changed

+241
-60
lines changed

src/ng/animator.js

+30-3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
*
4141
* The `event1` and `event2` attributes refer to the animation events specific to the directive that has been assigned.
4242
*
43+
* Keep in mind that, by default, **all** initial animations will be skipped until the first digest cycle has fully
44+
* passed. This helps prevent any unexpected animations from occurring while the application or directive is initializing. To
45+
* override this behavior, you may pass "animateFirst: true" into the ngAnimate attribute expression.
46+
*
4347
* <h2>CSS-defined Animations</h2>
4448
* By default, ngAnimate attaches two CSS3 classes per animation event to the DOM element to achieve the animation.
4549
* This is up to you, the developer, to ensure that the animations take place using cross-browser CSS3 transitions.
@@ -140,6 +144,30 @@ var $AnimatorProvider = function() {
140144
*/
141145
var AnimatorService = function(scope, attrs) {
142146
var ngAnimateAttr = attrs.ngAnimate;
147+
148+
// avoid running animations on start
149+
var animationEnabled = false;
150+
var ngAnimateValue = ngAnimateAttr && scope.$eval(ngAnimateAttr);
151+
152+
if (!animationEnabled) {
153+
if(isObject(ngAnimateValue) && ngAnimateValue['animateFirst']) {
154+
animationEnabled = true;
155+
} else {
156+
var enableSubsequent = function() {
157+
removeWatch();
158+
scope.$evalAsync(function() {
159+
animationEnabled = true;
160+
});
161+
};
162+
var removeWatch = noop;
163+
164+
if (scope.$$phase) {
165+
enableSubsequent();
166+
} else {
167+
removeWatch = scope.$watch(enableSubsequent);
168+
}
169+
}
170+
}
143171
var animator = {};
144172

145173
/**
@@ -214,7 +242,6 @@ var $AnimatorProvider = function() {
214242
return animator;
215243

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

235262
return function(element, parent, after) {
236-
if (!globalAnimationEnabled || !$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart) {
263+
if (!animationEnabled || !globalAnimationEnabled ||
264+
(!$sniffer.supportsTransitions && !polyfillSetup && !polyfillStart)) {
237265
beforeFn(element, parent, after);
238266
afterFn(element, parent, after);
239267
return;
@@ -268,7 +296,6 @@ var $AnimatorProvider = function() {
268296
0,
269297
duration);
270298
});
271-
272299
$window.setTimeout(done, duration * 1000);
273300
} else {
274301
done();

src/ngMock/angular-mocks.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -628,7 +628,9 @@ angular.mock.createMockWindow = function() {
628628
if (setTimeoutQueue.length > 0) {
629629
return {
630630
process: function() {
631-
setTimeoutQueue.shift().fn();
631+
var tick = setTimeoutQueue.shift();
632+
expect(tick.delay).toEqual(delay);
633+
tick.fn();
632634
}
633635
};
634636
} else {

test/ng/animatorSpec.js

+27-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@
22

33
describe("$animator", function() {
44

5-
var element;
5+
var body, element;
6+
7+
function html(html) {
8+
body.html(html);
9+
element = body.children().eq(0);
10+
return element;
11+
}
12+
13+
beforeEach(function() {
14+
// we need to run animation on attached elements;
15+
body = jqLite(document.body);
16+
});
617

718
afterEach(function(){
8-
dealoc(element);
19+
dealoc(body);
920
});
1021

1122
describe("enable / disable", function() {
@@ -120,6 +131,7 @@ describe("$animator", function() {
120131
animator = $animator($rootScope, {
121132
ngAnimate : '{enter: \'custom\'}'
122133
});
134+
$rootScope.$digest(); // re-enable the animations;
123135
expect(element.contents().length).toBe(0);
124136
animator.enter(child, element);
125137
window.setTimeout.expect(1).process();
@@ -129,6 +141,7 @@ describe("$animator", function() {
129141
animator = $animator($rootScope, {
130142
ngAnimate : '{leave: \'custom\'}'
131143
});
144+
$rootScope.$digest();
132145
element.append(child);
133146
expect(element.contents().length).toBe(1);
134147
animator.leave(child, element);
@@ -140,6 +153,7 @@ describe("$animator", function() {
140153
animator = $animator($rootScope, {
141154
ngAnimate : '{move: \'custom\'}'
142155
});
156+
$rootScope.$digest();
143157
var child1 = $compile('<div>1</div>')($rootScope);
144158
var child2 = $compile('<div>2</div>')($rootScope);
145159
element.append(child1);
@@ -154,6 +168,7 @@ describe("$animator", function() {
154168
animator = $animator($rootScope, {
155169
ngAnimate : '{show: \'custom\'}'
156170
});
171+
$rootScope.$digest();
157172
element.css('display','none');
158173
expect(element.css('display')).toBe('none');
159174
animator.show(element);
@@ -166,6 +181,7 @@ describe("$animator", function() {
166181
animator = $animator($rootScope, {
167182
ngAnimate : '{hide: \'custom\'}'
168183
});
184+
$rootScope.$digest();
169185
element.css('display','block');
170186
expect(element.css('display')).toBe('block');
171187
animator.hide(element);
@@ -181,6 +197,8 @@ describe("$animator", function() {
181197
ngAnimate : '"custom"'
182198
});
183199

200+
$rootScope.$digest();
201+
184202
//enter
185203
animator.enter(child, element);
186204
expect(child.attr('class')).toContain('custom-enter-setup');
@@ -222,6 +240,7 @@ describe("$animator", function() {
222240
animator = $animator($rootScope, {
223241
ngAnimate : '{show: \'setup-memo\'}'
224242
});
243+
$rootScope.$digest();
225244
expect(element.text()).toEqual('');
226245
animator.show(element);
227246
window.setTimeout.expect(1).process();
@@ -234,6 +253,8 @@ describe("$animator", function() {
234253
animator = $animator($rootScope, {
235254
ngAnimate : '{show: \'setup-memo\'}'
236255
});
256+
$rootScope.$digest();
257+
237258
element.text('123');
238259
expect(element.text()).toBe('123');
239260
animator.show(element);
@@ -262,11 +283,13 @@ describe("$animator", function() {
262283
it("should skip animations if disabled and run when enabled",
263284
inject(function($animator, $rootScope, $compile, $sniffer) {
264285
$animator.enabled(false);
265-
element = $compile('<div style="transition: 1s linear all">1</div>')($rootScope);
286+
element = $compile(html('<div style="' + vendorPrefix + 'transition: 1s linear all">1</div>'))($rootScope);
266287
var animator = $animator($rootScope, {
267288
ngAnimate : '{show: \'inline-show\'}'
268289
});
269290

291+
$rootScope.$digest(); // skip no-animate on first digest.
292+
270293
element.css('display','none');
271294
expect(element.css('display')).toBe('none');
272295
animator.show(element);
@@ -289,6 +312,7 @@ describe("$animator", function() {
289312
it("should throw an error when an invalid ng-animate syntax is provided", inject(function($compile, $rootScope) {
290313
expect(function() {
291314
element = $compile('<div ng-repeat="i in is" ng-animate=":"></div>')($rootScope);
315+
$rootScope.$digest();
292316
}).toThrow("Syntax Error: Token ':' not a primary expression at column 1 of the expression [:] starting at [:].");
293317
}));
294318
});

test/ng/directive/ngIncludeSpec.js

+24-7
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,24 @@ describe('ngInclude', function() {
282282
});
283283

284284
describe('ngInclude ngAnimate', function() {
285-
var element, vendorPrefix, window;
285+
var vendorPrefix, window;
286+
var body, element;
287+
288+
function html(html) {
289+
body.html(html);
290+
element = body.children().eq(0);
291+
return element;
292+
}
293+
294+
beforeEach(function() {
295+
// we need to run animation on attached elements;
296+
body = jqLite(document.body);
297+
});
298+
299+
afterEach(function(){
300+
dealoc(body);
301+
dealoc(element);
302+
});
286303

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

301318
$templateCache.put('enter', [200, '<div>data</div>', {}]);
302319
$rootScope.tpl = 'enter';
303-
element = $compile(
320+
element = $compile(html(
304321
'<div ' +
305322
'ng-include="tpl" ' +
306323
'ng-animate="{enter: \'custom-enter\'}">' +
307324
'</div>'
308-
)($rootScope);
325+
))($rootScope);
309326
$rootScope.$digest();
310327

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

343360
//if we add the custom css stuff here then it will get picked up before the animation takes place
@@ -367,12 +384,12 @@ describe('ngInclude ngAnimate', function() {
367384
inject(function($compile, $rootScope, $templateCache, $sniffer) {
368385
$templateCache.put('enter', [200, '<div>data</div>', {}]);
369386
$rootScope.tpl = 'enter';
370-
element = $compile(
387+
element = $compile(html(
371388
'<div ' +
372389
'ng-include="tpl" ' +
373390
'ng-animate="{enter: \'custom-enter\'}">' +
374391
'</div>'
375-
)($rootScope);
392+
))($rootScope);
376393
$rootScope.$digest();
377394

378395
//if we add the custom css stuff here then it will get picked up before the animation takes place

test/ng/directive/ngRepeatSpec.js

+30-13
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,24 @@ describe('ngRepeat', function() {
513513
});
514514

515515
describe('ngRepeat ngAnimate', function() {
516-
var element, vendorPrefix, window;
516+
var vendorPrefix, window;
517+
var body, element;
518+
519+
function html(html) {
520+
body.html(html);
521+
element = body.children().eq(0);
522+
return element;
523+
}
524+
525+
beforeEach(function() {
526+
// we need to run animation on attached elements;
527+
body = jqLite(document.body);
528+
});
529+
530+
afterEach(function(){
531+
dealoc(body);
532+
dealoc(element);
533+
});
517534

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

525-
afterEach(function(){
526-
dealoc(element);
527-
});
528-
529542
it('should fire off the enter animation + add and remove the css classes',
530543
inject(function($compile, $rootScope, $sniffer) {
531544

532-
element = $compile(
545+
element = $compile(html(
533546
'<div><div ' +
534547
'ng-repeat="item in items" ' +
535548
'ng-animate="{enter: \'custom-enter\'}">' +
536549
'{{ item }}' +
537550
'</div></div>'
538-
)($rootScope);
551+
))($rootScope);
552+
553+
$rootScope.$digest(); // re-enable the animations;
539554

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

575-
element = $compile(
590+
element = $compile(html(
576591
'<div><div ' +
577592
'ng-repeat="item in items" ' +
578593
'ng-animate="{leave: \'custom-leave\'}">' +
579594
'{{ item }}' +
580595
'</div></div>'
581-
)($rootScope);
596+
))($rootScope);
582597

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

613628
it('should fire off the move animation + add and remove the css classes',
614629
inject(function($compile, $rootScope, $sniffer) {
615-
element = $compile(
630+
element = $compile(html(
616631
'<div>' +
617632
'<div ng-repeat="item in items" ng-animate="{move: \'custom-move\'}">' +
618633
'{{ item }}' +
619634
'</div>' +
620635
'</div>'
621-
)($rootScope);
636+
))($rootScope);
622637

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

669-
element = $compile(
684+
element = $compile(html(
670685
'<div><div ' +
671686
'ng-repeat="item in items" ' +
672687
'ng-animate="{enter: \'custom-enter\'}">' +
673688
'{{ item }}' +
674689
'</div></div>'
675-
)($rootScope);
690+
))($rootScope);
691+
692+
$rootScope.$digest(); // re-enable the animations;
676693

677694
$rootScope.items = ['a','b'];
678695
$rootScope.$digest();

0 commit comments

Comments
 (0)