3

I have a UserService

angular.module('mango.services', [])
    .factory('UserService', function() {
        var user = {
            id: null,
            name: 'anonymous.'
        };
        function getUser(){
            return user;
        }
        function setUser(val){
            user = val;
        }
        return {
            getUser: getUser,
            setUser: setUser,
        }
});

a NavbarController

.controller('NavbarController', ['$scope','$location','UserService', function($scope, $location, UserService){
    $scope.isActive = function (viewLocation) {
        return viewLocation === $location.path();
    };
    $scope.username = UserService.getUser().name;
}])

and a UserController where I have registerUser and loginUser functions.

.controller('UserController', ['$scope', '$http', 'UserService', function($scope, $http, UserService) {
   $scope.loginUser = function(){
        $http.post('/api/1.0/user/authenticate', $scope.user)
            .success(function(data,status,headers,config){
                if (data["valid"] === true) {
                    UserService.setUser(data.user);
                } else {
                    $scope.flashes = data.flashes;
                    $scope.user.password = "";
                }
             })
}

and the HTML

        <li ng-switch="username">
            <a ng-class="{ active: isActive('/user/login')}" href="#/user/login" ng-switch-when="anonymous."><i class="fa fa-sign-in"></i> Sign in</a>
            <a ng-class="{ active: isActive('/user/logout')}" href="#/user/logout" ng-switch-default><i class="fa fa-sign-out"></i> Sign out</a>
        </li>

As you can see I'm trying to set the user of UserService if data.valid is true.

The server is returning a valid json object.

But the username value in NavbarController remains "anonymous." .

I'm not very experienced in JS, but I read something about broadcast and watch. I believe this might be the right approach. But maybe there's a better one.

I believe why it's not working is because the factory returns a singleton. But then using a factory is pointless.

So essentially what I want is, if credentials valid set user.name user.id client-app-wide. Later it should go through an "check if client user is valid" service. My session cookie is encrypted. But that's out of scope of the question.

All I need right now is to set the app's or rather the NavbarController's user data from UserController. How do I do that so it also updates the DOM aka ng-switch getting a different value.

1
  • Have you tried using a service instead of a factory? I believe services use a single object, whereas factories make new references every time they're injected. Commented Dec 10, 2013 at 21:46

3 Answers 3

2

It is not working because you are not creating a binding of some kind: with $scope.username = UserService.getUser().name you get the user name at that instant of time (which is anonymous) and hold on to it forever. One way out of this is with a watch. In NavbarController, replace the previous code with:

$scope.$watch(
    function() {
        return UserService.getUser().name;
    },
    function(newval) {
        $scope.username = newval;
    }
);

This will incur your application with a function call in every digest cycle. This function call is not slow, so it wouldn't matter.

If you do not want this overhead, you can also do it with events. In NavbarController:

$scope.username = UserService.getUser().name;
$scope.on("loggedIn", function(event, newUserName) {
    $scope.username = newUserName;
});

And in UserController (add $rootScope to the dependencies):

if (data["valid"] === true) {
    UserService.setUser(data.user);
    $rootScope.$broadcast("loggedIn", data.user);
}
Sign up to request clarification or add additional context in comments.

1 Comment

thank you, events are the better solution. $broadcast and $on are similar to signals/slots of Qt, a concept I'm familiar with.
1

You indeed do need a $watcher to sync username in the NavbarController and UserService instead of $scope.username = UserService.getUser().name; which only sets up the initial value of $scope.username when the Controller is initialised:

$scope.$watch(
    function () { return UserService.getUser().name; }, 
    function (newVal) {
        $scope.username = newVal;
    }
);

The factory indeed designed to be a singleton across the application and that is the place where you should save the state of the application (i.e. your models, like the User model). The controllers are ephemeral and are created anew each time a template is created.

However, if you have a long lived Controller (like your NavbarControll), then the onus of maintaining a sync between the services and the controller is on the programmer using $watch.

Broadcasting messages is useful in some cases when inter-controller communication (which does not necessarily involve the models) is needed.

2 Comments

thank you, I picked Nikos one because it also had the broadcast example (and was 3 minutes quicker than you). I was quite undecided.. since you've explained it better. So I oked his answer and upvoted yours ~
@user2312578 Sure, though you should upvote his answer as well. Also, I am not sure you have correctly worked out who was quicker. :)
0
  • You can same approach without using $watch.

I have written a short function that let you put data in Object without replace it. You can use it in you service. This way youu don't need to use watch. You can use the normal digest loop of Angular. For example, see this simple controller & service:

Demo: JSFidde - http://jsfiddle.net/y09kx7f7/1/ with assert tests

Controller:

app.controller('dem',function($scope,me){
    $scope.user=me.user;   // {name:'user name'}
})

The controller will be chnage automatically without watch every time the user name changed!

Service

.factory('me',function($timeout){
    var obj={}
    obj.user={name:'hello'}
    $timeout(function(){  //Example of change the data
        newUser={name:'israel'}
        cloneInto(obj.user,newUser)
    },1000)
    return obj
})

The cloneInto function that I written:

// The function it is like a=b, 
// but keeping the object in his original memory position.
function cloneInto(a,b){
    var i
    for (i in a){
        if(typeof a[i]!='object') //For supporting deep-copy
           delete a[i]
    }
    for (i in b){
        if (typeof b[i]=='object'){
            if(!a[i]) a[i]={}
            cloneInto(a[i],b[i])
        }
        else{
            a[i]=b[i]
        }
    }
}

Comments

Your Answer

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