6

I come from a static typed object oriented background (C#) and am new to Angular and Javascript in general. I am trying to build an app using Angular and JQueryMobile and am facing a situation where the services are not behaving like singletons - i.e. even though they have been initialised once in one controller, the properties within the service are not storing any state that they were set to when passed into another controller or service. Also, I am experiencing some unexpected behaviour when I try to debug the code which is described below:

My setup:

  • I am using the JQueryMobile single page template having all the pages of the app as divs on the same html page and the redirection takes place using "href="#DivName" pattern
  • Every div(page) of the app has an associated controller and service
  • The controllers get injected with multiple services as per requirement and the injected services are expected to retain the state that they were initialised to in other controllers.
  • I am not using routing and templates - the whole html is in a single page

The relevant code outline is below:

HTML:

<div data-role="page" id="PageA" data-ng-controller="PageAController">      

<!-- page content and UI elements-->    

</div>


<div data-role="page" id="PageB" data-ng-controller="PageBController">

<!-- page content and UI elements-->    

</div>

SERVICES:

var App = angular.module('App', []);

App.service('serviceA', function () {

    var propA;

    //objectX is an array of objects with each object containing a hashtable and some other properties 

    propA = [{},{}];  //hardcoded value for objectX gets returned properly

    return {

        initialize: function(objectX){          

            propA = objectX; // Dynamically initialized objectX value is returned as "undefined" by the getPropA function below
        },                 

        getPropA: function () {               

            return propA;
        }
    };
});

App.service('serviceB', function (serviceA) {

    var propB;

    return {

        initialize: function(){         

            propB = serviceA.getPropA();    //THIS DOES NOT WORK AS THE VALUE RETURNED IS UNDEFINED     
        },                 

        processPropB: function () {               

            //logic for processing of propB
        }
    };
});

CONTROLLERS:

App.controller('ControllerA', ['$scope', 'ServiceA',
function ($scope, ServiceA) {    

    $scope.UITriggeredAction = function(){          
        ServiceA.initialize(objectX);
    };    

}]);

//This controller gets invoked at a later point during the user interaction where ServiceA is supposed to be initialised and injected


App.controller('ControllerB', ['$scope', 'ServiceB', 'ServiceA',
function ($scope, ServiceB, ServiceA,) {    

    var returnedPropA = ServiceA.getPropA(); //THIS RETURNS UNDEFINED TOO

    //process logic to use serviceB depending on valu of returnedPropA

}]);

QUESTIONS:

  • In the above code for serviceA, if I initialise the value for propA by hard coding it before the return block, the value does get returned when getPropA() method is executed. Please explain how this works.

  • I would also like to know the order of invoking the the javascript code in Angular - my understanding is that angular runtime invokes the appropriate controller's function when the relevant page is loaded in the browser and the controller code in turn invokes the service methods. But if I have alerts in different controllers and/or services and load the page, the alerts get displayed right away even though the page or controller/service was not supposed to be invoked. What is even more odd is that the alerts do not get executed when the actual controller or service method in which they were placed does get executed. It seems like all the controllers are getting executed on page load.

Please let me know how to setup my code so that I can pass the services in different controllers without them losing their state once initialised or updated anywhere during the execution.

Thanks, Shantanu

EDIT:

The answer I think lies right there in my second question. For me - the control flow for the execution of the code is not clear. Below is what I think the problem is:

Basically, both the angular controllers are getting executed on page load by the angular runtime rather than in the sequence that they are loaded and used in the app - though this might just be a JQMobile thing.

When an assignment operation takes place on a user interface action, the corresponding variable in the service is set successfully. But the angular runtime does not re-evaluate all the controllers to ensure that the new assignment is reflected across all the controllers.

So the other controller continues to return what it was set to at initialization - undefined.

But if the variable is set at page load itself - that is the value returned as the initialization takes place with the required value and everything works.

As requested I have also created below a simple plunk to show the above points with comments: http://plnkr.co/edit/yBOL0P3ycsoNzVrydaMs

As a solution to the problem, below is what I think should work:

  • Somehow ensure that the controllers are initialized only when required rather than all at once on page load so that the initialization can take place as per the state set in the previous actions. I think can be achieved through templates and routing so that the controllers are invoked only when the required route and template is called foe. I'm not sure if this is the way it works.

  • The second option is to publish events in setting controllers and subscribe to them wherever required to keep things in sync. This seems like a better approach.

I will confirm this and post an answer if it works.

Please let me know your thoughts.

Sorry for the long post :)

