Keywords: AngularJS | Service | Factory | Dependency Injection | Singleton Pattern
Abstract: This article provides an in-depth exploration of the fundamental differences between service and factory methods for creating services in AngularJS. Through detailed code examples, it analyzes their implementation mechanisms and usage scenarios, revealing that service instantiates constructor functions with the new keyword while factory directly invokes functions to return objects. The article presents multiple practical application patterns and discusses the advantages and disadvantages of both approaches in terms of flexibility, API design, dependency injection, and testing, concluding with clear usage recommendations based on community practices.
Introduction and Background
When learning and applying the AngularJS framework, developers frequently encounter both angular.service() and angular.factory() methods for creating services. While they are functionally very similar—both creating singleton objects that can be injected throughout the application—their underlying implementation mechanisms and usage patterns differ fundamentally. Understanding these differences is crucial for writing maintainable and testable AngularJS code.
Core Concept Analysis
At their core, the primary distinction between service and factory lies in how objects are created:
Service Creation Mechanism: When registering a service using angular.service(), AngularJS treats the provided function as a constructor and instantiates it using the new keyword. This means the injected service instance is actually a new instance of the constructor function.
// Service definition example
angular.service('myService', function() {
this.publicMethod = function() {
return "This is a service public method";
};
});
// The actual injection process is equivalent to:
var injectedService = new MyServiceFunction();
Factory Creation Mechanism: In contrast, angular.factory() directly invokes the provided factory function and uses its return value as the injected object. This approach offers greater flexibility since the factory function can return any type of value.
// Factory definition example
angular.factory('myFactory', function() {
var privateVariable = "private data";
function privateMethod() {
return "This is a private method";
}
// Return public API
return {
publicApi: function() {
return privateMethod() + " - " + privateVariable;
}
};
});
// The actual injection process is equivalent to:
var injectedFactory = myFactoryFunction();
Practical Application Patterns
Typical Service Application Scenarios
Service is most suitable for creating service objects with well-defined public APIs. Since service uses the constructor pattern, it conveniently supports object-oriented service structures.
function UserService() {
this.users = [];
this.addUser = function(user) {
this.users.push(user);
return this.users.length;
};
this.getUsers = function() {
return this.users.slice();
};
this.findUser = function(id) {
return this.users.find(function(user) {
return user.id === id;
});
};
}
angular.service('userService', UserService);
// Usage in controller
app.controller('UserController', function($scope, userService) {
$scope.addUser = function(userData) {
var count = userService.addUser(userData);
$scope.userCount = count;
};
$scope.getAllUsers = function() {
$scope.users = userService.getUsers();
};
});
Diverse Factory Applications
Factory's flexibility is evident in its ability to return various types of values, including objects, functions, primitive values, and even constructor functions.
Pattern 1: Returning Configuration Objects
angular.factory('appConfig', function() {
return {
apiUrl: 'https://api.example.com',
version: '1.0.0',
features: {
analytics: true,
caching: false,
logging: true
}
};
});
Pattern 2: Returning Function References
angular.factory('calculator', function() {
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
function calculate(operation, a, b) {
switch(operation) {
case 'add': return add(a, b);
case 'multiply': return multiply(a, b);
default: throw new Error('Unknown operation');
}
}
return calculate;
});
// Usage pattern
app.controller('MathController', function($scope, calculator) {
$scope.result = calculator('add', 5, 3); // Returns 8
});
Pattern 3: Returning Constructor Functions
angular.factory('personFactory', function() {
return function Person(name, age) {
this.name = name;
this.age = age;
this.getInfo = function() {
return this.name + " - " + this.age + " years old";
};
this.isAdult = function() {
return this.age >= 18;
};
};
});
// Usage pattern
app.controller('PersonController', function($scope, personFactory) {
var person = new personFactory("John", 25);
$scope.personInfo = person.getInfo();
$scope.isAdult = person.isAdult();
});
Technical Details and Best Practices
Importance of Singleton Pattern
Both service and factory in AngularJS are singletons. This means that throughout the application lifecycle, injecting the same service anywhere will yield the same instance reference. This design ensures state consistency and memory efficiency.
// Verifying singleton特性
angular.factory('singletonDemo', function() {
var instanceCount = 0;
return {
getInstanceId: function() {
if (instanceCount === 0) {
instanceCount++;
}
return instanceCount;
}
};
});
// Injecting in multiple controllers will always yield instanceId of 1
Dependency Injection and Testing Considerations
Directly using the new keyword in controllers to create objects introduces hard-to-track dependencies that complicate unit testing. A better approach is to let services manage object creation and lifecycle.
// Not recommended pattern (direct new in controller)
app.controller('BadController', function($scope, personFactory) {
var person = new personFactory(); // Difficult to mock and test
// ...
});
// Recommended pattern (managed through service)
angular.factory('personManager', function(personFactory) {
var persons = [];
return {
createPerson: function(name, age) {
var person = new personFactory(name, age);
persons.push(person);
return person;
},
getAllPersons: function() {
return persons.slice();
}
};
});
Selection Recommendations and Community Practices
Based on technical analysis and community consensus, it is recommended to prefer factory over service for the following reasons:
1. Greater Flexibility: Factory can return any type of value, while service can only return object instances. This flexibility allows factory to adapt to more diverse usage scenarios.
2. Cleaner API Design: When returning constructor functions, factory provides a more intuitive usage pattern:
// Factory approach (recommended)
var obj = new myInjectedFactory();
// Service approach (not recommended)
var obj = new myInjectedService.myFunction();
3. Community Standard: According to John Papa's AngularJS Style Guide and widespread community practice, factory is considered the preferred method. Most AngularJS codebases and open-source projects tend to use factory.
4. Progressive Complexity: For simple services, factory syntax is more straightforward. As service complexity increases, factory can naturally scale without changing the fundamental pattern.
Conclusion
While angular.service() and angular.factory() are functionally very similar, understanding their core differences is essential for writing high-quality AngularJS code. Service instantiates constructor functions with new, making it suitable for creating services with clear object-oriented structures. Factory directly invokes functions and returns results, offering greater flexibility and broader application scenarios.
In practical development, it is recommended to consistently use the factory pattern unless there are specific object-oriented design requirements. This consistency not only aligns with community best practices but also improves code readability and maintainability. Regardless of the chosen approach, remember that services should be singletons, and avoid directly creating object instances in controllers to maintain testability and loose coupling.
By deeply understanding these concepts, developers can better leverage AngularJS's service system to build more robust and scalable web applications.