Angular.js: Hidden animated panels are visible on page load

Created on 11 Sep 2015  Â·  20Comments  Â·  Source: angular/angular.js

Hi,

I have hidden floating panel.
HTML:
<div class="modal animated left-modal " data-ng-hide="!showFormCreator" ng-cloak data-ng-controller="formCreatorController" data-ng-include="'/app/views/form-creator.html'"></div>

CSS:

.left-modal.ng-hide-add {      
     -webkit-animation:  fadeOutRight  0.5s ;   
    -moz-animation:   fadeOutRight 0.5s;
    -ms-animation:   fadeOutRight 0.5s;    
    animation:   fadeOutRight 0.5s;     
}

.left-modal.ng-hide-remove{
    -webkit-animation:  fadeInRight  .5s ;   
    -moz-animation:   fadeInRight .5s;
    -ms-animation:   fadeInRight .5s;    
    animation:   fadeInRight .5s;   
}

In angular 1.3.13 works all fine.

After update to 1.4.4 and now to 1.4.5 all hidden animated panels are visible on page load for few seconds.

After page load is all fine.

Thanks for help.

Josef

ngAnimate low investigation regression bug

Most helpful comment

Quick (& dirty) workaround is to add ng-hide class to the element.

All 20 comments

Can you please post a demo via plnkr.co or similar that shows the problem?

Demo: http://plnkr.co/edit/xWkbSWkKu2fMfUhRdvsk . When the application starts, you will see the hidden animated panel.

A little more info:

This seems to have been introduced in 1.4.0-rc.0 (aka the big $animate refactoring).
I suspect it is the last breaking change from here, namely, the addClass animation is cancelled while an enter animation is in progress.

What I haven't figured out yet, is what causes the enter animation (but it is happening as you can see here).

Aha...it's the ngInclude, of course.

