Creando un formulario de contacto con PHP y AngularJS
Este artículo fue escrito en 2014 👴🏼. La mayoría de los consejos aquí no son válidos y no recomiendo usar ninguno de los fragmentos mostrados aquí.
Mantengo este artículo aquí por razones históricas y para recordarme cuánto he mejorado a lo largo de los años.
Solo se hicieron cambios cosméticos a este artículo.
AngularJS parece ser la sensación en estos días, por eso decidí intentar y aprender lo básico de él. De hecho, este mismo sitio web está construido usando AngularJS, sin embargo, encontré un par de problemas mientras trabajaba con él, nada serio, pero uno de ellos que me sorprendió fue el formulario de contacto, ya que me llevó un tiempo entenderlo, decidí compartir lo que aprendí con un pequeño tutorial.
El markup
Para este tutorial voy a usar una configuración muy básica, solo un par de campos y un botón de envío, así que empecemos.
1<!DOCTYPE html>2<html>3 <head>4 <link rel="stylesheet" href="../assets/css/bootstrap.min.css">5 <title>Formulario de contacto usando AngularJS :: demo de Mario Aguiar</title>6 </head>7 <body>89 Contactto10 Nombre:1112 Email:1314 Mensaje:1516 Prometo que no soy un bot17 </fieldset> <button type=”submit” name=”submit” value=”submit”>Enviar</button> </form> </div> </body> </html>18
Muy bien, diría que es bastante simple, tenemos un formulario, con tres campos de entrada, nombre, email y mensaje, también tenemos un cuarto campo usado solo para atrapar bots de spam, esto estará oculto para nuestros visitantes humanos y de hecho necesitamos que esté vacío, lo ocultaremos usando css.
La forma jQuery
Antes de comenzar con AngularJS, veamos cómo haríamos esto usando jQuery, como en los buenos viejos tiempos:
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>¡Gracias por ponerte en contacto!</p>');53 } else {54 contactForm.errorFn();55 }56 },57 errorFn: function () {58 contactForm.$form.prepend('<p>¡Algo falló!, intenta de nuevo.</p>');59 },60 };61 contactForm.init();62})(jQuery);63
Después de configurar un código básico, seleccionamos el formulario, agregamos un callback al proceso de envío, validamos los campos, hacemos la solicitud AJAX y esperamos el resultado, sin embargo, por simple que sea, toma unas cuantas líneas de código.
Ahora veamos cómo podríamos lograr lo mismo usando AngularJS.
La forma AngularJS
En primer lugar, una de las cosas que más me gusta de Angular es la forma en que se integra con nuestro código html, ¿notaste cómo al usar jQuery hicimos casi todo en nuestro archivo js? el código html permaneció prácticamente sin cambios.
AngularJS requiere un poco más de configuración en el lado del marcado, así que cambiemos a esto:
1<!DOCTYPE html>2<html ng-app="contact">3 <head>4 <link rel="stylesheet" href="../assets/css/bootstrap.min.css">5 <title>Formulario de contacto usando AngularJS :: demo de Mario Aguiar</title>6 </head>7 <body>8910¡Gracias por ponerte en contacto!1112¡Algo falló!, intenta de nuevo.1314 Contacto15 Nombre:1617 Email:1819 Mensaje:2021 Prometo que no soy un 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
Unas cuantas directivas aquí y allá y nuestro marcado está listo para ser integrado con AngularJS, desglosemos un poco:
ng-app="contact"
: Este es el comienzo de nuestra aplicación AngularJS, le dice a AngularJS que todo dentro de esta etiqueta es parte de la aplicación de contacto.ng-controller="contactForm"
: Este es el comienzo de nuestro controlador, los controladores en AngularJS son básicamente funciones que controlan el comportamiento de diferentes características en nuestro sitio web.ng-submit="form.$valid && sendMessage(input)"
: Esta etiqueta vincula una función a nuestro evento onSubmit, además, solo se ejecuta cuando el formulario [con nombre] "form" es válido.ng-show="success"
: Este atributo lee el valor de "success" enviado por AngularJS y solo muestra la etiqueta si la condición es válida, en este caso, si "success" o "error" son verdaderos.ng-model
: Esto es lo que AngularJS llama un "enlace bidireccional", significa que todo lo que se escribe en él puede ser utilizado por AngularJS, y todo lo que AngularJS envía a él se mostrará en el campo. ¿Impresionante no?
Ahora veamos nuestro archivo js:
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
Mucho más pequeño que nuestro homólogo de jQuery, esto se debe a que no tenemos ninguna función de validación en este, ¿por qué? Simple, porque AngularJS por defecto valida el formulario antes de enviarlo. No solo eso, sino que automáticamente agrega clases css para campos válidos e inválidos, gracias Angular.
Así que lo único que necesitamos hacer es hacer la solicitud AJAX usando el componente $http
,
y estar atentos al resultado, desglosemos:
angular.module("contact", []):
Primero definimos nuestro módulo, o nuestra aplicación, y todas sus dependencias externas, en este caso no hay ninguna. También almacenaré esto en la variable app.app.controller("contactForm", ["$scope", "$http", function...
: Luego definimos nuestro controladorcontactForm
para manejar nuestro formulario, y sus dependencias, $scope es bastante estándar pero no necesario, puedes aprender sobre $scope en aquí.$http
es el módulo que usaremos para hacer la solicitud AJAX, ya que AngularJS trabaja con inyección de dependencia, necesitamos hacerle saber que lo necesitamos.$scope.success
: definimos valores predeterminados para nuestros objetos de respuesta, estos podrían cambiarse más tarde dependiendo de la respuesta AJAX.$scope.sendMessage( input )
: Esta es la función que usaremos para hacer la solicitud AJAX, tomará todos los datos de entrada que definimos en nuestro formulario. Luego hará una solicitudPOST
aprocessForm.php
y establecerá las variables de éxito o error dependiendo de la respuesta.
El problema
En este punto, nuestro formulario de contacto debería estar funcionando, pero si intentas enviarlo, encontrarás que siempre devuelve un error, ¿por qué es eso?
AngularJS envía los datos como JSON.
Para ser justos, el problema no es causado por AngularJS. El problema realmente es que
PHP no deserializa este formato. Pudimos solucionar esto con jQuery
haciendo uso del método .serialize()
, pero AngularJS no proporciona un
equivalente para esto, entonces, ¿qué podemos hacer?
La solución
Siempre hay un par de formas de casi todo, y esto no es la excepción, aquí te doy dos sugerencias, puedes elegir la que mejor se adapte a tus necesidades.
La forma "salvame jQuery"
Podemos usar jQuery para evitar este problema. Aunque AngularJS ya tiene una
versión reducida de jQuery en él, no proporciona el método que necesitamos,
esto por supuesto significa que necesitamos incluir jQuery junto con AngularJS.
Gracias al método $.param
.
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
Dentro de AngularJS, jQuery es conocido como angular.element
, así que para usar el
método que necesitamos, necesitamos incluir jQuery en nuestro HTML, y llamarlo usando
angular.element
. Ahora probamos nuestro formulario nuevamente, ¡y funciona!
La forma "arreglalo PHP"
Como dije antes, este es más un problema con PHP, así que ¿por qué no dejar que lo arregle?
Es realmente simple, no podemos acceder a nuestras variables $_POST
de la manera habitual,
pero podemos acceder a ellas usando file_get_contents
, así que cambiaremos nuestro PHP a esto:
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 = 'Nuevo mensaje de contacto';12 $body = 'De: ' . $name . "n";13 $body .= 'Email: ' . $email . "n";14 $body .= "Mensaje: " . $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
Y ahí está! Ahora podemos acceder a nuestros datos, y nuestro código AngularJS no fue cambiado.
Conclusión
Los formularios en AngularJS pueden ser un poco complicados, o tal vez es hora de pasar a tecnologías más nuevas, estoy bastante seguro de que este tipo de problema no sucedería si usara NodeJS en lugar de PHP.