Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

event $viewContentLoading doesn't seem to work #685

Closed
dmatteo opened this issue Dec 16, 2013 · 30 comments · Fixed by #2003
Closed

event $viewContentLoading doesn't seem to work #685

dmatteo opened this issue Dec 16, 2013 · 30 comments · Fixed by #2003
Labels
Milestone

Comments

@dmatteo
Copy link

dmatteo commented Dec 16, 2013

It seems I'm unable to get the $viewContentLoading fired.

The doc says it's fired by the $rootScope, while in the example listen to the $scope

$scope.$on('$viewContentLoading', 
function(event, viewConfig){ 
    // Access to all the view config properties.
    // and one special property 'targetView'
    // viewConfig.targetView 
});

I've tried both, but I couldn't catch any event

@laurelnaiad
Copy link

If you haven't already, double check that your controller is being instantiated and/or listen for $stateChageError from your run function.

@dmatteo
Copy link
Author

dmatteo commented Dec 17, 2013

I can surely tell that the controller gets istantiated because in this code

        $scope.$on('$viewContentLoading',
            function(event, viewConfig){
                console.log('content loading: ', event, viewConfig)
            });

        $scope.$on('$viewContentLoaded',
            function(event){
                console.log('content loaded: ',event)
            });

The event $viewContentLoaded gets fired, while the $viewContentLoading gets not

I'm experiencing this problem with Angular 1.2.2 as well as 1.2.5

@timkindberg
Copy link
Contributor

I actually couldn't this to work at all either. I think it may be just plain broke.

@gustavohenke
Copy link
Contributor

Not really broken. I think.

Take a look at this line.
This is the only place where $view.load is called (with notify: false), and $view.load is the only emitter of $viewContentLoading event.

@vicentereig
Copy link

@dmatteo The $viewContentLoading is broadcasted before the controller gets instantiated and the promises in the state resolved.
Therefore if you setup the $viewContentLoading and $viewContentLoaded delegate from the controller that is attached to the state you're visiting , it will receive only the $viewContentLoaded update.

Long story short:

UsersController instantiated.
content loaded!
.state('users', { 
  controller: UsersController, 
  resolve: { 
    users: function() {
       return [ /* ... */  ];
    }
  }
});

function UsersController($scope) {
   console.log("UsersController instantiated");

  $scope.$on('$viewContentLoading', function(event, viewConfig){
     console.log('content loading: ', event, viewConfig)
  });

  $scope.$on('$viewContentLoaded', function(event){
    console.log('content loaded!')
  });
}
app.controller('UsersController', ['$scope', UsersController]);

You can instantiate a parent controller right in your view and setup the view loading delegate.

 <div class="users" ng-controller="LoadingIndicatorController">
   <div class="contents" ui-view></div>
</div>
function LoadingIndicatorController($scope) {
    console.log("LoadingIndicatorController instantiated");
    $scope.$on('$viewContentLoading',function(event, viewConfig){
        console.log("Contents will load.");
    });

    $scope.$on('$viewContentLoaded',function(event, viewConfig){
        console.log("Contents did load.");
    });
}

app.controller('LoadingIndicatorController', ['$scope', LoadingIndicatorController]);

That even doesn't work because of the issue that @gustavohenke points out. I still had to hack a options.notify = true in https://github.com/angular-ui/ui-router/blob/0.2.7/src/view.js#L19 when name == "@". @timkindberg any thoughts there?

@timkindberg
Copy link
Contributor

Therefore if you setup the $viewContentLoading and $viewContentLoaded delegate from the controller that is attached to the state you're visiting , it will receive only the $viewContentLoaded update.

@vicentereig Are you saying this is how it currently works, or how it should work?

My gut is that this is just broken on accident, but perhaps someone set notify to false for a different reason, not realizing it would stop the $viewContentLoading event from firing. I'm also not completely sure I even understand why and when you'd even need the $viewContentLoading event!

