Getting Started with Django REST Framework (DRF) and AngularJS (Part 5)

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:

Write:


This post focuses on connecting our AngularJS application with the existing Django Retail application backend.

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!

map3

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!

Tim Butler

I'm a Software Maven at TrackMaven.

I specialize in Python backend development through Django and Django Rest Framework. Want to get in touch? Feel free to drop me an email.

Comments