When it comes to services in Angular, they are instantiated using the new
keyword but behave as singletons because the instance is only created once. Subsequent calls to inject dependencies will simply return the existing instance, allowing controllers to access the same data within that service. Despite the use of new
, it may seem like a new instance is created each time, but in reality, it's only called once.
To illustrate this concept, I've put together a simplified example that mimics Angular syntax. This example aims to provide a better understanding of what happens behind the scenes when dealing with services and controllers.
// var module = angular.module('myApp'); // Commented this out as I'm mocking what Angular does
var module = {
controller: function(name, arg){
var fn = arg.splice(arg.length - 1, 1)[0]; // Split the array and retrieve the controller's constructor
var deps = [];
for(var i = 0; i < arg.length; i ++){
deps.push(this.service(arg[i])); // Retrieve or construct services
}
return fn.apply({}, deps); // Apply dependencies with a new object assigned as 'this'
},
controllers: {},
service: function (name, fn){ // Simplified version of what module.service likely does to instantiate a singleton
if(!fn) return this.services[name]; // Retrieve the service if it already exists
if(this.services.hasOwnProperty(name)) {
return this.services[name]; // Return the existing instance if available
} else {
this.services[name] = new fn(); // Create a new instance if one doesn't exist
return this.services[name];
}
},
services: {}
};
var instances = 0; // Global counter to track how many times the constructor is called despite using 'new'
function ServiceConstructor(){
instances++;
this.instanceId = instances; // Assign a unique instance ID based on the global counter
}
// Manually create a couple of services
var a = module.service('myService', ServiceConstructor);
var b = module.service('myService', ServiceConstructor);
// Create mock controllers with injected services
var ctl = module.controller('testCtl', ['myService', function(myService){
this.myService = myService;
console.log(myService.instanceId);
return this;
}]);
var ctl2 = module.controller('testCtl2', ['myService', function(myService){
this.myService = myService;
console.log(myService.instanceId);
return this;
}]);
console.log(b);
console.log("Are a & b the same?", a === b); // True
console.log("InstanceIds:", a.instanceId, b.instanceId); // 1 , 1
b.testProp = "Yep definitely the same";
console.log(a.testProp); // Yep definitely the same
console.log("Are all 4 service references the same?", (a === b) === (ctl.myService === ctl2.myService));
var c = new ServiceConstructor(); // Call the service's constructor manually for testing purposes
console.log("Are b & c the same?", b === c) // false - c was manually instantiated
console.log ("b & c instanceid's: ", b.instanceId, c.instanceId); // 1, 2
console.log("Total instances:", instances); // 2 total instances created