After this lesson, students will be able to:
- Refactor $resource as a service
- Describe the difference between a factory and a service
Before this lesson, students should already be able to:
- Create http requests with
$http
- Describe RESTful resources
- Build a Node RESTFUL API
Each web application you build with Angular is composed of objects that collaborate to get stuff done. These objects need to be instantiated and wired together using dependency injection for the app to work.
When you inject a dependency into a module, e.g.
angular
.module("lightsaberApp", ['ngResource']);
The injector service needs to know how it should insert these objects. It does this by using recipes.
The main recipe that the injector needs to inject something into a module is called the provider recipe. However, on top of that, there are five other types of recipes which the injector uses to "cook" the dependency slightly differently. These recipes are:
- Factory - the value returned by invoking the function reference
- Service - the actual function reference
- Provider - the value returned by invoking the $get method of the function reference
- Constant - similar to a constant in other languages
- Value - a literal value that can change
Each of these create a new object when they are used.
Angular services (anything that can be injected as a dependency) are:
- Lazily instantiated – Angular only instantiates a service when it is needed
- Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
Note: Point out that a singleton is an object that there should only be one of.
Once instantiated, unlike a controller, it is persisted throughout the lifetime of our app.
If your app needs data from an API or database, we probably don't want to keep loading that same data every time we change routes. Fetching that data once and holding it in a service makes a lot of sense.
Also, because services are persistent singletons, they provide a mechanism to share data between controllers.
Factory and Service are the most commonly used recipes. However, if factories and services are so similar, what's the difference? They look quite similar:
angular.service('myService', myServiceFunction);
angular.factory('myFactory', myFactoryFunction);
Service:
...will be new-ed, always creating a new object.
new myServiceFunction();
You should only make a service if you only ever need to and want to make one instance of it. For example:
- A function that we want to wrap up in an object with a name so it's clear and useable to check whether a JWT token is acceptable & should be authorized or not
- A function to add Cross Origin Resource Sharing to our app, so an API can share JSON with any domain
- A function to inject the replace every instance of the word "javascript" with "bagel", because that would make tons of sense
Factory:
...will be invoked, which can return an object.
myFactoryFunction();
It's possible to do the same things with both services and factories.
However, in some cases the factory gives you a little bit more flexibility because while the service must always be an object, the factory can be an object, a function reference, or any value at all.
Building from the lesson where we used the $resource
service, we now want to refactor our code to use a custom service, specifically a factory.
Inside the starter-code/app/front-end/ directory, let's first start by making a new directory and a new js file for our new component:
mkdir js/resources
touch js/resources/character.js
Now, let's add this script to the head of index.html
file before we forget!
<script type="text/javascript" src="./js/resources/character.js"></script>
Inside this file, let's add some boilerplate code for our factory service:
angular
.module('lightsaberApp')
.factory('Character', Character);
Character.$inject = ['$resource'];
function Character($resource) {
}
This looks almost exactly the same as a controller module.
In order to use this factory in our MainController, we want to include it as a dependency.
MainController.$inject = ['$resource', 'Character']
function MainController($resource, Character){
Let's test that this has been injected correctly by just returning something from inside the factory:
function Character($resource) {
return {
test: "Testing"
}
}
Now let's assign it to a view model in our MainController
function MainController($resource, Character){
var self = this;
self.test = Character;
...
And finally bind it on our page so that we can see if something was returned:
<main ng-controller="MainController as main">
{{ main.test.test }}
You should see your string output!
We can remove the test code from index.html
and MainController
now and we can move this code:
// Obtain our resource class
var Character = $resource('http://localhost:3000/characters/:id', {id: '@_id'}, {
'update': { method:'PUT' }
});
Into the factory, like so:
function Character($resource) {
return $resource('http://localhost:3000/characters/:id', {id: '@_id'}, {
'update': { method:'PUT' }
});
}
We can now remove the $resource
dependency from MainController
and it should still work.
Now that our new resource class is a function, we can attach extra functionality to its prototype
!
For example, if we wanted to be able to easily access only the first name of our character, we could write our Character
resource like this:
function Character($resource) {
var UserResource = $resource('http://jsonplaceholder.typicode.com/users/:id', {id: '@id'});
CharacterResource.prototype.firstName = function(){
if (this.name) {
if (this.name.indexOf(" ") === -1) return this.name;
return this.name.slice(0, this.name.indexOf(' '));
}
}
return CharacterResource;
}
Better yet, if you wanted to make firstName
behave like a property instead of having to execute a function, try this on for size:
function Character($resource) {
var CharacterResource = $resource('http://localhost:3000/characters/:id', {id: '@_id'}, {
'update': { method:'PUT' }
});
Object.defineProperty(CharacterResource.prototype, 'firstName', {
get: function(){
if (this.name) {
if (this.name.indexOf(" ") === -1) return this.name;
return this.name.slice(0, this.name.indexOf(" "));
}
}
})
return CharacterResource;
}
Now you can bind firstName
to your view without having to invoke it as a function?!
<h1 ng-show="main.selectedCharacter">{{ main.selectedCharacter.firstName }}</h1>
You now have the ability to create client-side resource models that mimic pretty much the model on the server!
In a real-world app, it's fairly rare that we'd want to retrieve every record in a database table like we have been doing with this code:
this.characters = Character.query();
Imagine if there was 1,000,000 Jedis?! We might need to add some custom code in our resource
You might have noticed that this API also has an Episodes resource as well as a lightsaber resource.
Your task is to replicate what we have done with the character model and add episodes to the app.
Don't copy and paste, you will thank yourself later!
- How do you include NgResource in your App?
- What two syntaxes can you use to save an item with
$resource
- Why does
query()
have no callback?