We are going to dive into Angular Factories and Services.
- Learn how to implement factories, services and values in our apps
- Learn some of the built-in services angular gives us for free
- Learn how to make API calls from our angular apps
- Learn how to transfer data between our factories and our controllers
We have copied all of the code from the last lesson js-angular-routes into this repo to start off.
Oh, all but the app/customersData.js. And we'll see why we don't need this later.
These services are provided by Angular.
- $http - Provides Ajax requests. Like the jQuery $.ajax we've used.
- $location - Represent the Browser URL. Can access and change the Browser's URL.
- $timeout
- $window
- $q - Provides a Promise that can be used to handle asynchronous callbacks.
Will return a custom object that can be used by multiple other components, typically controllers.
Factories use the Revealing Module Javascript Pattern to create the custom object they return.
(function customersFactoryIIFE(){
// Create a customers factory
var customersFactory = function(){
// customers is private, only available in this scope
var factory = {};
factory.customers = [];
factory.customer = {};
var customers = [
{
id: 1,
joined: '2000-12-02',
name:'John',
city:'Chandler',
orderTotal: 9.9956,
orders: [
{
id: 1,
product: 'Shoes',
total: 9.9956
}
]
},
{
id: 2,
joined: '1965-01-25',
name:'Zed',
city:'Las Vegas',
orderTotal: 19.99,
orders: [
{
id: 2,
product: 'Baseball',
total: 9.995
},
{
id: 3,
product: 'Bat',
total: 9.995
}
]
},
{
id: 3,
joined: '1944-06-15',
name:'Tina',
city:'New York',
orderTotal:44.99,
orders: [
{
id: 4,
product: 'Headphones',
total: 44.99
}
]
},
{
id: 4,
joined: '1995-03-28',
name:'Dave',
city:'Seattle',
orderTotal:101.50,
orders: [
{
id: 5,
product: 'Kindle',
total: 101.50
}
]
}
]; // end of customers data
factory.getCustomers = function(){
return angular.copy(customers, factory.customers);
};
factory.getCustomer = function(customerId){
for(var i=0, len=customers.length; i < len; i++){
if(customers[i].id == parseInt(customerId)){
return angular.copy(customers[i], factory.customer);
}
}
return {};
};
return factory;
};
angular.module('customersApp').factory('customersFactory', customersFactory);
})();
-
Create an IIFE that will hide all the variables from Global scope.
-
Create a Self Revealing Function, customersFactory, in the IIFE.
-
In the customersFactory function we will:
- hard code all the customer data.
- We'll remove this when we get customer data from the back end
- Create an empty object literal, "factory".
- Create a method on factory, getCustomers, that can be used to access the customer data.
- Create a method on factory, getCustomers, that given a customer id will return data for that customer.
- return the object literal "factory".
- hard code all the customer data.
-
Register the Angular Factory. So it's available throughout the application.
(function customersControllerIIFE(){
// 1. Inject the customersFactory into this controller
var CustomersController = function(customersFactory){
this.sortBy = "name";
this.reverse = false;
// 2. Set the controllers array of customers equal to the factories
this.customers= customerFactory.customers;
// 3. Create a function that will set the customers Array in the scope
// from the customersFactory
function init(){
// Init the customers from the factory
customersFactory.getCustomers();
}
// 4. Initialize the controller.
init();
this.doSort = function(propName){
this.sortBy = propName;
this.reverse = !this.reverse;
};
};
// Prevent the minifier from breaking dependency injection.
CustomersController.$inject = ['customersFactory'];
// The Controller is part of the module.
angular.module('customersApp').controller('customersController', CustomersController);
})();
- Inject the customersFactory into this controller
- Set the controller's customers equal to the factory's customers within the scope.
- Create a function that will retrieve customers from our "DB".
- Initialize the controller.
(function ordersControllerIIFE(){
var OrdersController = function($routeParams, customersFactory){
var customerId = $routeParams.customerId;
this.customer = customersFactory.customer;
// private function, not available outside of IIFE
function init(){
// Search for the customer by id
customersFactory.getCustomer(customerId);
}
init();
};
// Prevent the minifier from breaking dependency injection.
OrdersController.$inject = ['$routeParams', 'customersFactory'];
// The Controller is part of the module.
angular.module('customersApp').controller('ordersController', OrdersController);
})();
- Inject the customersFactory into this controller
- Set the customer equal to the customerFactory's customer.
- Create a function, init, that will set the customers from the customerId param.
- Initialize the controller.
<!DOCTYPE html>
<html ng-app="customersApp">
<head>
<title>Angular is Fun!</title>
</head>
<body>
<ng-view></ng-view>
<!-- vendor javascript -->
<script src='bower_components/angular/angular.js'></script>
<script src='bower_components/angular-route/angular-route.js'></script>
<!-- app -->
<script src='app/app_done.js'></script>
<!-- factories -->
<script src='app/factories/customersFactory.js'></script>
<!-- controllers -->
<script src='app/controllers/customersController.js'></script>
<script src='app/controllers/customersController.js'></script>
</body>
</html>
<h3>{{ orderCtrl.customer.name}}'s Orders</h3>
<table>
<tr>
<th>Product</th>
<th>Total</th>
</tr>
<tr ng-repeat="order in customer.orders">
<td>{{ orderCtrl.order.product }}</td>
<td>{{ orderCtrl.order.total | currency }}</td>
</tr>
</table>
<br/>
Provide variables that don't belong in a factory, or controller. They are application wide variables that have values.
// Create applicaton wide settings
angular.module("customersApp").value('appSettings', {
title: "Customers Application",
version: "1.0"
});
/*
// Use constant if you need app wide values available inside the app config
angular.module("customersApp").constant('appSettings', {
title: "Customers Application",
version: "1.0"
});
*/
(function customersControllerIIFE(){
// 1. Inject application wide value, appSetting.
var CustomersController = function(customersFactory, appSettings){
...
// 2. Make the application wide settings available in the view.
this.appSettings = appSettings;
...
};
// 3. Prevent the minifier from breaking dependency injection.
CustomersController.$inject = ['customersFactory', 'appSettings'];
...
})();
<h3>{{ customersCtrl.appSettings.title}} </h3>
...
<span>Total customers: {{customersCtrl.customers.length}}</span>
<br/>
<br/>
<footer>Version: {{ customersCtrl.appSettings.version }}</footer>
Let's revist our songs app. Create a factory, pull all of the logic out of your controller and put it into your factory. We want to keep our controllers nice and neat.
We are, finally, going to make remote API HTTP Request for the customer data. We will be using a very simple Rails app, actually created with the Rails API gem.
The repo for this API is Customers API.
But, we will need to setup the Angular Factories we created above to make Ajax calls to this API.
(function customersFactoryIIFE(){
// Create a customers factory
var customersFactory = function($http){
var factory = {};
factory.customers = [];
factory.cutomer = {};
factory.getCustomers = function(){
// allow access to the list of customers
return $http.get('http://localhost:3000/customers').success(function(response){
angulr.copy(response, factory.customers);
});
};
factory.getCustomer = function(customerId){
return $http.get('http://localhost:3000/customers/' + customerId).sucess(function(response)
angular.copy(response, factory.customer);
});
};
return factory;
};
customersFactory.$inject = ['$http'];
angular.module('customersApp').factory('customersFactory', customersFactory);
})();
- We've injected the Angular Ajax Service, $http , in to this Factory.
- The
$http behaves very much like the jQuery $ .ajax
- The
- We have FINALLY removed the hard coded customers data from our app. And now are making a remote API request for this data.
$http.get('http://localhost:3000/customers')
- And we are making a HTTP GET Request for a specific customers data.
$http.get('http://localhost:3000/customers/' + customerId)
- Once this data comes back, we are setting the value of our factory to the response via angular copy. Angular copy is a deep copy. Not entirely sure what that means; don't care. It paves over any issues of timing or scope we might have.
- Doing the weasel work of preventing the javascript minification problems.
customersFactory.$inject = ['$http'];
Check out the $http Angular service. In each of the methods above we return a Promise from the $http service. A Promise will be invoked when the Ajax asynchronous request is returned from the server.
(function customersControllerIIFE(){
var CustomersController = function(customersFactory, appSettings){
...
function init(){
// Init the customers from the factory
//this.customers = customersFactory.getCustomers();
customersFactory.getCustomers()
}
init();
...
};
...
})();
- We have changed the init function to handle the Promise returned by the customersFactory.getCustomers method.
- The anonymous function passed to success will fire and update the ViewModel's, scope, with the customers data.
- The anonymous function passed to error will fire if there is an error communicating with the API.
...
function init(){
// Search for the customer by id
// this.customer = customersFactory.getCustomer(customerId);
customersFactory.getCustomer(customerId)
}
...
- We have changed the init function to handle the Promise returned by the customersFactory.getCustomer(customerID) method.
- The anonymous function passed to success will fire and update the ViewModel's, scope, with the customer data, this.customer.
- The anonymous function passed to error will fire if there is an error communicating with the API.
Time to get serious with our songs app. Create an API (either in Node.js or Rails) and seed some songs. Then, refactor your angular app to read and render those songs.
This is like the $.ajax in JQuery. Ajax HTTP Service