0

Environment: AngularJS application (using UI Router) that has nested states.

Short version: How do I change the value of a parent-state's dependency from inside a child's controller, such that siblings of the child state, who are also using the dependency will get the new value.

Long version: Take the following configuration:

shopApp.config(function($stateProvider) {
    $stateProvider.state('shop', {
        url : '/shop',
        templateUrl : 'shop.html',
        resolve : {
            user : function($q) {
                // callback that ends up returning null 
                // because the user is not logged in
            }
        },
        controller: function($scope, user) {
            // user is null at this point
            $scope.user = user;
        }
    }).state('shop.login', {
        url : '/login',
        templateUrl : 'login.html',
        controller : function($scope) {
            // onLoggedIn is called from some form-processing
            // logic once the user has successfully logged in
            $scope.onLoggedIn = function(userObject) {
                // I want the shop.userDetails state's controller
                // to get the new $scope.user value
                $scope.user = userObject;
                $state.go('^.userDetails');
            };
        }
    }).state('shop.userDetails', {
        url : '/userDetails',
        templateUrl : 'userDetails.html',
        controller : function($scope, user) {
            // Unfortunately user is null here
            $scope.user = user;
        }
    });
});

The shop.login state's controller has a function called onLoggedIn that's called once the user has logged in successfully. An object containing user-information is passed to this function. From this point on I want all resolutions of the user dependency to use this value (i.e userObj). Unfortunately, instead of using the value that I've just assigned to $scope.user, it seems that the value that gets passed to the controller of shop.userDetails is the one that was originally resolved when the shop state's controller was created.

Does anyone know how to do this? If I'm doing this all wrong please tell me.

2 Answers 2

1

One way of doing what you want is to define a UserDetailsService that holds the logged in user information. You set the user information on login and resolve user value by using the service.

Sign up to request clarification or add additional context in comments.

3 Comments

I found this blog post, which expands on what you're suggesting by pointing out that if you make it an object rather than an atomic value then Angular will watch for changes to the object and therefore update the bindings when changes are made to the object.
I think the reason why I didn't think of using a service was that I think of services as components that do something rather than just being a place to store stuff. I think it'd be nice if Angular supplied a place for developers to store 'app-scoped' variables. Actually, I suppose I could probably just add my variable to $rootScope?
A service that you store stuff is basically you StorageService, isn't it? You store stuff in rootScope too, I think it is a matter of taste.
0

There is a rather straightforward approach using scope objects with more semantics:

Looking at your problem, I think, the relevant information that you need to model here is not only the user, which can be null and changing which during login leads to your problem. Rather you want to share the login state of your application (or the application state, in more general), i.e. is a user logged in, since when , which user and so on. This is also good design, since you keep information together, which are likely to be changed or extended in common. So instead of a single property, which can be null and thus gets created in the wrong scope, use a complex property, which will be created at the topmost scope, underneath which it will be shared and visible, in your code example that woul be in the shopController:

// in ShopController
$scope.loginState = { logggedin: false, user: null }; 
// could add time information, grants and other stuff here as well

// in LoginController
$scope.loginState.loggedin = true;  
$scope.loginState.user = user;  
// crucial: this will only change the attributes at the object in ShopControllers Scope

In the user detail template you could then use {{loginState.user}} directly, the user will be fetched from the parent scope (shop), where it will have been set by the sibling (shop.login).

This works, because like in templates, if you access a scope property reading, the scope hierarchy is scanned bottom up, until the property is found. Of course, you have to be careful not to assign the #loginState itself in deeper scopes. But you may use and change the properties (or even create new ones) of the common loginState object in the top scope.

But if you like to use {{user}} in the template, e.g. to make it more readable and reusable, this can easily be done by the following:

// in UserDetailController
$scope.user = $scope.loginState.user;   

This works, because UserDetailController will be initialized anew after switching from shop.login to shop.userdetail in your example. In other cases, you might want to use a watch on $scope.loginState, create an event $userLoggedIn or similar approaches.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.