@xdhmoore
Copy link

+1

@vicentereig
Copy link

@timkindberg Sorry for the late reply.

What I mean is that this is how it currently works. To recap:

  1. $viewContentLoading gets broadcasted
  2. Dependencies under the resolve section are resolved
  3. Controller gets instantiated and resolve dependencies injected.
  4. $viewContentLoaded is emitted.
  5. The controller reacts to $viewContentLoaded (when it is setup as the delegate to dispatch those events of course).

I've got some views that take a little while to load the data from the server (sometimes ~1 second), so I wanted to show a loading indicator while transitioning from one route to another.

I'm aware of other ways to render it while loading the data. But if routes are responsible of defining the UI state by putting together the controller, template, and data coming from the resolve section, I find it natural to let them notify whoever wants to listen it to it about the current status of the loading process.

My use case

My use case is a Master-Detail view drilldown interaction. Let's say I've got the following ui-views set up. The detail view is a child of the collection view rendering user names in this case.

+-----------------------------------------+
| user 1 |  user 1 details view          | 
| user 2 |                               | 
| user 3 |                               | 
| user 4 |                               | 
| user 5 |                               | 
| user 6 |                               | 
+-----------------------------------------+

Maybe I'm missing something, but when I first point the browser #/users/1 ideally I'd expect the following to happen:

  1. users parent route resolves the promises and injects the result on the controller associated to that state.
  2. The Collection View gets rendered while users.show is still idle getting data from the sever (this is the API call that sometimes takes up to a second to load).
  3. The detail view renders a loading indicator.
  4. The resolve section in users.show finally gets a 200 OK from the server, injects the dependencies in the controller.
  5. So I can finally render the user's detail.

Talk you soon,
Vicente.

@timkindberg
Copy link
Contributor

Yes, the use case makes sense now. Hopefully we'll be able to get to this in 0.4.0.

@ivandotv
Copy link

ivandotv commented Apr 2, 2014

+1

2 similar comments
@ryanmillerdev
Copy link

+1

@hesalx
Copy link

hesalx commented Jul 13, 2014

+1

@yasar
Copy link

yasar commented Jul 20, 2014

Facing with the same issue due to exact same reason. so, another +1.

@dagilleland
Copy link

+1 - in fact, handling $viewContentLoading is a core need in my using dynamic templateUrls, since I need to do a bit of post-processing on the load and wanted it to happen before it's placed in the view.

@favio41
Copy link

favio41 commented Oct 13, 2014

+1 For me the event isn't trigger the first time, after that perfect. Anybody have a hack for this? cheers.

@mladenp
Copy link

mladenp commented Mar 12, 2015

For me too $viewContentLoaded is not firing. How the heck should i know that my view is ready?

@nateabele
Copy link
Contributor

Still waiting on a plunkr that demonstrates the issue.

@laurynas-karvelis
Copy link

+1

2 similar comments
@0xae
Copy link

0xae commented Apr 10, 2015

+1

@lc-nyovchev
Copy link

+1

@111crb111
Copy link

