3

So It took me a very long time to understand this issue and get to a mcve. Here is the case : I'm trying to redirect a user to a login page when he's not authenticated (this is very basic). Here is the code :

HTML :

<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.6/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.13/angular-ui-router.min.js"></script>
    <script src="js/app.js"></script>
</head>
<body ng-app="starter">

<div ui-view></div>

<script id="login.html" type="text/ng-template">
    l
</script>

<script id="welcome.html" type="text/ng-template">
    w
</script>
</body>
</html>

JS :

    angular.module('starter', ['ui.router'])

.run(["$rootScope", "$state", function($rootScope, $state) {
    $rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
        if (error === "nope") {
            $state.go("login");
        }
    });
}])

.config(function($stateProvider, $urlRouterProvider) {
    $urlRouterProvider.otherwise('/welcome');

    $stateProvider
        .state('welcome', {
            url: '/welcome',
            templateUrl: 'welcome.html',
            resolve: {
                "auth": ["$q", function($q) {
                    return $q.reject("nope");
                }]
            }
        })

        .state('login', {
            url: '/login',
            templateUrl: 'views/login.html'
        })
});

The "views/login.html" contains just "vl" to see it showing on the screen. So, when the above code is ran, the output is good, but look at the Chrome console :

crazy error

Now, the crazy part of it, is if you modify the JS and replace

templateUrl: 'views/login.html'

in the login state by simply

templateUrl: 'login.html'

using the ng-template defined in the html, ALL IS GOOD ! So well, I guess using a file as template must trigger some watcher or whatever... Well no, I'm just out of ideas.

Thanks ahead for your help !

[EDIT]

here are two things that could help :

  • There is no error when using ngRoute in place of UI Router
  • There is no error if replacing Angular version with the 1.2.25

So I think it's a bug with UI Router and Angular 1.3. But don't know at all what to do to solve it.

Community
  • 1
  • 1
Jeremy Belolo
  • 4,319
  • 6
  • 44
  • 88

2 Answers2

3

You need to call event.preventDefault() before you call $state.go().

See here: http://plnkr.co/edit/eUosIpdN7adJFxfDo3kV?p=preview

The main reason this error is occuring, as you have already identified, is that the template url 'views/login.html' doesn't exsit. You said that when you changed the template url from 'views/login.html' to 'login.html' everything worked, and this makes sense because that template does exist.

The reason you are seeing the infinite digest error is that each time you try to access the welcome state the auth resolver is throwing an error which triggers the $stateChangeError event like you would expect it too. Everything is fine up until the point when it tries to go to the login state. Attempting to transition to the login state fails because the template url 'views/login.html' doesn't exist, and this failure is causing your otherwise redirect to bring you back to the welcome state. You can see at this point that arriving back at the welcome state will restart the whole cycle and cause the infinite digest loop.

Simply ensuring that your template urls are correct should prevent this from happening, as you have already discovered.

lukewestby
  • 1,207
  • 8
  • 15
  • Hey man, thanks for trying to answer but no, the template actually exists, as I wrote, it contains just some random text. Also this code works, it's finally displaying what is in "views/login.html", but before that it does loop the digest cycle a bunch of times - only when the template is a file, if it's an inline ng-template, as I said, everything is fine. – Jeremy Belolo Dec 31 '14 at 05:33
  • 1
    From what I could understand by reading the source code, calling `event.preventDefault()` will prevent a call to `urlRouter.update()` immediately following the event broadcast. `urlRouter.update()` is responsible for synchronizing with `$location`, so this may have been interfering with the transition to your `login` state. – lukewestby Dec 31 '14 at 18:46
  • You man are the best, if I could I would vote you up 10 times :) Thanks again ! – Jeremy Belolo Dec 31 '14 at 18:50
  • 1
    No problem! That's very kind of you :) – lukewestby Dec 31 '14 at 18:52
2

I have had the same problem and this is how I solved it

.run(function($rootScope, $state) {
        $rootScope.$on('$stateChangeError', function() {
          // Redirect user to our login page
          $state.go('auth.login');
        });
      });

Below I pasted my entire page so you can see how it all works together 'use strict';

angular.module('mean.pdash').config(['$stateProvider',

function($stateProvider) {
  // Check if the user is connected
  var checkLoggedin = function($q, $timeout, $http, $location) {
    // Initialize a new promise
    var deferred = $q.defer();
    $http.get('/loggedin').success(function(user) {
      // Authenticated
      console.log(user);
      if (user !== '0') {
        $timeout(deferred.resolve);
      } else {
        $timeout(deferred.reject);
      }
    });

    return deferred.promise;
  };
  $stateProvider.state('pdash', {
    url: '/',
    templateUrl: 'pdash/views/index.html',
    controller: 'PdashController',
    resolve: {
      checkLoggedin: checkLoggedin
    }
  });
}
])
  .run(function($rootScope, $state) {
    $rootScope.$on('$stateChangeError', function() {
      // Redirect user to our login page
      $state.go('auth.login');
    });
  });
acdclive
  • 69
  • 1
  • 6