angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

Views are rendered twice on route transition #881

Closed DorianGrey closed 10 years ago

DorianGrey commented 10 years ago

My team has recently faced an issue with controllers being instantiated twice on route transition. While trying to find the reason for this, I've recognized that even the corresponding views got rendered twice, which was rather... unlikely. This problem was detected in v0.2.7 - https://github.com/angular-ui/ui-router/commit/20fcbf8384090a092c7c91a26443e3c818438120 thus I considered moving to v0.2.8 - https://github.com/angular-ui/ui-router/commit/f9ce4dddbe579d215ba84ee9e9ec29249b23ec05 should solve this. But it did not.

Information

Angular-version: 1.2.11 The relevant routes look like this (I cannot post everything, since it's part of a closed project):

$stateProvider
        .state('1ColumnLayout', {
            'abstract': true,
            url: "/1",
            templateUrl: "1ColumnLayout.html"
        })
        .state('login', {
          parent: "1ColumnLayout",
          url: "/login",
          views: {
            "screen-above": {template: ""},
            "screen": { templateUrl: "login.html", controller: "loginController"}
          }
        })
       .state('plantDetails', {
           parent: "2ColumnLayout",
           url: "/plantDetails?plantId",
           views: {
               "screen-above": { templateUrl: "userInfoAndNavigation.html"},
               "screen-left": { templateUrl: "details.left.html", controller: "detailsLeftController"},
                    "screen-right": { templateUrl: "details.right.html", controller: "detailsRightController"}
                },
                resolve: {
                    currentId: getId(false)
                }
            })

While using this template in 1ColumnLayout.html...

<div id="screen-above" class="clearfix" ui-view="screen-above"></div>
<div class="screen1Column clearfix" id="screen-below">
  <div class="s1cContent" ui-view="screen"></div>
</div>

...and this one in 2ColumnLayout.html

<div id="screen-above" class="clearfix" ui-view="screen-above"></div>

<div class="screen2Column clearfix" id="screen-below">
  <div id="screen-right" ui-view="screen-right"></div>
  <div id="screen-left" ui-view="screen-left"></div>
</div>

In a more-or-less long debugging session, I was able to drag down the issue to the following line in viewDirective.js (in v0.2.7):

initialView = transclude(scope);

The call stack mentioned that by calling transclude(...), the compile function is called again, building up the entire view, including controller instantiation. Afterwards, the function went ahead as expected, but destroyed the previously built view completely, building it up once more. In v0.2.8, this line of code has changed to

anchor    = angular.element($document[0].createComment(' ui-view-anchor ')),

which seems more intuitive. However, moving to v0.2.8 did not fix the issue - seems to be broken on another part.

The curious thing...

... is primarily related to the sometimes in the topic: After performing all potential route transitions at least once, all views got rendered only once - as expected.

Workaround

Anyway, just for testing, I've changed

initialView = transclude(scope);

in v0.2.7 to

 initialView = angular.element($document[0].createComment(' ui-view-anchor '));

(picked from v0.2.8), and injected $document as well. Result: Everything worked as expected (I've added my diff so that the whole change is visible), which is... interesting.

diff --git a/public/lib/angular-ui-router/src/viewDirective.js b/public/lib/angular-ui-router/src/viewDirective.js
index efc8930..5e0a9ed 100644
--- a/public/lib/angular-ui-router/src/viewDirective.js
+++ b/public/lib/angular-ui-router/src/viewDirective.js
@@ -1,6 +1,6 @@

-$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$anchorScroll'];
-function $ViewDirective(   $state,   $compile,   $controller,   $injector,   $anchorScroll) {
+$ViewDirective.$inject = ['$state', '$compile', '$controller', '$injector', '$anchorScroll', '$document'];
+function $ViewDirective($state, $compile, $controller, $injector, $anchorScroll, $document) {
   var $animator = $injector.has('$animator') ? $injector.get('$animator') : false;
   var viewIsUpdating = false;

@@ -15,7 +15,8 @@ function $ViewDirective(   $state,   $compile,   $controller,   $injector,   $an
             name = attr[directive.name] || attr.name || '',
             onloadExp = attr.onload || '',
             animate = $animator && $animator(scope, attr),
-            initialView = transclude(scope);
+            initialView = angular.element($document[0].createComment(' ui-view-anchor '));
+

         // Returns a set of DOM manipulation functions based on whether animation
         // should be performed

Even worse, I was not able to reproduce this issue using v0.2.8 with a more simple example, and I'm not sure why it still occurs - wasn't that easy to drag down to a particular line as in v0.2.7 ;)

Maybe some of you have a clue what may cause this, or may check this in a more complex project - rendering views twice, including controller instantiation, is rather unlikely when it comes to performance, or possible side-effects that come along with init-functions. For the moment, this appears to be a blocking issue in our case which prevents us from moving to v0.2.8 and, as a result, from other fixes / improvements.

timh5 commented 10 years ago

any update on this? do you know if it was fixed in 0.2.10. im seeing the same problem now

timh5 commented 10 years ago

i tried this patch and it broke for me. what is "ui-view-anchor" referring to? do i need to add that tag somewhere in my template?

DorianGrey commented 10 years ago

I'll have a look at 0.2.10 when I'm back in office next week. As you see, 'ui-view-anchor' is a comment added to the document itself, no special declaration in any template. In our case, it modifies one of the templates mentioned above (yes, that's their whole content!). The patch I've described only works for 0.2.7, not for any other version. "Broke for me" is not a useful error message, though ;)

timh5 commented 10 years ago

thanks for reply. not a big deal now. i've given up on ui-router now... its just not ready for what we want to do (preserve dom cache/state between state changes). Im back to RouteProvider and ng:show which seems to work great now...

MathiasTim commented 10 years ago

Mhh interesting, seems we're running into the same issue you described. We're on version 0.2.8. Tried to update to 0.2.10 today and got an error, but don't know right now if it is related with this.

Would be great to hear if there is a fix in version 0.2.10. The code in this version in viewDirective.js looks a lot different to 0.2.7

nateabele commented 10 years ago

Should be fixed, as uiView now uses the same DOM-handling code as ngView.

DorianGrey commented 10 years ago

Alright, I've checked 0.2.10 - and the problem still persists. As I've mentioned in the report, the handling of uiView was already changed in 0.2.8 (the version I picked the code from), but it seems to just have moved the problem to another place.

nateabele commented 10 years ago

As I've mentioned in the report, the handling of uiView was already changed in 0.2.8

0.2.10 changed it again, and more extensively.

Could you possibly create a minimal reproduction of the problem in a Plunkr?

DorianGrey commented 10 years ago

First of all, I've already mentioned that the hackfix I've illustrated for 0.2.7 did not work for later versions - seems the cause of this problem has changed since then.

However, I've stripped down some of our logic and created a medium-complex example here (had to provide angular and ui-router manually, since Chrome complained about MIME types), but unfortunately, it does not illustrate the error. I'm not sure why, since it includes all logical steps we are using. Do you see anything that might cause a problem slightly misused in a wrong way?

Maybe one of the others that have faced this are more lucky with a smaller example?

Just to illustrate that this is not a joke: blub3

That was a test with 0.2.10 this morning, which created the feedController three (!) times, always with different ids (see the right hand side), before anything happens on the site (see the upper part). I'm sorry that I can't provide more details, but most of the projects contents are rather... secret up to now.

snekbaev commented 10 years ago

Hi,

seems like I'm also getting this (0.2.10). I didn't check other ui-views, but currently 4-th (nested in other ui-views: 4th inside 3rd, 3rd inside 2nd etc) ui-view is doing that. I'm loading a cached template markup into ui-view="workspaceEditorContent", each template has a controller set, so inside one of those controllers I've added an 'alert'. When that view is loaded I see previously loaded content AND the new one which is being loaded. Following image should help:

untitled

For now fixed the UI glitch with:

[ui-view="workspaceEditorContent"] : nth-child(2) 
{
    display:none;
}
artem1 commented 10 years ago

Have exactly issue as @snekbaev, 2 view and only 1 controller instantiation. v0.2.10 There https://github.com/angular-ui/ui-router/blob/master/src/viewDirective.js#L204 view updated two time? and for second call go to transclude https://github.com/angular-ui/ui-router/blob/master/src/viewDirective.js#L211, in wich called cleanupLastView(), that remove duplicate dom node. Can't find hot to fix this. In Opera (portable last version) this flickr visible in ui (in chrome, ff or opera standalone not, strange).

update: comment in viewDirective.js lines 143:157 (exclude $animate and $animator) - and it seems it solved the problem

cyberdude commented 10 years ago

Is this a ui-router error or a ng-animate?

pihomeserver commented 10 years ago

Seems to be ui-router. Today i removed yeoman, grunt and all stuuf installed by yeoman. I made a fresh install of phonegap and angular and now all works well ... Really strange

Le 2 juil. 2014 à 21:57, Arnaldo Capo notifications@github.com a écrit :

Is this a ui-router error or a ng-animate?

— Reply to this email directly or view it on GitHub.

joshjung commented 10 years ago

Any update on this issue? I just had it start occurring. Am attempting some of the solutions here but was curious if this bug has been fixed since July.

DorianGrey commented 10 years ago

We've recently switched to 0.2.11 (using angular 1.3) and the issue seems to be solved.

wylieconlon commented 9 years ago

I'm still seeing this issue on 0.2.12, I would be surprised if this is actually fixed. @artem1 your workaround to comment out the $animate and $animator code does not work for me

edrpls commented 9 years ago

I am using 0.2.13 and this is still happening.

nateabele commented 9 years ago

@ikhos What version of Angular?

edrpls commented 9 years ago

@nateabele I'm using 1.3.15. Since I am using multiple modules I am using angular.bootstrap().

'use strict';

angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies);

angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
    function($locationProvider) {
        $locationProvider.hashPrefix('!');
    }
]);

angular.element(document).ready(function() {
    if (window.location.hash === '#_=_') window.location.hash = '#!';

    angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]);
});