So, I am tempted to say that this works as expected (in the sense that it's a documented breaking change).
Still have to figure out the most appropriate work-around (suggestions welcome).

As is usually the case, moving the directive on a child element solves the issue. E.g.:

<!-- Change this: -->
<div ... ng-show="..." ng-include="...">Pane</div>

<!-- To that: -->
<div ... ng-show="..."><div ng-include="...">Pane></div></div>

Here is an updated plnkr.


So, like I said, I am inclined to calling this a works as expected.
WDYT, @Narretz, @matsko ?

Thanks for investigating @gkalpak!
I don't think it's a cancellation of the addClass animation because the ngInclude does not have actual animation code. But I haven't looked at what happens yet.

@Narretz, I'm not sure what you mean by _"the ngInclude does not have actual animation code"_.
It does call $animate.enter() (which initiates a structural animation).

That's right, it initiates an animation but when no styles are found the
animation closes prematurely.
Am 07.12.2015 21:33 schrieb "Georgios Kalpakas" [email protected]:

@Narretz https://github.com/Narretz, I'm not sure what you mean by _"the
ngInclude does not have actual animation code"_.
It _does_ call $animate.enter() (which initiates a structural animation).

—
Reply to this email directly or view it on GitHub
https://github.com/angular/angular.js/issues/12825#issuecomment-162651618
.

@Narretz, you are right; it's not enter cancelling addClass (they are merged actually). What makes the difference is that with ngInclude on the same element, the animations are enabled before ngShow tries to add the .ng-hide class for the first time (thus the animation).

Here's what is going on in both cases (with ngInclude on the same element and on a child element):

1. ngInclude on the same element as ngShow

<div ... ng-show="..." ng-include="...">...
  1. ngInclude transcludes the whole element (i.e. it removes it from the DOM and leaves a comment as a placeholder), before ngShow is compiled (since ngShow has a lower priority).
  2. ngInclude requests the template.
  3. By the time the template is back and the request promises are resolved, there have already been 2 $digests, thus animations are enabled (as per animateQueue.js#L113-L121).
  4. ngInclude creates a new element from the transcluded template element + the fetched HTML content, inserts it into the DOM and compiles/links it.
  5. ngShow on the newly created element, adds the .ng-hide class (using $animate.addClass()).
    Since animations are enabled, the user gets to see the element animating out of the view.

Compare this to:

2. ngInclude on a child element - ngShow on the parent

<div ... ng-show="..."><div ng-include="...">...
  1. ngInclude transcludes the child element (i.e. it removes it from the DOM and leaves a comment as a placeholder). Note that ngShow is not on the transcluded element, but on its parent.
  2. ngInclude requests the template.
  3. While the template is being fetched, ngShow's post-link function adds the .ng-hide class to the parent element (using $animate.addClass()).
  4. At this point, no request promises have been resolved yet, thus no two $digests have
    completed, which means animations are still disabled. With animations disabled, the .ng-hide class is added directly to the element's classes (without going through the ng-hide-animate/ng-hide-add/ng-hide-add-active hoops).
  5. By the time the template is back and ngInclude creates and inserts a new child element, the parent is already hidden (via .ng-hide).
  6. The user doesn't get to see the element animating out of the view.

That said, I still think this _works as expected_.
One could think of several workarounds, but having the directives on different elements is the simplest imo.

It remains to be investigated, why it did work prior to v1.4.0-rc.0 :smiley:
Still looking into it.

Here is why it used to work prior to v1.4.0-rc.0:

Class-based animations were "blocked" on elements currently performing a structural animation (see animate.js#L517-L525). So, not allowing class-based animations while a structural animation (like enter) was in progress, meant that the .ng-hide class was added immediately (no ng-hide-animate/add/add-active ceremony) - effectively hiding the element as soon as it was inserted into the DOM by ngInclude.

After all, allowing class-based animations along with structural ones is a feature (not a bug) :smiley:

If anyone's interested, here are two demos:

Interesting. In this comment https://github.com/angular/angular.js/issues/11492#issuecomment-91605476, Matias said that animate would wait for the templates to download ...

As a workaround, you can put your templates into the templateCache when your module runs: http://plnkr.co/edit/Q6o13RNXjdRtmw8LW67W?p=preview

There are also build tools that do this automatically for you, e.g. https://github.com/karlgoldstein/grunt-html2js

In this comment #11492 (comment), Matias said that animate would wait for the templates to download ...

Matias is right (obviously :smiley:). It (sort of) does. That's why there is _no_ animation when ngShow is applied before downloading the template (thus no flicker), but there _is_ an animation when ngShow is applied after downoading the template.

The fact that it works (at least visually) when you add the template to the cache when the app runs, makes me think there is still a difference. Because template fetching is still async, even if the template is cached. It looks more like a timing issue.

There's definitely something strange going on: ng-animate is adding these bogus classes on the initial hide / enter animation: ng-ng-hide-add ng-ng-hide-add-active. structural animations get this ng prefix added. So it looks like the class based animation is mistaken for a structural animation. Not sure if that's the cause of this problem, but it's definitely a bug.

Forked example with classList log: http://plnkr.co/edit/FDEAbvvjVJzZccuZWMwY?p=preview

Yes, I've noticed it. This happens because the (non-structural) addClass('ng-hide') animation gets merged into the (structural) enter animation (which is already in progress) and in some sense "inherits" the isStructural: true. Thus there are also the ng-prefixed classes added (along with the expected ng-hide-* classes).

It would be nice to fix that, but I don't think it is causing any issues.

I've talked with @matsko about this and he said he would do some investigating. It's expected that the animations are merged, but the way they are merged doesn't look right.

The actual problem in the issue is caused by the way ngAnimate blocks animations on bootstrap. It waits two digest until animations are enabled. ngInclude calls $enter inside a watchActionFn, so it's happenind one digest later. We could delay animations further, but I'm not sure if that won't break some expectations. However, if you put it in the templateCache (best practice anyway) it doesn't animate, so it's probably more consistent to wait another digest.

The way $animate detects when the templates have been fetched and should enable animations is not 100% reliable. For example, because ngInclude calls $templateCache inside a watch-action callback and the src expression has to be evaluated first, $templateCache.totalPendingRequests is 0 when $animate checks, but not all (necessary) templates have been fetched.

That being said, I don't think it is reasonable to change how $animate detects "stability", because:

  1. It will break a lot of apps (and it will be very difficult for people to determine the source of the problem).
  2. I don't think there is a 100% reliable way to detect when the view is stable (and it heavily depends on the directives used and their arbitrary behaviors).

The current mechanism, tries to account for route and directive templates and it does it quite well.
It doesn't try to account for templates that directives might request asynchronously after evaluating an expression inside a $watch (which is what ngInclude does). Directives having that kind of behavior (and people using them) should be responsible for the side-effects of such behavior.

They can put them into wrappers, load templates into the $templateCache as a build step or whatever works for their usecase.

Quick (& dirty) workaround is to add ng-hide class to the element.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fredrikbonander picture fredrikbonander  Â·  55Comments

vojtajina picture vojtajina  Â·  78Comments

coli picture coli  Â·  60Comments

coli picture coli  Â·  62Comments

red-crown picture red-crown  Â·  78Comments