Using an AngularJS based frontend, paired with a Rails backend, is a great stack. AngularJS provides $resource
, which is a mostly complete module to interface with the Rails backend and provide CRUD actions. However, if you are using strong parameters with a require statement in Rails, then $resource
can be a bit of a pain.
When generating forms with ActionView, you use form_for @model
, this will generate input names with a name like name="model[attribute]"
. Using this same naming scheme in AngularJS does not work, because the input names are not correctly interpreted as a JSON object. So, instead of using $resource, we can create our own form submission service.
NOTE: There is a dependency on jquery.serializeJSON, https://github.com/marioizquierdo/jquery.serializeJSON
angular.module("MyApp").service 'FormService', ($http) -> class Form submit: ($form) -> $http url: $form.attr('action') method: $form.attr('method') data: $form.serializeJSON() displayErrors: ($form, data) => errors = @formatErrors(data) for input, messages of errors $input = $form.find("input[name=#{input}], input[name*=\"[#{input}]\"], select[name=#{input}], select[name*=\"[#{input}]\"], textarea[name=#{input}], textarea[name*=\"[#{input}]\"]") if $input.length > 0 $group = $input.parent() $group.addClass('has-error') $group.append("<p class='help-block error-message'>#{messages.join("<br/>")}</p>") return clearErrors: ($form) => $form.find('.has-error').removeClass('has-error') $form.find('p.error-message').remove() return formatErrors: (data) -> data.errors = {} if !data.errors? data.errors.base = [] if !data.errors.base? data.errors['base'].push(data.error) if data.error? return data.errors return new Form()
The submit()
function in the above service is capable of serializing form input as a JSON object and submitting it to the form’s action using the form’s method by way of the AngularJS $http
module. This will pass back a promise that can be handled by the caller. If the form returned an error, we can use the displayErrors()
method to display the error messages on each input.
Below is an applied example using FeedbackService
. This service is in charge of handling the submission of a feedback form on an application.
angular.module("MyApp").service 'FeedbackService', (FormService) -> class Feedback _form: $('#feedback-form') constructor: -> @_form.on 'submit', @submit submit: (event) => event.preventDefault() FormService.clearErrors(@_form) result = FormService.submit(@_form) result.success => alert('Your feedback has been submitted. Thank You.') result.error (data) => FormService.displayErrors(@_form, data) return new Feedback()
As we can see the submit()
function in FeedbackService
will first clear any errors, then submit the form, and handle the submission promise by either displaying errors or giving a simple success alert message.
While the form submission works great for all Rails forms, the error displaying is not perfect. Nested resources may not be correctly identified and have the wrong error displayed