This is the bootstraping config:

'use strict';

var ApplicationConfiguration = (function() {
    var applicationModuleName = 'myApp';
  var applicationModuleVendorDependencies = [
    'ui.router',
    'ui.bootstrap',
    'ngAnimate',
    'ngSanitize',
    'ngTouch',
    'ajoslin.promise-tracker',
    'angular-google-analytics',
    'angular-inview',
    'LocalStorageModule',
  ];

    var registerModule = function(moduleName, dependencies) {
        angular.module(moduleName, dependencies || []);

        angular.module(applicationModuleName).requires.push(moduleName);
    };

    return {
        applicationModuleName: applicationModuleName,
        applicationModuleVendorDependencies: applicationModuleVendorDependencies,
        registerModule: registerModule
    };
})();

I just found out that if I comment the angular.bootstrap(...) line, the ui-view renders just once. Any hints on this behaviour will be greatly appreciated.

edrpls commented 9 years ago

I feel really dumb now. I was using ng-app="" as well as initialising the app programmatically. I just removed the ng-app directive from my view and everything works perfectly now. Thanks.

nateabele commented 9 years ago

:+1: You might feel dumb for having wasted time on it, but those are the best kinds of problems to have. Seriously.

roybein commented 8 years ago

I almost missed @edrpls 's answer, just because @nateabele 's bad joke. :smirk: thank you all. Maybe silly, but remind again. hey, @edrpls 's answer helps.

