1

Im currently having an issue where when I call a function in a service that changes its own internal variable it doesn't appear to stay unless I change it from a controller. When I output the variable to the console right after I change it, it appears with the correct value. However when I call a function in the controller that prints the object to console it shows the original value.

The variable Im having issues with is "isSignedIn" which always shows false (confirmed with the $scope.verify method in login controller) even after the line "console.log('Signin:' + isSignedIn);" shows true.

Thanks ahead of time for any guidance!

Service Skype

angular.module('Skype', ['ng'])
    .service('skypeClient', function () {
        //Service Properties
        var client = new Skype.Web.Model.Application;
        this.errors = [];
        this.errorCount = -1;
        var isSignedIn = false;
        var state = 'SignedOut';

        var init = function () { 
            client.signInManager.state.when('SignedIn', function () {
                isSignedIn = true;
                console.log('Signin:' + isSignedIn); // This outputs the correct value
            });
            property = client.signInManager.state;
            property.changed(function (status) { 
            state = status;
           console.log("New State"+status) });
           }

        //Signin Function
        var signIn = function (username,password) {
            client.signInManager.signIn({
                username: username,
                password: password
            }).then(function () {
               isSignedIn = true;
                this.errorCount++;
                console.log(this.errorCount);
            });
        }

        //SignOut Function
        var signOut = function () {
        client.signInManager.signOut()
        .then(function () {
           this.isSignedIn = false;
        }, function (error) {
            this.erros.push(error);
            this.errorCount++;
        });
        }

        return {
            signIn: signIn,
            signOut: signOut,
            init: init,
            state: state,
            isSignedIn: isSignedIn
        };

});

Controller Login

'use strict';
angular.module('login', ['ngRoute', 'classy', 'Metio.Skype'])
.config(['$routeProvider', function ($routeProvider) {
    $routeProvider.when('/login', {
        templateUrl: 'app/views/login.html',
        controller: 'loginCntrl'
    });
}]).controller('loginCntrl', function ($scope,skypeClient) {

    skypeClient.init();

    $scope.skypeClient = skypeClient;
    console.log('LoginCntrl Loaded');

    $scope.signIn = function () {
        skypeClient.signIn($scope.user, $scope.password);
    }
    $scope.signOut = function () {
        skypeClient.signOut();
    }
    $scope.verify = function () {
        console.log(skypeClient);
    }
});

[EDIT] Modified Code according to pdenes recommendations(comments), same issue but cleaner

Factory Skype

.factory('skypeClient', function () {
        //Service Properties
        var client = new Skype.Web.Model.Application;
        var state = 'SignedOut';
        //Initialize Listeners
        var init = function () {
            client.signInManager.state.when('SignedIn', function () {
                console.log('Signin:' + state); // This outputs the correct value
            });
            property = client.signInManager.state;
            property.changed(function (status) {
                state = status;
                console.log("New State" + state);
            });
            console.log('init');
           }
        //Signin Function
        var signIn = function (username, password) {
           client.signInManager.signIn({
                username: username,
                password: password
            }).then(function () {console.log('LoggedIn');});
        }
        init();
        return {
            signIn: signIn,
            state: function(){return state}
        }
   });

Controller Login

.controller('loginCntrl', function ($scope,skypeClient) {
    $scope.skypeClient = skypeClient;
    $scope.signIn = function () {
        skypeClient.signIn($scope.user, $scope.password);
    }
    $scope.verify = function () {
        console.log(skypeClient);
        console.log($scope.skypeClient);
    }
});

DOM Markup

<input type="checkbox">Keep Me Signed In (Currently Signedin: {{skypeClient.state()}} )

Console Output and DOM Changes

When I hit the signin function the console logs "New State SigningIn" and the dom changes to "Currently Signedin: SigningIn" but when I get the next event fired the console logs "New State SignedIn" but the DOM still reflects the old value "Currently Signedin: SigningIn" so the binding only appears to update the first time but not subsequent times.

John Slegers
  • 45,213
  • 22
  • 199
  • 169
strspl
  • 63
  • 2
  • 6

2 Answers2

7

The value in the object that you return as the service is not the same as the isSignedIn variable you defined earlier. So:

...
var isSignedIn = false;
...
return {
   isSignedIn: isSignedIn  
   //          ^this is false when the object is created
   //           and will remain false, no matter how you
   //           change the value of the above variable later
};

To expose the value of isSignedIn in your closure from the service, you'd need a function, something like:

...
return {
  ...
  isSignedIn: function () { 
    return isSignedIn; // <- this refers to the above variable itself 
  }
};

EDIT: Follow up for the updated code in the question...

So apparently the value is updated properly internally, but the {{skypeClient.state()}} binding is still not updated (only when some other action somehow "forces" it). This is probably happening because the value is updated by something outside of Angular's digest cycle, so there is nothing telling Angular to update things at the right moment.

Looking at the code, state is assigned a new value inside a callback for property.changed (btw, property should properly be declared with var!). Try wrapping that line in an $apply() to make Angular update the bindings!

pdenes
  • 782
  • 6
  • 9
  • Thank you for the incredibly quick response! Two part question when I try to bind using {{skypeClient.isSignedIn()}} the binding doesnt seem to update when the value changes(Do I need to add a watch?). Second part is it possible instead of doing a return block in the service just using "this.isSignedIn" in the service and accessing it directly from the controller? I had tried this second method prior but ran into the same issue. – strspl Aug 16 '15 at 01:45
  • It might help to note, it appears that if I call the $scope.verify method which prints the service object to console, the values update Thanks – strspl Aug 16 '15 at 02:01
  • You're welcome! :) First one: the {{skypeClient.isSignedIn()}} binding works fine for me, you shouldn't need a watch... Can you please post some code to see what you're trying to do? Second: you're right, I've just noticed you're using a service and not a factory -- so instead of returning an object, you should populate "this" (your function is a constructor). – pdenes Aug 16 '15 at 12:11
  • I also noticed some confusion about the usage of `this` in the original code: e.g. `errors` is set on `this`, but `isSignedIn` is not. In the `.then` callback in `signIn`, you're referencing `this.errorCount` -- I think _this_ is the wrong `this`! In `signOut`, you have `this.isSignedIn` -- also seems wrong. Generally I find it easier to use `factory` instead of `service`, and only closures, avoiding the confusion about `this`. This may also be interesting: [When to use service instead of factory](http://stackoverflow.com/questions/18939709/when-to-use-service-instead-of-factory?rq=1) – pdenes Aug 16 '15 at 12:12
  • Another one: [AngularJS - The correct way of binding to a service properties](http://stackoverflow.com/questions/15800454/angularjs-the-correct-way-of-binding-to-a-service-properties) – pdenes Aug 16 '15 at 12:23
  • I have added to updated code to my original post after trying your recommendations along with a more succinct explanation of the output and what Im trying to do(Couldnt figure out code post in comments). Also the two reference posts were very helpful! Though I cant seem to achieve success with them, which im hoping is an error on my part. Thanks again for such thorough responses, this is an amazing community! – strspl Aug 16 '15 at 21:12
0

In place where you call service (factory) value that is returned by function add a $watch. That will update value if it changes in the service.

$scope.$watch(function() { return ModalService.getInfo(); }, function(){
    $scope.info = ModalService.getInfo();
}); 

See solution

Community
  • 1
  • 1
Martins
  • 39
  • 5