angular / angular.js

AngularJS - HTML enhanced for web apps!
https://angularjs.org
MIT License
58.82k stars 27.51k forks source link

delay on $routeProvider in main controller #7053

Closed plong0 closed 9 years ago

plong0 commented 10 years ago

Assuming I have in my index.html file:

<html ng-app="MyModule" ng-controller="MainCtrl">

and a MainCtrl looking like:

angular.module('MyModule')
  .controller('MainCtrl', function ($scope, $routeParams) {
      console.log('routeParams:'+JSON.stringify($routeParams));
  });

$routeParams is coming up empty... unless I wrap it in a very short timeout:

angular.module('MyModule')
  .controller('MainCtrl', function ($scope, $routeParams, $timeout) {
    $timeout(function(){
      // do stuff with $routeParams
      console.log('routeParams:'+JSON.stringify($routeParams));
    }, 20);
  });

I didn't experience the problem when I used yo cg-angular to generate my project, but started experiencing it after I rebuilt my project using yo angular-fullstack

A couple others having similar trouble: http://stackoverflow.com/questions/17519937/routeparams-is-empty-in-main-controller/22955711

kcmerrill commented 10 years ago

+1

We were having the same issue as well. It could very well be that we are flat out implementing it wrong but ... can confirm the issue and @plong0 fix.

grabus commented 10 years ago

+1 the same issue.

noducks commented 10 years ago

You can also inject the $route service which will show your params immediately.

angular.module('MyModule')
  .controller('MainCtrl', function ($scope, $route) {
      console.log('routeParams:'+JSON.stringify($route.current.params));
  });
elliterate commented 10 years ago

The $routeParams service is populated asynchronously. This means it will typically appear empty when first used in a controller.

To be notified when $routeParams has been populated, subscribe to the $routeChangeSuccess event on the $scope. (If you're in a component that doesn't have access to a child $scope, e.g., a service or a factory, you can inject and use $rootScope instead.)

module.controller('FooCtrl', function($scope, $routeParams) {
  $scope.$on('$routeChangeSuccess', function() {
    // $routeParams should be populated here
  });
);

Controllers used by a route, or within a template included by a route, will have immediate access to the fully-populated $routeParams because ng-view waits for the $routeChangeSuccess event before continuing. (It has to wait, since it needs the route information in order to decide which template/controller to even load.)

If you know your controller will be used inside of ng-view, you won't need to wait for the routing event. If you know your controller will not, you will. If you're not sure, you'll actually have to explicitly allow for both possibilities. Subscribing to $routeChangeSuccess will not be enough; you will only see the event if $routeParams wasn't already populated:

module.controller('FooCtrl', function($scope, $routeParams) {
  // $routeParams will already be populated
  // here if this controller is used within ng-view

  $scope.$on('$routeChangeSuccess', function() {
    // $routeParams will be populated here if
    // this controller is used outside ng-view
  });
);

The reason your use of $timeout works is because you happen to be waiting long enough to allow the router to finish its work. But this introduces a race condition and an unnecessary delay; subscribing to the routing event allows you to react as soon as possible without risk of reacting too soon.

gabn88 commented 9 years ago

when using multiple nested controllers, it seems the $routeChangeSucces function may hit multiple times and I seem to need the last one. So, for every nesting I need to wait for one more hit. It would be nice if this could 'somehow' be fixed I think.

petebacondarwin commented 9 years ago

I don't think this is a bug in Angular. Routing is inherently async. @elliterate is correct here. Either listen to the route change success event or put your route param dependent code in a route controller.