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 :)

Shantanu
  • 125
  • 1
  • 2
  • 11
  • 1
    This is a really long question. Maybe try to divide it into different questions and have jsFiddle/plnkrs to that demonstrate your issues. – NicolasMoise Mar 17 '14 at 17:15
  • I suspect you might have a typo. JavaScript is case sensitive, so serviceA is different from ServiceA. – OdeToCode Mar 17 '14 at 22:30
  • Is this a direct copy/paste? `function ($scope, ServiceB, 'ServiceA',) {`. Shouldn't it be: `function ($scope, ServiceB, ServiceA) {` – TheRocketSurgeon Mar 22 '14 at 05: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. – Shantanu Mar 22 '14 at 05:52
  • Not sure if it will help, but I did a codepen for a similar question: http://codepen.io/yakovkhalinsky/pen/LeirK – TheRocketSurgeon Mar 22 '14 at 05:54
  • @TheRocketSurgeon, that code is helpful. In that code too the controllers get invoked jsut once during page load. After that the updating of the allow files property takes place through angular runtime managed 2-way data binding. My guess is if u tried to set the label from within a controller without using events - then u would face the same problem as me. Please let me know ur thoughts. – Shantanu Mar 22 '14 at 06:07
  • @user1526858 usually I would expect that to be ok – TheRocketSurgeon Mar 22 '14 at 07:18
  • @user1526858 I've updated the example to set a property in the service from one of the controllers. As you can see, the property in the service is updated correctly. Perhaps it's another issue. http://codepen.io/yakovkhalinsky/pen/LeirK – TheRocketSurgeon Mar 22 '14 at 07:26
  • Perhaps it's the hierarchy of the actual controllers in your markup. i.e. it's an order of execution problem. – TheRocketSurgeon Mar 22 '14 at 07:28
  • @TheRocketSurgeon - the issue is with the flow of control rather than the hierarchy of the controllers. I have modified your codepen to show what my aim is and the problem I am facing. Check it out: [link]http://codepen.io/anon/pen/jBdwH – Shantanu Mar 22 '14 at 09:54

3 Answers3

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/

timh
  • 1,572
  • 2
  • 15
  • 25
  • 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. – Shantanu Mar 22 '14 at 04:46
  • 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. :) – Shantanu Mar 23 '14 at 09:05
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.

Sten Muchow
  • 6,623
  • 4
  • 36
  • 46
  • 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. – Shantanu Mar 22 '14 at 09:04
  • 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. – Sten Muchow Mar 22 '14 at 09:34
  • Also dont forget to read about the asynchronous behavior of the language and the event queue. http://javascript.info/tutorial/events-and-timing-depth – Sten Muchow Mar 22 '14 at 09:42
  • 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. – Shantanu Mar 22 '14 at 19:02
  • 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. – Sten Muchow Mar 22 '14 at 20:34
  • Yes - both promises and events with pub/sub work as a solution to the problem. Though I think I'll use events as it seems like a closer fit to my requirement. Thanks for all the time spent helping me @Sten Muchow. :) – Shantanu Mar 23 '14 at 09:03
  • Also I'll change the title of the question as the services do act like singletons but initializing them properly was the issue and updating the title will help someone new to angular facing solve a similar problem. – Shantanu Mar 23 '14 at 09:11
  • Sounds good, glad to share what I have learned about this incredible way of building web apps... the angular way. You will find your need for promises soon enough, they are deeply ingrained in the angular way of doing things - $http.success() is a promise for example. – Sten Muchow Mar 23 '14 at 09:13
  • you can also upvote this answer to gimme full credit (and a few extra points). :-) – Sten Muchow Mar 23 '14 at 09:14
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