URL Routing for a Decoupled App, with Angular 2 and Django

Lately, I have worked on a few projects where a single-page Angular app is contained within a site built on a server-side framework like Django. One of the challenges is to get their URLs to play nicely together.

Say you have a project with an Angular 2 front end and an API back end using the Django Rest Framework. Further, imagine that your Angular 2 page is also served from within the Django app. Your URL structure might look like this:

/ - renders an index.html file that contains the Angular 2 app
/api/books- JSON books list rendered on the server side by Django
/api/books/1 - JSON book detail data, also rendered on the server side

For the purpose of this tutorial, let's assume that the two /api URLs are REST API endpoints which are rendered on the server side by Django. We can imagine that the Angular 2 app might call these endpoints to retrieve data, though the details of the API call are beyond the scope of this article.

In your urls.py module in your Django project, this would look something like the following:

urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^api/books', views.book_list, name='book_list'),
    url(r'^api/books/<P<id>\d+)/$', views.book_detail, name='book_detail'),
]

NOTE: If you are using the Django Rest Framework to generate your API, your "/api/…" URLs would be built using one of the built-in Router classes. This logic is omitted here for simplicity.

However, that's not the end of the story regarding URLs. Angular 2 uses HTML5 pushstate within its router, to provide clean URLs without the hash style commonly seen in Angular 1. This means that you might have a URL within Angular like "/books/42".

Within your single-page app, Angular will handle updating the address bar to the correct URL. This means that the single-page app will work as long as the user keeps her browser open. But, what happens when she bookmarks a URL within the single-page app and comes back to it later? Or she follows a link given to her by someone else?

For incoming traffic, the application will handle URLs like this:

  1. / - Django routes this to the home page
  2. /api/books - Django shows the list of books from the API
  3. /books/42 - This route is known to Angular but not to Django. Django will show a 404!
  4. /bogus-page - A real 404. Django renders the 404 page, as expected.

Obviously, the third route is not being handled the way we want. Let's fix this. We need to add a catch-all URL pattern to Django's urls.py:

urlpatterns = [(your other routes here) …
 
    # catch-all pattern for compatibility with the Angular routes. This must be last in the list.
    url(r'^(?P<path>.*)/$', views.index),
]

Also, in your views.py file, find the view that renders the 'index' page, and make sure it is able to accept the 'path' keyword argument:

Views.py:

def index(request, path=''):
    """
    Renders the Angular2 SPA
    """
    return render(request, 'index.html')

Note: you don't actually need to do anything with the path argument. It just needs to exist so that Django won't throw an error when it's present.

Now, incoming traffic will be routed like this:

  1. / - Django routes this to the home page
  2. /api/books - Django shows the list of books from the API
  3. /books/42 - Matches the new Django catch-all route, and is sent to Angular. The Angular 2 router shows the detail for book #42 within the single-page app.
  4. /bogus-page - A real 404. Django still passes this on to Angular, and Angular must show a 404 page.

That's more like it.

Limitations

If any routes within the Angular app match the ones provided by Django, the Django routes will take precedence and make that page within the Angular app unreachable.

Any unknown or incorrect URLs entered into the address bar will not show a Django 404 page. They will be sent into the Angular2 app, even if Angular doesn't have a matching route. You would need to provide a 404 page within the Angular app if you want to show a "page not found" message to the user.

Other Frameworks

The concepts shown here work similarly with other server-side frameworks and CMSes like Drupal, Symfony, etc. Most of these systems should allow you to create a catch-all route in their URL configuration. In Drupal, for example, I have accomplished a similar setup using hook_menu().

Happy coding!

I have just got started with this combination. I've been thinking on this issue for a while and found your post. Good strategy Keith. I'll give it a shot.
I have following concerns, hope you would be able to advice,
1. What's the best authentication protocol for this combination? I'm currently looking at JWS and OAuth 2.0, not sure which one suite best.
2. With Angular 2, will the template syntax conflicted with Django?

I haven't looked into authentication options. The API that I used to develop this does not require authentication.

I've worked with several different combinations of front-end/back-end framework (Angular 2/Django, Angular 1/Drupal 7, Angular 1/ASP.NET) and I've never had a problem with templating language conflicts. Generally speaking, the back-end framework only processes the outermost HTML needed to bootstrap the front-end app. After that, the front-end templates are loaded as static HTML and processed in JavaScript. Django doesn't even care about the Angular template tags within the HTML files.

Thanks! It'd be great to have a tutorial with a bit more detail. I've been trying for ages to import a module in a angular 2 app served using Django, but I can't get it to work.

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.

About the Author

Keith Dechant, Software Architect

Keith has been working on the web almost since it was new, writing his first HTML in 1996 and his first PHP code in 1999. Along the way, he has written everything from e-commerce websites to mailing list software to large web applications for industrial and non-profit organizations. Recently, he has done a lot of work with ASP.NET, Django, Drupal 7 and 8, and AngularJS.

Keith is a native of Illinois who moved to Portland in 2009. He has been to 49 US states, five continents, three former Soviet republics, and two countries that no longer exist. He likes hiking, mountain climbing, classic video games, and the Oxford comma.