This is the fifth installment in a multi-part series geared toward getting started with Django Rest Framework (DRF) and AngularJS. The goal of this series is to create an extensive, RESTful web application that uses DRF as a backend DB/API service and AngularJS as a frontend service connecting to the API.
Read Previous Posts:
- Part 1 - Initial Project Setup
- Part 2 - Django Models and the ORM
- Part 3 - Serializers, views, and API routes
- Part 4 - Client Project Setup
Write:
This post focuses on connecting our AngularJS application with the existing Django Retail application backend.
- A Recap and Introduction to AngularJS Services
- Angular Resource
- Retail Services
- Making Use of the Retail Services
- Displaying API Results in the Template
- Running it All Together
This post uses AngularJS 1.5.7
and Angular Resource 1.4.8
.
A Recap and Introduction to AngularJS Services
In the previous post, we got our Angular application up and running in a very minimal way. As minimal as it was, the framework put into place allows us to build on top of it to begin integrating data from the backend Retail application previously created. This post covers the basics on AngularJS services - injectable Angular modules that can be shared across your application to perform common functions.
By the end of this post we will define multiple services that contact the Retail API and display the results of the query onto our main page.
Angular Resource (ngResource)
There are many ways to contact an API through Angular. We could use $http
, but it's a bit raw and is used for general purpose requests. For this application we use Angular Resource
, or ngResource
. ngResource
is an AngularJS package that wraps $http
functionality for RESTful APIs.
To begin, add ngResource
as a dependency for the AngularJS application. Change the bower.json
dependencies to contain angular-resource
and add a resolutions section (this eliminates potential Angular version conflicts).
...
"dependencies": {
"angular": "^1.5.7",
"angular-ui-router": "^0.3.1",
"angular-bootstrap": "^1.3.3",
"angular-resource": "~1.4.8"
},
"resolutions": {
"angular": "^1.5.7"
}
...
Then run the bower install command in the client directory.
drf-sample$ cd server
drf-sample/client$ bower install --config.interactive=false --allow-root
angular-resource
is installed into the bower_components
directory. Great! ngResource
is installed, but the application is unable to use it until we import it for our application.
Add angular-resource.min.js
as a dependency in the head
section of index.html
index.html
...
<script src="bower_components/angular-resource/angular-resource.min.js"></script>
...
and import ngResource
as a dependency in public/app.js
.
public/app.js
'use strict';
var retail = angular.module("retail", []);
angular
.module('SampleApplication', [
'appRoutes',
'retail',
'ngResource'
]);
Start the angular application, navigate a browser to localhost:8081
, and check the development console.
drf-sample/client$ node server.js
Use port 8081 to connect to this server
If there are no errors then you're ready to move to the next step!
Retail Services
According to the AngularJS documentation on services,
Angular services are substitutable objects that are wired together using dependency injection (DI). You can use services to organize and share code across your app.
Angular services are:
Lazily instantiated – Angular only instantiates a service when an application component depends on it.
Singletons – Each component dependent on a service gets a reference to the single instance generated by the service factory.
Long story short, services are individual modules of code that can be imported by other services, controllers, etc. to perform work.
We're going to create three AngularJS services with the goal to provide a way to query the Retail API endpoints. Each service focuses on a specific API endpoint (chains
, stores
, and employees
).
Start by adding three service files to the services directory
components/
└── retail/
├── controllers/
├── services/
│ ├── chain.service.js
│ ├── store.service.js
│ └── employee.service.js
└── templates/
and add the following to each specified file.
chain.service.js
retail
.factory('Chain', function($resource) {
return $resource(
'http://localhost:8000/chains/:id/',
{},
{
'query': {
method: 'GET',
isArray: true,
headers: {
'Content-Type':'application/json'
}
}
},
{
stripTrailingSlashes: false
}
);
});
store.service.js
retail
.factory('Store', function($resource) {
return $resource(
'http://localhost:8000/stores/:id/',
{},
{
'query': {
method: 'GET',
isArray: true,
headers: {
'Content-Type':'application/json'
}
}
},
{
stripTrailingSlashes: false
}
);
});
employee.service.js
retail
.factory('Employee', function($resource) {
return $resource(
'http://localhost:8000/employees/:id/',
{},
{
'query': {
method: 'GET',
isArray: true,
headers: {
'Content-Type':'application/json'
}
}
},
{
stripTrailingSlashes: false
}
);
});
Then import each service file into the head
section of index.html
so they can be used by the application.
...
<script src="public/components/retail/services/chain.service.js"></script>
<script src="public/components/retail/services/store.service.js"></script>
<script src="public/components/retail/services/employee.service.js"></script>
...
Our three services have been defined and imported! What do they do? First, each service is defined as a factory
. An AngularJS factory
typically defines one or more objects and returns those objects for direct use by the calling party. We are instantiating a $resource
object in each of our factory
services returning it directly. Any module that uses the services have access to a pre-configured $resource
object.
Each $resource
object is defined with a set of configurations explaining how to access the API endpoints. For each, we are expecting the Django endpoint to live on localhost:8000
and to be identified by either /chains/
, /stores/
or /employees/
. The extra :id
parameter denotes that we can specify a resource ID and the ID will be plugged into the URI.
Next, we specify a query
action. This action allows GET
requests to be performed on the specified endpoint, expects an array in return, and ensures that the result is JSON.
Lastly, our Django application expects trailing slashes at the end of API requests so we ensure that those trailing slashes are not stripped.
Our services are ready to be used by another module. The first module to make use of these services is the retail controller.
Making Use of the Retail Services
The retail controller in the previous post was a very basic skeleton of what a controller can be. It is good to keep controllers light and move any heavy lifting to services, but the original controller didn't serve much purpose. Now that we have services defined we can inject them into the controller to dynamically populate variables based on the results of various API calls.
Open the retail controller and add the following code to the file.
retail.control.js
retail
.controller('RetailController', function($scope, Chain, Store, Employee) {
Chain.query().$promise.then(function(data) {
$scope.chains = data;
});
Store.query().$promise.then(function(data) {
$scope.stores = data;
});
Employee.query().$promise.then(function(data) {
$scope.employees = data;
});
});
Chain
, Store
and Employee
services are injected into the controller. Each is used by calling the query
action when the controller is instantiated. Using the promises the actions return, we populate $scope
variables of like-names with the results.
In essence, the controller makes three GET
requests to different endpoints on the local retail API and populates variables once the results of each call has returned sucessfully.
This is a very simple example of how services can be used, but it's a great start to get API results into our client application!
Displaying API Results in the Template
The last step to display API results on a page is to modify the template to make use of the new controller variables. Add the following code to the retail template file.
retail.template
<div ng-controller="RetailController">
Chains
<div ng-repeat="chain in chains">
<div style="margin-left: 20px;">
name: {{ chain.name }} <br/>
description: {{ chain.description }} <br/>
slogan: {{ chain.slogan }} <br/>
founded_date: {{ chain.founded_date }} <br/>
website: {{ chain.website }} <br/>
<hr/>
</div>
</div>
<br/>
Stores
<div ng-repeat="store in stores">
<div style="margin-left: 20px;">
chain: {{ store.chain }} <br/>
number: {{ store.number }} <br/>
address: {{ store.address }} <br/>
opening_date: {{ store.opening_date }} <br/>
business_hours_start: {{ store.business_hours_start }} <br/>
business_hours_end: {{ store.business_hours_end }} <br/>
<hr/>
</div>
</div>
<br/>
Employees
<div ng-repeat="employee in employees">
<div style="margin-left: 20px;">
store: {{ employee.store }} <br/>
number: {{ employee.number }} <br/>
first_name: {{ employee.first_name }} <br/>
last_name: {{ employee.last_name }} <br/>
hired_date: {{ employee.hired_date }} <br/>
</div>
</div>
<br/>
</div>
This example is a bit more complex than the previous post, so let's go into what's happening here. At a high level, AngularJS ng-repeat
is used to iterate through the lists defined in the controller to display data for each item within the lists.
ng-repeat is a great AngularJS directive used to loop over collections and generate dynamic content based on each item within the collections. Think of these as a Python for
loop in syntax where the current element of the loop is defined as a subset of a collection.
While python uses
for item in itemset:
the AngularJS ng-repeat
syntax would be
ng-repeat="item in itemset"
In this case, we are looping other three lists - chains
, stores
, and employees
. These lists were defined within the retail controller from the previous section whereas each contains a list of results from their respective API call.
When rendered, each item is be displayed as a separate div
element. Within the repeated div
, we use the current item variable (chain
, store
, and employee
) from the ng-repeat
to interact with the data. In this simple example the template displays the noteworthy fields of each object.
Everything is ready to go to start displaying the data!
Running it All Together
Let's get the total application up and running! Keep in mind that the intent of this guide is to run the Django application and the AngularJS application as two separate services. To run everything together we need to run two different commands to start both sides:
Start Django application
tim@tim-XPS-13-9343:~/code/side/drf-sample/server$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
January 02, 2017 - 18:52:56
Django version 1.8, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Start AngularJS spplication
tim@tim-XPS-13-9343:~/code/side/drf-sample/client$ node server.js
Use port 8081 to connect to this server
Great! Both applications are up and running. Open a browser and head to localhost:8081
. Assuming you haven't cleared the Django database from Part 2 you should see some data populated on the page! If you did clear the database then you will have to populate your models through the ORM.
Here's what the rendered page looks like!
Looking Forward
This will be the last part of the Django/Angular application for now! The ultimate goal here was to get two separate applications up and running with Django/DRF as the backend and AngularJS running the frontend and we have accomplished that goal!
There will be various, smaller posts in the future about how to improve Django and AngularJS applications in a number of ways. Look out for those posts coming soon!