dragon_cat dragon_cat - 7 months ago 2405
Javascript Question

How to integrate an Angular 2 app into a Django app

I've been following this Tour of Heroes tutorial. I have a Django app whose structure can be simplified as follows

apps/my_app/migrations/
apps/my_app/__init__.py
apps/my_app/urls.py
apps/my_app/views.py

frontend_stuff/js/ javascripts here
frontend_stuff/css/ css here


the hero app has the following structure:

heroes/app/ contains all the .ts and .html files
heroes/node_modules/ angular2 and other libraries
heroes/styles.css css file
heroes/index.html
heroes/package.json and other config files


currently i've built an API using Django View that handles Http Request via URL and returns JSON. But where/how should I place this Angular app into the current Django app, and how can I load the Angular app into the browser upon the initial GET request to Django backend?

EDIT: more specific questions on loading HTML and JS:

Since Angular 2 uses
Component
and the templates are rendered by Angular rather than Django, how to let Angular "access" those html? @Luis mentioned that those files should be placed inside the static folder, but
{% static %}
is Django syntax which wouldn't make sense to Angular.

Similarly, I suppose
app/main
(shown below) should also be placed in static folder. But how to refer to the location inside Angular? Thanks!

System.import('app/main')
.then(null, console.error.bind(console));


UPDATE:
i followed this answer and used
/static/js/my_app/main
and it's "mostly" loading fine now. so far all my js files seem to be loaded; but the console log shows that some angular files give 404 error before loading successfully. error as shown below:

system.src.js:1085 GET http://localhost:8000/static/js/node_modules/angular2/src/platform/browser.js 404 (NOT FOUND)fetchTextFromURL @ system.src.js:1085(anonymous function) @ system.src.js:1646ZoneAwarePromise @ angular2-polyfills.js:589(anonymous function) @ system.src.js:1645(anonymous function) @ system.src.js:2667(anonymous function) @ system.src.js:3239(anonymous function) @ system.src.js:3506(anonymous function) @ system.src.js:3888(anonymous function) @ system.src.js:4347(anonymous function) @ system.src.js:4599(anonymous function) @ system.src.js:337ZoneDelegate.invoke @ angular2-polyfills.js:332Zone.run @ angular2-polyfills.js:227(anonymous function) @ angular2-polyfills.js:576ZoneDelegate.invokeTask @ angular2-polyfills.js:365Zone.runTask @ angular2-polyfills.js:263drainMicroTaskQueue @ angular2-polyfills.js:482ZoneTask.invoke @ angular2-polyfills.js:434
system.src.js:1085 XHR finished loading: GET "http://localhost:8000/static/js/rbac/app/rbac.service.js".fetchTextFromURL @ system.src.js:1085(anonymous function) @ system.src.js:1646ZoneAwarePromise @ angular2-polyfills.js:589(anonymous function) @ system.src.js:1645(anonymous function) @ system.src.js:2667(anonymous function) @ system.src.js:3239(anonymous function) @ system.src.js:3506(anonymous function) @ system.src.js:3888(anonymous function) @ system.src.js:4347(anonymous function) @ system.src.js:4599(anonymous function) @ system.src.js:337ZoneDelegate.invoke @ angular2-polyfills.js:332Zone.run @ angular2-polyfills.js:227(anonymous function) @ angular2-polyfills.js:576ZoneDelegate.invokeTask @ angular2-polyfills.js:365Zone.runTask @ angular2-polyfills.js:263drainMicroTaskQueue @ angular2-polyfills.js:482ZoneTask.invoke @ angular2-polyfills.js:434
system.src.js:1085 XHR finished loading: GET "http://localhost:8000/static/js/node_modules/angular2/src/platform/browser.js".fetchTextFromURL @ system.src.js:1085(anonymous function) @ system.src.js:1646ZoneAwarePromise @ angular2-polyfills.js:589(anonymous function) @ system.src.js:1645(anonymous function) @ system.src.js:2667(anonymous function) @ system.src.js:3239(anonymous function) @ system.src.js:3506(anonymous function) @ system.src.js:3888(anonymous function) @ system.src.js:4347(anonymous function) @ system.src.js:4599(anonymous function) @ system.src.js:337ZoneDelegate.invoke @ angular2-polyfills.js:332Zone.run @ angular2-polyfills.js:227(anonymous function) @ angular2-polyfills.js:576ZoneDelegate.invokeTask @ angular2-polyfills.js:365Zone.runTask @ angular2-polyfills.js:263drainMicroTaskQueue @ angular2-polyfills.js:482ZoneTask.invoke @ angular2-polyfills.js:434
angular2-polyfills.js:332 Error: Error: XHR error (404 NOT FOUND) loading http://localhost:8000/static/js/node_modules/angular2/src/platform/browser.js(…)ZoneDelegate.invoke @ angular2-polyfills.js:332Zone.run @ angular2-polyfills.js:227(anonymous function) @ angular2-polyfills.js:576ZoneDelegate.invokeTask @ angular2-polyfills.js:365Zone.runTask @ angular2-polyfills.js:263drainMicroTaskQueue @ angular2-polyfills.js:482ZoneTask.invoke @ angular2-polyfills.js:434
system.src.js:1085 GET http://localhost:8000/static/js/node_modules/angular2/src/http.js 404 (NOT FOUND)