Not firing, too :(

@metamatt
Copy link

I think the problem is that the $viewContentLoading / $viewContentLoaded events are not always paired.

$viewContentLoading is emitted from $ViewProvider.load which is called from resolveViews as a side effect of state transition.

$viewContentLoaded is emitted from <ui-view>'s updateView whenever it needs to update (as a side effect of initial instantiation, $viewContentLoading, or $stateChangeSuccess), after it runs the controller.

The problem is if you have nested views and states such that you can update views without activating new states (such as transitioning from a child state to a parent state, which involves only state DEactivation) then you get $viewContentLoaded without $viewContentLoading. Plunkr here (all the output goes to the devtools console so open the console, then click back and forth between "enter state 1" and "enter state 2" to verify you get matched pairs of loading/loaded events, then click "enter root state" and you'll see only a loaded event but no loading event.)

@metamatt
Copy link

I also note in real usage in my real apps that when ui-view receives a $viewContentLoading event, it always seems to follow the return; // nothing to do path in updateView(). Then immediately after that, it receives a $stateChangeSuccess event which triggers another call to updateView() which is not a no-op. That is, it never does anything with the $viewContentLoading event, and its behavior is always driven directly from $stateChangeSuccess.

Proposal 1: leave $viewContentLoading how it is for backwards compatibility, but add a second event ($viewContentReallyLoading?!) that is emitted inside updateView before the $transclude call, always paired with $viewContentLoaded. (Use case: my test harness wants to know when views are instantiating so that if a controller throws an exception, it knows which view to blame. So I really want a reliable "loading" signal.)

Proposal 2: remove the existing $viewContentLoading, and just emit $viewContentLoading from updateView before the $transclude call. This might break people who depend on the current behavior, but barring an explanation I don't currently understand, this behavior makes more sense.

I'd favor proposal 2 because I don't know what the meaning of the existing $viewContentLoading site really means to external client code (since it doesn't reliably happen), the internal consumer of the current $viewContentLoading event doesn't seem necessary, I think the other site is a more reliable indicator of what external client code wants to know, and I think $viewContentLoading is the right name for the event that's paired with $viewContentLoaded. But it's very possible I'm missing something about the current $viewContentLoading emit site.

I'd be happy to submit a PR for either of these proposals if the main contributors would nominate their preference.

@metamatt
Copy link

A couple other differences I note between $viewContentLoading (in 0.2.15) and $viewContentLoaded:

  • $viewContentLoading is broadcast down from the root scope, $viewContentLoaded is emitted up from the view scope
  • I didn't prove it in that plunkr but I'm guessing since "loading" is sent from the resolve-state path, and "loaded" is sent from the view-update path, if one state maps to multiple views, you get one "loading" event and multiple "loaded" events
  • $viewContentLoading comes with an "options" parameter (which doesn't actually seem to include the name of the view that's loading), $viewContentLoaded does not

Again if there's a need (for backwards compatibility or for internal reasons I don't understand) for the current $viewContentLoading behavior (broadcast on the root scope once per state transition and only if notify was specified and it needs to include the options parameter) then I suggest we keep it and add another event which is paired with $viewContentLoading (emitted on the view scope once per view update); that's roughly my proposal 1 above; if there's no such need then my proposal 2 seems simpler and cleaner (and it does pass the test suite).

@nateabele please note the new plunkr in response to the plunkr-please label, and I'm curious what you think of this analysis and the proposals.

metamatt pushed a commit to metamatt/ui-router that referenced this issue May 29, 2015
The old $viewContentLoaded event wasn't needed for the one internal
client (ui-view) because ui-view always ignores that event and acts on
a $stateChangeSuccess event that follows right behind anyway.

And it wasn't as useful to external clients as it could be because
it wasn't delivered on every view update -- it was delivered only
on state transitions that activate a state defining a view, and
didn't deal with inheritance.

Also, neither $viewContentLoading nor $viewContentLoaded events
contained the name of the view loading or loaded.

This change makes $viewContentLoading and $viewContentLoaded be
emitted always in pairs, before/after updateView does the work of
actually loading the view, and both events include the name of the
view being loaded.

This is a breaking change for users of the old $viewContentLoading
event that relied on it being broadcast from the root scope, the
precise time it was broadcast, the fact that it wasn't always sent
when a view is loaded even if $viewContentLoaded will be sent, or the
"options" parameter that was emitted with it. If people rely on any of
that behavior and we can't break it, then we can restore the old
behavior but I think there needs to be some other event which is
reliably paired with $viewContentLoaded and contains the view name,
and I can't think of a better name than $viewContentLoading for that
event.

Closes angular-ui#685.
@metamatt
Copy link

I updated the plunkr to demonstrate that if one state has two child views, and a child state populates both those views, you get two "loading" and two "loaded" events, but if a child state populates only one of those views, you get one "loading" and two "loaded" events. The problem I understand is related to situations with multiple views defined across multiple states with inheritance. The inherited views get a "loaded" event but no "loading" event.

@christopherthielen
Copy link
Contributor

Metamatt Thanks for the analysis. I've never understood how people are using these events external to ui-router code. I think option #2 seems reasonable.

Can anyone else speak up if they are using viewContentLoading in some meaningful way and would object to proposal #2?

That said, Nate is revamping the transition to view interaction, and these events are likely going to be deprecated or removed completely in 1.x.

@OnkelTem
Copy link

OnkelTem commented Dec 2, 2015

To those who requested use cases — take a look at my original issue: #2403 and [finally] working example: http://plnkr.co/edit/ED0SGYyJerO1kaNDCwGv?p=preview

This requires the patch from: #2003

@nateabele
Copy link
Contributor

@OnkelTem I'm not sure I follow. #2003 has been merged. Is there something further to do here?

@OnkelTem
Copy link

OnkelTem commented Dec 2, 2015

No actually, I just leaved a message for those seeking additional information.

@nateabele
Copy link
Contributor

Ah, okay. 😄 Thanks!

legendar pushed a commit to legendar/ui-router that referenced this issue Dec 14, 2015
The old $viewContentLoaded event wasn't needed for the one internal
client (ui-view) because ui-view always ignores that event and acts on
a $stateChangeSuccess event that follows right behind anyway.

And it wasn't as useful to external clients as it could be because
it wasn't delivered on every view update -- it was delivered only
on state transitions that activate a state defining a view, and
didn't deal with inheritance.

Also, neither $viewContentLoading nor $viewContentLoaded events
contained the name of the view loading or loaded.

This change makes $viewContentLoading and $viewContentLoaded be
emitted always in pairs, before/after updateView does the work of
actually loading the view, and both events include the name of the
view being loaded.

This is a breaking change for users of the old $viewContentLoading
event that relied on it being broadcast from the root scope, the
precise time it was broadcast, the fact that it wasn't always sent
when a view is loaded even if $viewContentLoaded will be sent, or the
"options" parameter that was emitted with it. If people rely on any of
that behavior and we can't break it, then we can restore the old
behavior but I think there needs to be some other event which is
reliably paired with $viewContentLoaded and contains the view name,
and I can't think of a better name than $viewContentLoading for that
event.

Closes angular-ui#685.
@christopherthielen christopherthielen modified the milestones: 0.2.16, 1.5.0 Feb 6, 2016
ExpFront pushed a commit to ExpFront/ui-router that referenced this issue Jun 23, 2016
The old $viewContentLoaded event wasn't needed for the one internal
client (ui-view) because ui-view always ignores that event and acts on
a $stateChangeSuccess event that follows right behind anyway.

And it wasn't as useful to external clients as it could be because
it wasn't delivered on every view update -- it was delivered only
on state transitions that activate a state defining a view, and
didn't deal with inheritance.

Also, neither $viewContentLoading nor $viewContentLoaded events
contained the name of the view loading or loaded.

This change makes $viewContentLoading and $viewContentLoaded be
emitted always in pairs, before/after updateView does the work of
actually loading the view, and both events include the name of the
view being loaded.

This is a breaking change for users of the old $viewContentLoading
event that relied on it being broadcast from the root scope, the
precise time it was broadcast, the fact that it wasn't always sent
when a view is loaded even if $viewContentLoaded will be sent, or the
"options" parameter that was emitted with it. If people rely on any of
that behavior and we can't break it, then we can restore the old
behavior but I think there needs to be some other event which is
reliably paired with $viewContentLoaded and contains the view name,
and I can't think of a better name than $viewContentLoading for that
event.

Closes angular-ui#685.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.