Creating a contact form with PHP and AngularJS
This article was written in 2014 👴🏼. Most of the tips here are not valid anymore and I don't recommend using any of the snippets displayed here.
I'm keeping this article here for historical reasons and to remind myself how much I've improved over the years.
Only cosmetic changes were made to this article.
AngularJS seems to be the sensation these days, that's why I decided to try and learn the basics of it. In fact, this very website is built using AngularJS, however, I did find a couple of issues while working with it, nothing serious, but one of them that surprised me was the contact form, since it took me a while to get the hang of it, I decided to share what I learned with a small tutorial.
The markup
For this tutorial I'm going to use a very basic setup, just a couple fields and a submit button, so lets get started.
1<!DOCTYPE html>2<html>3 <head>4 <link rel="stylesheet" href="../assets/css/bootstrap.min.css">5 <title>Contact Form using AngularJS :: Mario Aguiar demo</title>6 </head>7 <body>89 Contact10 Name:1112 Email:1314 Message:1516 I promise I'm not a bot17 </fieldset> <button type=”submit” name=”submit” value=”submit”>Submit</button> </form> </div> </body> </html>18
Alright, I'd say that's pretty simple, we have a form, with three input fields, name, email, and message, we also have a fourth field used only to catch spam bots, this will be hidden from our human visitors and in fact we need it to be empty, we'll hide it using css.
The jQuery way
Before I start with AngularJS, let's have a look at how we would do this using jQuery, like in the good old days:
1(function ($) {2 var contactForm = {3 $form: $('form'),4 init: function () {5 this.$form.on('submit', this.validateForm);6 },7 validateField: function ($field) {8 if ($field.val() === '') {9 return false;10 }11 if ($field.attr('type') === 'email') {12 var emailRegex = /^w+@[a-zA-Z_]+?.[a-zA-Z]{2,3}$/;13 if (!emailRegex.test($field.val())) {14 return false;15 }16 }17 return true;18 },19 validateForm: function (e) {20 e.preventDefault();21 var $form = $(this);22 var fields = $form.find('input, textarea'),23 hasError = false;24 fields.each(function () {25 var $field = $(this);26 if ($field.attr('required')) {27 if (!contactForm.validateField($field)) {28 console.log('error');29 hasError = true;30 }31 }32 });33 if (!hasError) {34 contactForm.processForm($form);35 } else {36 contactForm.errorFn();37 }38 },39 processForm: function ($form) {40 var formData = $form.serialize();41 $.ajax({42 url: 'processForm.php',43 type: 'POST',44 dataType: 'json',45 data: formData + '&submit=' + $form.find('button').val(),46 })47 .done(contactForm.successFn)48 .fail(contactForm.errorFn);49 },50 successFn: function (data) {51 if (data.success === true) {52 contactForm.$form.prepend('<p>Thanks for getting in touch!</p>');53 } else {54 contactForm.errorFn();55 }56 },57 errorFn: function () {58 contactForm.$form.prepend(59 '<p>Something wrong happened!, please try again.</p>'60 );61 },62 };63 contactForm.init();64})(jQuery);65
I set up some basic code, we select the form, add a callback to the submit process, validate the fields, make the AJAX request and wait for the result, however simple it is, it takes a few lines of code.
Now let's see how we could achieve the same using AngularJS.
The AngularJS way
First of all, one of the things I like the most about Angular is the way it integrates with our html code, did you notice how when using jQuery we did almost everything in our js file? the html code remained pretty much unchanged.
AngularJS requires a bit more configuration on the markup side, so let's change it to this:
1<!DOCTYPE html>2<html ng-app="contact">3 <head>4 <link rel="stylesheet" href="../assets/css/bootstrap.min.css">5 <title>Contact Form using AngularJS :: Mario Aguiar demo</title>6 </head>7 <body>8910Thanks for getting in touch!1112Something wrong happened!, please try again.1314 Contact15 Name:1617 Email:1819 Message:2021 I promise I'm not a bot22 </fieldset> <button type=”submit” name=”submit” value=”submit”>Submit</button> </form> </div> //ajax.googleapis.com/ajax/libs/angularjs/1.2.21/angular.min.js http://angularForm.js </body> </html>23
A few directives here and there and our markup is ready to be integrated with AngularJS, let's break it down a bit:
ng-app="contact"
: This is the start of our AngularJS application, it tells AngularJS that everything inside this tag is part of the contact application.ng-controller="contactForm"
: This is the start of our controller, controllers in AngularJS are basically functions that control the behavior of different features in our website.ng-submit="form.$valid && sendMessage(input)
: This tag binds a function to our onSubmit event, additionally, it only runs when the form [with name] “form” is valid.ng-show="success"
: This attribute reads the value of “success” sent by AngularJS and only displays the tag if the condition is valid, in this case, if “success” or “error” are true.ng-model
: This one is what AnguarJS calls a “two-way binding”, it means that anything that's written in it can be used by AngularJS, and everything AngularJS sends to it will be displayed in the field. Awesome isn't it?
Now let's look at our js file:
1(function (angular) {2 var app = angular.module('contact', []);3 app.controller('contactForm', [4 '$scope',5 '$http',6 function ($scope, $http) {7 $scope.success = false;8 $scope.error = false;9 $scope.sendMessage = function (input) {10 $http({11 method: 'POST',12 url: '/processForm.php',13 data: input,14 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },15 }).success(function (data) {16 if (data.success) {17 $scope.success = true;18 } else {19 $scope.error = true;20 }21 });22 };23 },24 ]);25})(angular);26
So much smaller than our jQuery counterpart, this is because we don't have any validation functions going on in here, why's that? Simple, because AngularJS by default validates the form before submitting it. Not only that, but it automatically adds css classes for valid and invalid fields, thanks Angular.
So the only thing we need to do is make the AJAX request using the $http
component, and keep an eye open for the result, let's break it down:
angular.module("contact", []):
First we define our module, or our app, and all it's external dependencies, in this case there's none. I will also store this in the app variable.app.controller("contactForm", ["$scope", "$http", function...
: Then we define ourcontactForm
controller to handle our form, and it's dependencies, $scope is pretty standard but not necessary, you can learn about $scope
in here.$http
is the module we'll use to make AJAX request, since AngularJS works with dependency injection, we need to let it know that we need it.$scope.success
: we define defaults for our response objects, these might be changed later depending of the AJAX response.$scope.sendMessage( input )
: This is the function we'll use to make the AJAX request, it'll take all the input data that we defined in our form. Then it will make aPOST
request toprocessForm.php
and set the success or error variables depending on the response.
The problem
At this point, our contact form should be working, but if you try to submit it, you'll find that it always returns an error, why's that?
AngularJS send the data as JSON.
To be fair, the problem is not caused by AngularJS. The problem really is that
PHP does not unserialize this format. We were able to get around this with jQuery
by making use of the .serialize()
method, but AngularJS does not provide an
equivalent for this, so what can we do?
The solution
There's always a couple of ways to almost everything, and this is not the exception, here I give you two suggestions, you can choose the one that best fit your needs.
The “save me jQuery” way
We can use jQuery to get around this problem. Although AngularJS already has a
tiny version of jQuery in it, it does not provide the method we need, this of
course means that we need to include jQuery along AngularJS. That doesn't make
me very happy, but it is a workaround. Thanks to the $.param
method.
1(function (angular) {2 var app = angular.module('contact', []);3 app.controller('contactForm', [4 '$scope',5 '$http',6 function ($scope, $http) {7 $scope.success = false;8 $scope.error = false;9 $scope.sendMessage = function (input) {10 input.submit = true;11 $http({12 method: 'POST',13 url: 'processForm.php',14 data: angular.element.param(input),15 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },16 }).success(function (data) {17 if (data.success) {18 $scope.success = true;19 } else {20 $scope.error = true;21 }22 });23 };24 },25 ]);26})(angular);27
Inside AngularJS, jQuery is known as angular.element
, so in order to use the
method we need, we need to include jQuery in our HTML, and call it using
angular.element
. Now we test our form again, and it works!
The “fix it PHP” way
As I said before, this is more of a problem with PHP, so why not let it fix it?
It's actually really simple, we can't access our $_POST
variables the usual way,
but we can access them using file_get_contents
, so we'll change our PHP to this:
1<?php2 $response = array( 'success' => false );3 $formData = file_get_contents( 'php://input' );4 $data = json_decode( $formData );5 if ( $data->submit && empty( $data->honeypot ) ) {6 $name = $data->name;7 $email = $data->email;8 $message = $data->message;9 if ( $name != '' && $email != '' && $message != '' ) {10 $mailTo = 'example@mydomain.com';11 $subject = 'New contact form submission';12 $body = 'From: ' . $name . "n";13 $body .= 'Email: ' . $email . "n";14 $body .= "Message:n" . $message . "nn";15 $success = mail( $mailTo, $subject, $body );16 if ( $success ) {17 $response[ 'success' ] = true;18 }19 }20 }21 echo json_encode( $response );22?>23
And there! Now we can access our data, and our AngularJS code wasn't changed.
Conclusion
Forms in AngularJS can be a little tricky, or perhaps it's time to move on to newer technologies, I'm pretty sure this kind of problem wouldn't happen if I were to use NodeJS instead of PHP.