ionescho commented 8 years ago

This occurs for me with the latest ui-router. I need to use ng-app="app" and angular.bootstrap because I have window.name = "NG_DEFER_BOOTSTRAP!"; .The app is only instantiated once, so why does this occur for me still?

Roycohen commented 8 years ago

Hi there, I'm using Angular 1.3.15 and latest UI-Router (0.2.18) and my root UI-VIEW is always duplicating. I'm bootstrapping the application by myself and not like @edrpls, I'm not using ng-app :)

How can I fix that? Where are the suspicious places that might make this issue?

mpellerin42 commented 8 years ago

Hi there,

I'm facing the same issue. Using Angular 1.4.9 and angular-ui-router 0.2.18. The application is bootstrap using only the directive ng-app. On load, no problem, the ui-view directive behave as expected and routes work well. But at some point, I use $state.go('defaultRoute', {}, {reload: true}); This line reload the page as expected, but during the refresh the ui-view is duplicated for a moment. This duplication prevent one of my controllers to initialise scroll event on window as it should.

Do someone have any clue of what causes this ?

davidsmith242 commented 8 years ago

Hi there, the problem still exists with angular 1.5.3 and angular-ui-router 0.2.18. The directives on my view are initialized a second time when I route to the next view. Is there a bug fix available to avoid this problem??

