Integrate a Django form with an AngularJS model

When deriving from Django’s forms.Form class in an AngularJS environment, it can be useful to enrich the rendered form input fields with an AngularJS HTML tag, such as ng-model="my_field", where my_field corresponds to the named field from the declared form class.

Sample code

Assume we have a simple Django form class with a single input field. Enrich its functionality by mixing in the djng class NgModelFormMixin

Note

Here the names NgModelForm… do not interrelate with Django’s forms.ModelForm. Instead that name reflects the HTML attribute ng-model as used in <form>-elements under control of AngularJS.

from django import forms
from django.utils import six
from djng.forms import fields, NgDeclarativeFieldsMetaclass, NgModelFormMixin

class ContactForm(six.with_metaclass(NgDeclarativeFieldsMetaclass, NgModelFormMixin, forms.Form)):
    subject = fields.CharField()
    # more fields ...

Note

Since django-angular-1.1, you must use the adopted field classes provided by django-angular, instead of Django’s own fields module.

In the majority of cases, the Form inherits from Django’s forms.Form, so the above example can be rewritten in a simpler way, by using the convenience class NgForm as a replacement:

from djng.forms import NgModelFormMixin, NgForm

class ContactForm(NgModelFormMixin, NgForm):
    # members as above

If the Form shall inherit from Django’s forms.ModelForm, use the convenience class NgModelForm:

from djng.forms import NgModelFormMixin, NgModelForm

class ContactForm(NgModelFormMixin, NgModelForm):
    class Meta:
         model = ContactModel

    # fields as usual

Now, each form field rendered by Django, gets an additional attribute ng-model containing the field’s name. For example, the input field named subject now will be rendered as:

<input id="id_subject" type="text" name="subject" ng-model="subject" />

This means, that to a surrounding Angular controller, the field’s value is immediately added to its $scope. Since we do not want to pollute the AngularJS’s scope with various field names, we pack them into one single JavaScript object, named according to the scope_prefix attribute in our Form class. The above field, then would be rendered as:

<input id="id_subject" type="text" name="subject" ng-model="my_prefix['subject']" />

Full working example

This demonstrates how to submit form data using django-angular’s built-in form controller via Ajax. The Django view handling this unbound contact form class may look like

import json
from django.http import JsonResponse
from django.urls import reverse_lazy
from django.views.generic import FormView
from djng.forms import NgModelFormMixin, NgForm

class ContactForm(NgModelFormMixin, NgForm):
    form_name = 'contact_form'
    scope_prefix = 'contact_data'
    subject = fields.CharField()

class ContactFormView(FormView):
    template = 'contact.html'
    form_class = ContactForm
    success_url = reverse_lazy('success-page')

    def post(self, request, **kwargs):
        assert request.is_ajax()
        request_data = json.loads(request.body)
        form = self.form_class(data=request_data[self.form_class.scope_prefix])
        if form.is_valid():
            return JsonResponse({'success_url': force_text(self.success_url)})
        else:
            response_data = {form.form_name: form.errors}
            return JsonResponse(response_data, status=422)

with a template named contact.html:

<form djng-endpoint="/path/to/contact-form-view" name="contact_form">
    {{ contact_form }}
    <button ng-click="do(update()).then(redirectTo())">Submit</button>
</form>

Note that the <form> tag does not require any method or action attribute. This is because the form submission is not initiated by the form’s submit handler, but rather by the button’s click event handler. Inside this click handler, we first submit the form data using the update() function which itself returns a promise. On success, our click handler invokes the function inside the following .then(...) handler. Since it receives the HTTP response object from the previous submission, we use this inside the redirectTo() function. Therefore, we can pass our success_url from the server, down to our submit button, so that this can trigger a page redirection action.

In case the form was not validated by the server, a response with an error code 422 (Unprocessable Entity) is returned. In such a case, the error handler of our form submission function uses the returned data to fill the normally invisible error message placeholders located nearby each of our form fields.

Note

In real code, do not hard code the URL of the endpoint as shown in this example. Instead use the templatetag {% url ... %}.

Working with nested forms

NgModelFormMixin is able to handle nested forms as well. Just remember to add the attribute prefix='subform_name' with the name of the sub-form, during the instantiation of your main form. Now your associated AngularJS controller adds this additional model to the object $scope.my_prefix, keeping the whole form self-contained and accessible through one Javascript object, aka $scope.my_prefix.

Nested forms must use the AngularJS directive <ng-form ...> rather than <form ...>.

Note

Django, internally, handles the field names of nested forms by concatenating the prefix with the field name using a dash ‘-’. This behavior has been overridden in order to use a dot ‘.’, since this is the natural separator between Javascript objects.

Form with FileField or ImageField

If you have a FileField or an ImageField within your form, you need to provide a file upload handler. Please refer to the section Upload Files and images Images for details.