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:
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 :)
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.
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/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With