0
votes

Here is my code with the relevant parts bolded:

authorization.js

 angular
      .module('mean-starter')
      .run(run);

    function run($rootScope, Auth, $state) {
      $rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
        if (typeof toState.authenticate !== 'undefined') {
          var currentUser = Auth.getCurrentUser();
          while (!currentUser._id) {}
          var isAdmin = currentUser.role === 'admin';
          var isAuthorized = currentUser._id.toString() === toParams.id;
          if (!Auth.isLoggedIn()) {
            event.preventDefault();
            alert('Must be logged in to access this route.');
            $state.go('login');
          }
          else if (toState.authenticate.authorized) {
            if (!isAdmin && !isAuthorized) {
              event.preventDefault();
              alert('You are not authorized to access that route.');
            }
          }
          else if (toState.authenticate.isAdmin) {
            if (!isAdmin) {
              event.preventDefault();
              alert('You must be an admin to access this route.');
            }
          }
        }
      });
    }

auth.factory.js

angular
  .module('mean-starter')
  .factory('Auth', function($http, $state, $window, $cookies) {
    console.log('factory cb');
    var currentUser = {};
    if ($cookies.get('userId')) {
      console.log('userId');
      $http
        .get('/current-user')
        .success(function(data) {
          console.log('success');
          angular.copy(data, currentUser);
        })
        .error(function() {
          console.log('Problem getting the current user.');
        });
    }

    return {
      signup: function(user) {
        return $http.post('/users', user)
                  .success(function(data, status, headers, config) {
                    angular.copy(data, currentUser);
                    $cookies.put('userId', data._id);
                    $window.location.href = '/';
                  });
      },
      login: function(user) {
        return $http
                  .post('/login', user)
                  .success(function(data) {
                    angular.copy(data, currentUser);
                    $cookies.put('userId', data._id);
                    $window.location.href = '/';
                  });
      },
      logout: function() {
        $http
          .get('/logout')
          .success(function() {
            angular.copy({}, currentUser);
            $cookies.remove('userId');
            $window.location.href = '/';
          })
          .error(function() {
            console.log('Problem logging out.');
          });
      },
      getCurrentUser: function() {
        return currentUser;
      },
      isLoggedIn: function() {
        return !!currentUser._id;
      }
    };
  });

My problem is that without the while loop, I get this error:

Cannot read property 'toString' of undefined

It's referring to currentUser._id being undefined and me trying to call toString on it. My understanding is that Auth.getCurrentUser() initially returns a reference to {}. Then the assignment statement assigns {} to currentUser, and the code proceeds. When the response comes back, it should update {}, and thus should "update" currentUser in the sense that currentUser is pointing to an updated object.

If that were true, my problem would be understandable, because it's trying to do currentUser._id.toString() before the response comes back. So what I tried to do is put that while loop in there to essentially pause the execution until the response comes back. But the while loop is running infinitely! Shouldn't the response eventually come back, update currentUser, and when it does, !currentUser._id should be false, and the loop should break?

First factory cb is logged out. Then userId is logged out. So far so good. But then the infinite loop kicks in and success never gets logged out. Isn't the request supposed to be asynchronous? How could the while loop stop it? What's going on here?

There isn't a problem with the call. Without the while loop it hits the success callback and logs success. Additionally, if I console.dir(currentUser) in authorization.js it gives me the user, but if I console.log(currentUser), it gives me an empty object. Not sure why that is.

3
A while loop will not pause execution. Please see here for more information on loops and scope: stackoverflow.com/questions/18465211/…dannypaz
@livepo if the while loop isn't pausing execution, then what's the problem?Adam Zerner
Your code is executing before information is returned from your ajax calls. You put in a while loop to stop execution, but a while loop will not stop execution (code is async). When you declare currentUser = Auth.getCurrentUser, from what I understand, you are passing by value. currentUser's value will never change, so the loop will continue to run. Please see this article for info on reference vs value: stackoverflow.com/questions/7744611/…dannypaz
@livepo it passes by reference. console.dir(currentUser) works. If it passed {} by value, currentUser would never get updated. It also works in other parts of my code.Adam Zerner
What also works in other parts? and can you give us an example of where it does work?dannypaz

3 Answers

2
votes

AFAIK, as Javascript is single-threaded, while the thread is spinning in the while loop, the http callback will not get CPU time to process the response.

1
votes

What appears to be happening is that your Auth stuff is all being checked asynchronously (which makes sense because it has to do an HTTP call, which isn't instant)

So at the point where the script is run, the result of Auth.getCurrentUser(), i.e. the currentUser object, isn't populated with the data you're trying to access, since it's being written at this line:

angular.copy(data, currentUser);

Which is indeed inside an async block. So what's happening is that presumably _id isn't yet a property on currentUser, and so you are in fact calling toString() on undefined.

Your solution might be to use callbacks or promises. If you're the author of auth.factory.js, what you might consider doing is rather than returning the object synchronously in getCurrentUser, return a promise or a callback that is fulfilled when the HTTP request has completed.

EDIT: This is also why your console.dir in your script won't return anything, because at the point it runs, it wouldn't've been populated with any data.

0
votes

Angular services are singletons, meaning that they are created just once, the first time they are needed and then the same object is passed as a dependency here and there. This means that the code that fetches the current user is only run once. What happens if your user is not logged in then? Nothing.

Since you are using cookies you could do the following when you want the current user (getCurrentUser) you should look if the data are stored on the cookie, if yes then proceed with your code. If the current user is null, then you are not logged in and you should query your API for the data. That means that you should run the code inside a promise.

Since the code you run when the user is logged in and when you query the API is the same, you should move that code to a service to be reusable.

Also each time the user logs in, logs out etc it would be wise to broadcast an event and have the parts of your application that need this info listen to it and do whatever is required.