Modern web apps in Angular 5 and Django

So you want to check how Angular 5 works, you complete their (nice) tutorial, and you are left with an app that works with a fake in-memory database and doesn’t connect to any real REST API backend.

How the app works (credit [https://angular.io](https://angular.io/tutorial))

Let’s see a way to connect it to a Django Rest Framework (DRF) app. First of all, we move all the angular stuff to a frontend directory:

cd angular-tour-of-heroes
mkdir frontend
mv * frontend/
mv .angular-cli.json frontend/

Then we create a new virtualenv and django project:

virtualenv -p python3 .virtualenvs/heroes
source .virtualenvs/heroes/bin/activate
pip install django
pip install djangorestframework
django-admin.py startproject angular_tour_of_heroes .

In angular_tour_of_heroes/settings.py add the apps ‘heroes’ and ‘rest_framework’ in the INSTALLED_APPS vector, and add the following at the end:

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny'
    ],
    'PAGE_SIZE': 10
}

Start a new django app:

python manage.py startapp heroes

Define a model for the Hero class in heroes/models.py:

class Hero(models.Model):
    name = models.TextField(default='')

Define a serializer in a new file heroes/serializers.py (the id field is generated by Django as the primary key):

from rest_framework import serializers
from models import Hero

class HeroSerializer(serializers.ModelSerializer):
    class Meta:
        model = Hero
        fields = ('id', 'name')

Let’s define a URL where to view the list of heroes, in angular_tour_of_heroes/urls.py:

from django.conf.urls import url
from django.contrib import admin

from heroes import views

urlpatterns = [
    url(r'^heroes/$', views.hero_list.as_view()),
    url(r'^heroes/(?P<pk>[0-9]+)$', views.hero_detail.as_view()),
]

It’s now possible to set up the hero_list and hero_detail views in heroes/views.py as:

from django.shortcuts import render

from rest_framework import mixins, generics

from .models import Hero
from .serializers import HeroSerializer

class hero_list(mixins.ListModelMixin,
                mixins.CreateModelMixin,
                generics.GenericAPIView):

    queryset = Hero.objects.all()
    serializer_class = HeroSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)


class hero_detail(mixins.RetrieveModelMixin,
                  mixins.UpdateModelMixin,
                  mixins.DestroyModelMixin,
                  generics.GenericAPIView):

    queryset = Hero.objects.all()
    serializer_class = HeroSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

To check if it works, apply the db migrations:

python manage.py makemigrations
python manage.py migrate

To see the list of heroes, we can add one

python manage.py shell
from heroes.models import Hero
hero = Hero(name='Bug Blazer')
hero.save()
exit()

And then to see it

python manage.py runserver

and browse to http://127.0.0.1:8000/heroes/

Since the local development servers run on different ports, locally there will be CORS issues, therefore we need to setup a proxy. First of all, create a file angular_tour_of_heroes/frontend/proxy.conf.json:

{
    "/heroes": {
        "target": "http://localhost:8000",
        "secure": false
    }
}

Then tell npm to use it, changing the scripts.start value in angular_tour_of_heroes/frontend/package.json to

[...]
"start": "ng serve --proxy-config proxy.conf.json",
[...]

Remove all the references to the angular-in-memory-web-api and to the in-memory-data service that we used in the angular app from frontend/src/app/app.module.ts, and remove the former with:

npm uninstall angular-in-memory-web-api --save

Make sure that heroesUrl is properly set in the hero service, as in

private heroesUrl = '/heroes/';

Run the Django and Angular servers:

python manage.py runserver  # from the root directory
npm start  # from the frontend directory

To build the Angular app, we first modify frontend/package.json to contain, in scripts.build:

"build": "ng build --prod --base-href /static",

Then we run in the frontend directory:

npm run build

We want to add the following to angular_tour_of_heroes/settings.py to specify where to put the Angular frontend static files (in static/frontend):

STATIC_ROOT = os.path.join(BASE_DIR, 'static')

STATICFILES_DIRS = [
    ('frontend', os.path.join(BASE_DIR, 'frontend/dist')),
]

Then python manage.py collectstatic will collect all the Angular app static files and move them in static/frontend, from where they can be easily served through a web server such as Apache or Nginx. For our purposes, however, it’s easier to add two more url patterns in urls.py:

from django.conf.urls import url
from django.contrib import admin
from django.views.generic import RedirectView
from django.contrib.staticfiles.views import serve


from heroes import views

urlpatterns = [
    url(r'^$', serve,kwargs={'path': 'frontend/index.html'}),
    url(r'^(?!/?static/)(?!/?media/)(?P<path>.*\..*)$',
        RedirectView.as_view(url='/static/frontend/%(path)s', permanent=False)),
    url(r'^heroes/$', views.hero_list.as_view()),
    url(r'^heroes/(?P<pk>[0-9]+)$', views.hero_detail.as_view()),
]

Now running python manage.py runserver will be enough to view our app on http://localhost:8000/.

Written on February 4, 2018