diff --git a/docs/content/guide/animations.ngdoc b/docs/content/guide/animations.ngdoc
index 14b62f1f9d1b..0898789dd917 100644
--- a/docs/content/guide/animations.ngdoc
+++ b/docs/content/guide/animations.ngdoc
@@ -274,6 +274,37 @@ myModule.directive('my-directive', ['$animate', function($animate) {
}]);
```
+## Preventing flicker before an animation starts
+
+When nesting elements with structural animations such as `ngIf` into elements that have class-based
+animations such as `ngClass`, it sometimes happens that before the actual animation starts, there is a brief flicker or flash of content
+where the animated element is briefly visible.
+
+To prevent this, you can apply styles to the `ng-[event]-prepare` class, which is added as soon as an animation is initialized,
+but removed before the actual animation starts (after waiting for a $digest). This class is only added for *structural*
+animations (`enter`, `move`, and `leave`).
+
+Here's an example where you might see flickering:
+
+```html
+
+```
+
+It is possible that during the `enter` event, the `.message` div will be briefly visible before it starts animating.
+In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
+
+```css
+.message.ng-enter-prepare {
+ opacity: 0;
+}
+
+/* Other animation styles ... */
+```
+
## More about animations
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
diff --git a/src/ngAnimate/.jshintrc b/src/ngAnimate/.jshintrc
index 187ecd1f3c9d..9ab8bbe1b890 100644
--- a/src/ngAnimate/.jshintrc
+++ b/src/ngAnimate/.jshintrc
@@ -29,6 +29,7 @@
"REMOVE_CLASS_SUFFIX": false,
"EVENT_CLASS_PREFIX": false,
"ACTIVE_CLASS_SUFFIX": false,
+ "PREPARE_CLASS_SUFFIX": false,
"TRANSITION_DURATION_PROP": false,
"TRANSITION_DELAY_PROP": false,
diff --git a/src/ngAnimate/animation.js b/src/ngAnimate/animation.js
index c0deb035f790..f0ac060fcb9d 100644
--- a/src/ngAnimate/animation.js
+++ b/src/ngAnimate/animation.js
@@ -135,6 +135,12 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
options.tempClasses = null;
}
+ var prepareClassName;
+ if (isStructural) {
+ prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
+ $$jqLite.addClass(element, prepareClassName);
+ }
+
animationQueue.push({
// this data is used by the postDigest code and passed into
// the driver step function
@@ -357,6 +363,10 @@ var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
if (tempClasses) {
$$jqLite.addClass(element, tempClasses);
}
+ if (prepareClassName) {
+ $$jqLite.removeClass(element, prepareClassName);
+ prepareClassName = null;
+ }
}
function updateAnimationRunners(animation, newRunner) {
diff --git a/src/ngAnimate/module.js b/src/ngAnimate/module.js
index e6bb8208d56c..c1178902db13 100644
--- a/src/ngAnimate/module.js
+++ b/src/ngAnimate/module.js
@@ -254,6 +254,34 @@
* the CSS class once an animation has completed.)
*
*
+ * ### The `ng-[event]-prepare` class
+ *
+ * This is a special class that can be used to prevent unwanted flickering / flash of content before
+ * the actual animation starts. The class is added as soon as an animation is initialized, but removed
+ * before the actual animation starts (after waiting for a $digest).
+ * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
+ *
+ * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
+ * into elements that have class-based animations such as `ngClass`.
+ *
+ * ```html
+ *
+ * ```
+ *
+ * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
+ * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
+ *
+ * ```css
+ * .message.ng-enter-prepare {
+ * opacity: 0;
+ * }
+ *
+ * ```
+ *
* ## JavaScript-based Animations
*
* ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
diff --git a/src/ngAnimate/shared.js b/src/ngAnimate/shared.js
index 634cd90bada4..501e5bd0d015 100644
--- a/src/ngAnimate/shared.js
+++ b/src/ngAnimate/shared.js
@@ -21,6 +21,7 @@ var ADD_CLASS_SUFFIX = '-add';
var REMOVE_CLASS_SUFFIX = '-remove';
var EVENT_CLASS_PREFIX = 'ng-';
var ACTIVE_CLASS_SUFFIX = '-active';
+var PREPARE_CLASS_SUFFIX = '-prepare';
var NG_ANIMATE_CLASSNAME = 'ng-animate';
var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren';
diff --git a/test/ngAnimate/animationSpec.js b/test/ngAnimate/animationSpec.js
index 04ba3e5949a3..dcce9c1219f7 100644
--- a/test/ngAnimate/animationSpec.js
+++ b/test/ngAnimate/animationSpec.js
@@ -513,6 +513,25 @@ describe('$$animation', function() {
expect(captureLog[1].element).toBe(child);
expect(captureLog[2].element).toBe(grandchild);
}));
+
+
+ they('should add the preparation class before the $prop-animation is pushed to the queue',
+ ['enter', 'leave', 'move'], function(animationType) {
+ inject(function($$animation, $rootScope, $animate) {
+ var runner = $$animation(element, animationType);
+ expect(element).toHaveClass('ng-' + animationType + '-prepare');
+ });
+ });
+
+
+ they('should remove the preparation class before the $prop-animation starts',
+ ['enter', 'leave', 'move'], function(animationType) {
+ inject(function($$animation, $rootScope, $$rAF) {
+ var runner = $$animation(element, animationType);
+ $rootScope.$digest();
+ expect(element).not.toHaveClass('ng-' + animationType + '-prepare');
+ });
+ });
});
describe("grouped", function() {
diff --git a/test/ngAnimate/integrationSpec.js b/test/ngAnimate/integrationSpec.js
index feb28e581456..4a0610f2170a 100644
--- a/test/ngAnimate/integrationSpec.js
+++ b/test/ngAnimate/integrationSpec.js
@@ -268,6 +268,42 @@ describe('ngAnimate integration tests', function() {
});
});
+ it('should add the preparation class for an enter animation before a parent class-based animation is applied', function() {
+ module('ngAnimateMock');
+ inject(function($animate, $compile, $rootScope, $rootElement, $document) {
+ element = jqLite(
+ ''
+ );
+
+ ss.addRule('.ng-enter', 'transition:2s linear all;');
+ ss.addRule('.parent-add', 'transition:5s linear all;');
+
+ $rootElement.append(element);
+ jqLite($document[0].body).append($rootElement);
+
+ $compile(element)($rootScope);
+ $rootScope.exp = true;
+ $rootScope.$digest();
+
+ var parent = element;
+ var child = element.find('div');
+
+ expect(parent).not.toHaveClass('parent');
+ expect(parent).toHaveClass('parent-add');
+ expect(child).not.toHaveClass('ng-enter');
+ expect(child).toHaveClass('ng-enter-prepare');
+
+ $animate.flush();
+ expect(parent).toHaveClass('parent parent-add parent-add-active');
+ expect(child).toHaveClass('ng-enter ng-enter-active');
+ expect(child).not.toHaveClass('ng-enter-prepare');
+ });
+ });
+
+
it('should pack level elements into their own RAF flush', function() {
module('ngAnimateMock');
inject(function($animate, $compile, $rootScope, $rootElement, $document) {