10
  • 1
    This is a really long question. Maybe try to divide it into different questions and have jsFiddle/plnkrs to that demonstrate your issues. Commented Mar 17, 2014 at 17:15
  • I suspect you might have a typo. JavaScript is case sensitive, so serviceA is different from ServiceA. Commented Mar 17, 2014 at 22:30
  • Is this a direct copy/paste? function ($scope, ServiceB, 'ServiceA',) {. Shouldn't it be: function ($scope, ServiceB, ServiceA) { Commented Mar 22, 2014 at 5:42
  • @TheRocketSurgeon - this is not a direct copy/paste and it was a typo which has been corrected. Have also added a plunker example - please check it out. Thanks. Commented Mar 22, 2014 at 5:52
  • Not sure if it will help, but I did a codepen for a similar question: codepen.io/yakovkhalinsky/pen/LeirK Commented Mar 22, 2014 at 5:54

3 Answers 3

4

This is an example of the asynchronous nature of javascript. Basically everything is instantiated on page load so the value may or may not be populated. A awesome solution that Angular provides for us is to use a promise - $q - or we could use $timeout to poll but thats just sloppy.

Here is your code with the awesome promise to the rescue!

First the link to the plunker: http://plnkr.co/edit/OCgL8jATTdScRbYubt9W

Some code to look at:

Controllers:

var app = angular.module('plunker', []);

app.controller('ControllerA', ['$scope','serviceA',function($scope, serviceA) {

  $scope.initializeA = function(){          
        serviceA.initialize("Chris");
    };  
}]);


app.controller('ControllerB', ['$scope', 'serviceB', 'serviceA', function($scope, serviceB, serviceA) {

  $scope.initializeB = function(){          

        serviceA.getPropA().then(function(data) {
          alert("ControllerB : " + data);
        });

        serviceB.initialize();

        serviceB.processPropB();

    };

    /////////////////////////////    
    //Controller Initialization//
    /////////////////////////////

        serviceA.getPropA().then(function(data) {
          alert('now i have the data: ' + data)
        });

        serviceB.initialize();

        serviceB.processPropB();

}]);

They are now set up to process their code when promises are resolved.

Now where we set the promises up:

Services:

ServiceA:

 app.service('serviceA', function ($q) {

    var 
      propA,
      deferred = $q.defer()
    ;

    return {

        initialize: function(name){          

            propA = name; 
            deferred.resolve(propA);
        },                 

        getPropA: function () {               

            return deferred.promise;
        }
    };
});

ServiceB:

app.service('serviceB', function (serviceA, $q) {

    var 
      propB,
      deferred = $q.defer()
    ;

    return {

        initialize: function(){         

            serviceA.getPropA().then(function(data) {
              propB = data;
              deferred.resolve();
            }); 
        },                 

        processPropB: function () {               
            deferred.promise.then(function() {
                 alert("ServiceB: " + propB);
            })

        }
    };
});

This can be a really confusing issue to people starting out with JS and or Angular, but as we have this awesome weapon against such cases - "THE $q!" - once we learn to wield it properly our asynchronous nightmares once again become fluffy clouds and butterflies.

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

9 Comments

your solution does seem to work but like I said in my question - I am new to javascript so I dont quite get it yet - I am trying to wrap my head around the concept of promises and the way control flow works with them. I will mark this as an answer when I understand it completely. Thank you for taking the time to answer this.
Promises are a way to say pause and dont go any further because you are dependent on a previous function return, when that function is returned then continue.
Also dont forget to read about the asynchronous behavior of the language and the event queue. javascript.info/tutorial/events-and-timing-depth
thanks. I think I now understand promises reasonably. But could you tell me the differences/advantages of using promises as opposed to pub/sub of events(which seem easier and cleaner) on the $scope object to communicate between controllers.
For communication between controllers you basically have 2 options - broadcasting events (pub/sub) and services. At the current project I am working on (webshop) we are using both. They both have their advantages and play a unique roll in your application. Its really up to you to find that role. As fas as promises go they play a vital role on (mostly) the service side of the two i mentioned above. As we typically use services when we need to do, say server requests of data, so we can never be 100 percent sure when then return something, and promises help us win that battle.
|
4

I dont know if this solves the problem, but I think you are instantiating the service, as if it is a factory.

Factories return and object, like you are doing...

But services should utilize "this" type syntax:

App.service('serviceA', function () {
   var somePrivateProperty;
   this.propA = [{},{}];      // public property
   this.initalize = function(objectX) { 
       this.propA = objectX
    }
   this.getPropA = function() { return this.propA }
   // although, your controller which Injects this service, 
   // could access propA directly without the getter

   var somePrivateMethod = function() {}


  });

Example of services vs factories: http://jsfiddle.net/thomporter/zjFp4/1/

2 Comments

thanks - I will try this out and update the results here. But I think I have identified what the problem is and I will post it here soon.
using the "this" type syntax did not solve the problem. This was solved using promises or events as described in the answer. Thanks for taking the time out to help me @timh. :)
1

So I know this is a year old but here is an easier implementation.

Angular Service implementation for singleton pattern:

app.service('serviceA', function () {

var privateProperty;

this.publicObject = {};

this.publicObject.sharedProperty = "Hardcoded value";

this.initialize = function(value){
  this.publicObject.sharedProperty = value; 
}

});

First by using the this.Service syntax you assign the variables/functions directly on the prototype and the service behaves as a 'service'. Plus it feels more angular!

Second by moving the property onto an object it maintains the scope of the object so that it can be altered by multiple controllers since i believe the Angular controllers store objects by reference and specific variable instances by value.

So if your controllerA contained:

$scope.variableByValue = serviceA.publicObject.sharedProperty;

$scope.variableByValue = "New value from A"

than in controllerB:

alert("serviceA.publicObject.sharedProperty") //this would NOT be "New value from A"

Plunker of the restructured service:

http://plnkr.co/edit/3Ux4y37OtXjHsFLv6MSY?p=preview

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.