Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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:

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

like image 752
Shantanu Avatar asked Mar 17 '14 17:03

Shantanu


2 Answers

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.

like image 54
Sten Muchow Avatar answered Sep 19 '22 01:09

Sten Muchow


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/

like image 33
timh Avatar answered Sep 18 '22 01:09

timh