Update: the above error was due to incorrect path in System Confing. Now everything loads with the following config:

<script>
System.config({
defaultJSExtensions: true,
map:{
angular2: '/static/js/node_modules/angular2',
rxjs: '/static/js/node_modules/rxjs'
},
packages: {
app: {
format: 'register',
defaultExtension: 'js'
}
}
});
System.import('/static/apps/my_app/app/main')
.then(null, console.error.bind(console));
</script>

Answer

You should take care of few things:

  1. Django is not Rails. It does not come with a built-in way to parse TypeScript. You should compile all your TypeScript stuff first.
  2. Loading Angular app is just regular HTML. You can use your templating as always. I'm not pretty sure how to do it in Angular 2, but since it is just plain HTML/JS, it will make no difference: by including the appropriate files, you will reference Angular appropriately. In many of my projects, I prefer (for reasons out of the question's scope) to call angular.bootstrap explicitly. Don't know if Angular 2 has the same call or a different one, but the process does not change.
  3. Angular 2 templates will not be processed by django but instead treated like static files. Ensure you put them in your app's static/ directory and reference them appropriately (e.g. in your main django template, if you declare your angular app and stateProvider is still valid, you could declare a template for the state lke this: '{% static 'myapp/angular-templates/mytemplate.html' %}', provided that folder exists).
  4. In the case that you have a separate .js file (yes, COMPILE your .ts files since Django will not) for your main angular module, ensure somehow you can provide the STATIC_URL content from django to the code which configures the modules, for templates to be found (and use window.STATIC_URL directly instead of the static django tag). In Angularjs 1, I do this to initialize everything:

     angular.element.ready(function() {
         window.STATIC_URL = '{{ STATIC_URL }}';
         // more constants I'd need
         angular.bootstrap(['myAngularModule']);
     });
    
  5. Edit (please pay attention to this point which causes headaches) Remember that you must reference {{ something }} or {% something %} in the django served template. Then you assign to a JS variable as I did in point 4, so the values are available. In my case, I don't use static tag, but STATIC_URL variable. {% static %}, as you said, will not work inside angular templates, so instead of using it, just concatenate the required resource to the STATIC_URL (this is correlative with code in point 4) variable. This is an example inside a .js file in AngularJS 1. By tweaking it appropriately, you should do something similar in your AngularJS 2 files:

     mymodule.config(['$stateProvider', function($stateProvider) {
         $stateProvider.state({
             name: 'main',
             templateUrl: window.STATIC_URL + 'path/to/template.html'
             // perhaps better inject $window if you want to run test suites?
         })
     }])
    

So remember:

  1. Remember that AngularJS, and this will hold true for any framework - not just AngularJS versions-, is decoupled of the backend fwk.
  2. Remember that you need to reference, somehow, where are the static files you need to reference in your... static files xD. The trick of passing the variable will do the work in any framework: you simply tell javascript the value of a django variable in the main template (before bootstrapping your framework), and then your js framework will have access to the value in javascript.
  3. You will never access django tags from your fwk.
  4. Django does not convert anything to JS. You need your .js files ready unless you find a 3rd party assets pipeline library.
Comments