Shantanu
Shantanu

Reputation: 125

Proper way to initialize angular services using the controllers

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:

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:

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:

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

Please let me know your thoughts.

Sorry for the long post :)

Upvotes: 6

Views: 14408

Answers (3)

Stefan Bradstreet
Stefan Bradstreet

Reputation: 36

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

Upvotes: 1

Sten Muchow
Sten Muchow

Reputation: 6701

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.

Upvotes: 4

timh
timh

Reputation: 1590

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/

Upvotes: 4

Related Questions