alexilyaev commented 8 years ago

Why is this Closed?

This happens to me as well when using Chartist.js (charting library). Once I add a Pie chart, the root <ui-view> is duplicated, the second one is empty. If I navigate to this state from another state, I can see my previous state at the bottom <ui-view> and my current state at the top one.

I'm bootstraping with angular.bootstrap, no ng-app anywhere.

This hack fixes it:

$rootScope.$on('$stateChangeStart', () => {
  $animate.enabled(false);
});

But I'd really like it to be resolved, especially if I'd want to add state change animations later on.

Versions:

"angular": "1.5.0",
"angular-animate": "1.5.0",
"angular-ui-router": "0.2.18",
dgunderson commented 8 years ago

This is still a problem for me too. I'm using ng-animate along with ui-router to animate transitions. I started noticing that my view content was appearing twice on the screen during a transition, the bottom of the two look like styled empty templates while the one above is rendered properly. I'm using ng-app, not bootsrapping the app in js. After the animation is complete, the duplicated view disappears.

This screenshot shows the duplicated ui-view during the transition. After which, it disappears. The user can see this duplication on the screen while the transition is active:

image

These are my versions:

"angular": "1.5.5",
"angular-animate": "1.5.5",
"angular-ui-router": "0.2.18",

UPDATE:

I just noticed that if I trigger my ui-view transition a bunch by rapid clicking, I can get up to 5 ui-views rendered on the page:

image

UPDATE # 2

I finally realized that I had a directive that was dynamically adding a style to the same element that is serving as my ui-view. You can see it in my screenshot, and I didn't immediately recognize it for what it is, but that was why I was able to see the content on my screen during the many different copies of ui-view being added during the transition. There was a background/border being applied to it when it should not have been applied. So, I made it so the style that ui-view was using explicitly set background:none and border:none. Now I don't get any UI oddities, but copying of the ui-view is still there. That's likely exactly how ng-animate is intended to behave, adding the enter/leave transition classes. It's just unfortunate that it has to be applied this way in combination with ui-view. So, I no longer have a visual issue, but it still feels like a quirky bug.

kmaraz commented 8 years ago

For me the solution was to completely remove angular-animate. And it is extremely bad solution I know.

chillyistkult commented 8 years ago

It's also an issue for me. Is there any acceptable workaround yet?

ebenmoha commented 8 years ago

+1

nroma commented 8 years ago

Me too have this problem using the following versions: "angular": "^1.4.0", "angular-animate": "^1.4.0", To solve this issue I disable animation for the view that has effects.

The code for this workaround is the following: <div ui-view="theView" class="no-animate"> and in CSS: .no-animate { -webkit-transition: none !important; transition: none !important; }

Arkh1 commented 7 years ago

+1 I removed instances of reload:true to get around this on some pages, but I still have other pages that are rendering multiple views for a split second during state transitions. The rootscope listener that @alexilyaev posted gets around the issue, but I also would like a better solution. I was hoping I could get around it with absolute view naming, but that doesn't seem to help.