Submit form programatically using Angular

10,361 views
Skip to first unread message

Chris Roebuck

unread,
Nov 7, 2012, 6:27:51 AM11/7/12
to ang...@googlegroups.com
Hi, 

I need to do a simple form submit from angular, like $('#my_form').submit() - but I'm aware that you're not supposed to touch dom elements from your controller. The submit call needs to be executed in the success callback of a $http request - which is in my controller code, so I'm not sure how to get this behaviour with angular. 

The form that I am trying to automatically submit also needs to be a traditional submit, i.e. it should redirect the browser because I am submitting some stuff to a hosted payment provider.

Adam Bender

unread,
Nov 7, 2012, 11:49:53 AM11/7/12
to ang...@googlegroups.com
Chris,

The way to go about submitting forms in Angular is a little different than jQuery as you probably guessed. In order to achieve the functionality you describe you will first want to read up on Angular's forms and the FormController. In a nutshell Angular suppresses the default behavior of the page redirection because Angular apps live in a single page and so redirecting away from the page is not something you want most of the time. The good news is that you can very easily achieve the behavior you want with just a little more effort. 

Here is one possible way:

//These values should be bound in your form
//You can also use a single formValues object here if you want
$scope.formValue1 = "value1";
$scope.formValue2 = "value2";

//Make initial http request
$http.get(url, {headers: {'Accept': 'application/json'}})
  .success(function(data, status, headers, config){
      //scrape bound form objects, probably should do some validation here as well :) 
      var formValues = {value1:$scope.formValue, value2:$scope.formValue2}; 
      //submit the form values
      $http.post(postUrl, formValues).success(function(){
          //Redirect the user, make sure to inject $location service!
          $location.path(redirectionUrl);
      });
  })

I think its worth pointing out that when using Angular, thinking about your app in terms of pages and redirects can get you into a little trouble. Its better if you think about the app as a single-page remote client for a backend data service. This can make some of the page-centric behavior implicit in forms a little more cumbersome but it also gives you as a developer more control about how and when data flows and your application states change. 

Witold Szczerba

unread,
Nov 7, 2012, 1:55:01 PM11/7/12
to ang...@googlegroups.com
Hi Guys,
I cannot agree with you, Adam that AngularJS is little different than
jQuery as you are comparing oranges to apples. AngularJS uses jQuery
all the way under the hood and if one wants to do something which is
not directly provided by services and directives - it is very easy and
straight forward to do.

No need to hack angular, no need to walk around problems and no need
to break important rules of AngularJS. In my opinion this is the most
important feature of AngularJS - it does not step in our way :)

Few days ago, I had similar problem. The application shows OpenLayers'
map and I had to add a button to export a map as a bitmap in either:
another tab or as a browser's "save as..." dialog.
Quick browsing led me to the simple jQuery solution, here is how I
have applied it:

.directive('gisExport', function(dischargePoints, wmsLayers) {
return {
require: '^gisMap',
template: '<input type=button value="{{label}}"
ng-click="createAndSubmitForm()">',
scope: {label: '@gisExport'},
link: function(scope, elem, attrs, gisMapController) {
scope.createAndSubmitForm = function() {
var map = gisMapController.map,
bbox = map.getExtent().toBBOX(),
pixels = dischargePoints.asPixelString(map),
layers = wmsLayers.toString();
elem.append('<form action=gis-export target=_blank method=post>'
+ '<input type=hidden name=pixels value="' + pixels + '">'
+ '<input type=hidden name=bbox value="' + bbox + '">'
+ '<input type=hidden name=layers value="' + layers + '">'
+ '</form>');
elem.find('form').submit().remove();
}
}
}
})

This is simple jQuery solution I have found on the web, wrapped by
AngularJS infrastructure (directive) code. In markup I had to add a
button like this:
<div gis-map="map">
<div gis-export="Export as bitmap"></div>
<div id="map"></div>
</div>

As you can see, the "gisExport" directive is applied on a DIV element,
it communicates with a gisMap directive controller (do not confuse
directives' controllers with regular ones).

What is also important, such a code can be easily mocked in GUI tests
if one wants to exclude them in isolation. With little effort
(injecting bbox directly) it can be also easily unit tested (unit - I
mean in isolation).

Regards,
Witold Szczerba

P.S.
Adam, are you positive about your solution with $http? The $http
service is a XMLHttpRequest wrapper, so it is not going to post to
other domains, is it?
> --
> You received this message because you are subscribed to the Google Groups
> "AngularJS" group.
> To post to this group, send email to ang...@googlegroups.com.
> To unsubscribe from this group, send email to
> angular+u...@googlegroups.com.
> Visit this group at http://groups.google.com/group/angular?hl=en.
>
>

Peter Bacon Darwin

unread,
Nov 7, 2012, 3:02:04 PM11/7/12
to ang...@googlegroups.com

Chris
You should listen to Witold. He knows the Angular Way as well as anyone!

Pete
...from my mobile.

Adam Bender

unread,
Nov 7, 2012, 4:16:09 PM11/7/12
to ang...@googlegroups.com
Witold,

Your approach is definitely valid, though I dont think I would agree that my suggestion was hacking around Angular or abusing it in any way. I agree that comparing the jQuery and Angular is a bit of apples to oranges once you understand them but it a useful comparison as jQuery is a frame of reference that many new Angular developers have, which is what I assume was happening here.

The idea of using a directive is fine, but doesn't feel good to me in this use case because if I have many different forms I dont want to create a new directive for each one. That just seems like overkill when a simple ngSubmit will do in most cases and is equally testable given the injectability of the $http service.

Also, It wasnt clear to me that the OP was asking to use jQuery directly but rather using jQuery as an analogy, though if use of jQuery is required your answer is certainly a valid approach.

While it is true that jQuery or jqLite is always operating at some level for DOM stuff I dont think that any Angular developer should have to know that, jQuery is an implementation detail and should be treated that way. Personally I like the approach of letting the binding fill in my form data, using the validation framework to ensure clean data and then using the http.post to send it.  

As far as cross domain submits go certainly my solution is going to run afoul of the crossdomain policy. To deal with that I usually submit cross domain data to the server and proxy top the destination thus avoiding the sandbox issues. However, if proxying the request through the server side is too much of a pain then a simple directive to scrape a given form and perform the submit is probably a good way to go. It would make sense to start with what you provided and generalize to allow it to work with any form and submit to wherever the action attr indicates.


Adam

Witold Szczerba

unread,
Nov 7, 2012, 4:28:44 PM11/7/12
to ang...@googlegroups.com
You are right. It is not good idea to create a directive after
directive for all the stuff. Most of the time it is better to use
what's provided or create reusable stuff ourselves. But sometimes not.
The initial question was about leaving application by customized
redirection to remote payment provider. Seems like a special case and
special cases need special treatment :)

Regards,
Witold Szczerba

Peter Bacon Darwin

unread,
Nov 7, 2012, 5:30:34 PM11/7/12
to ang...@googlegroups.com
From looking at the Original Post, I would say Chris needs to combine the page redirect with the form submission.  I.E. The post/get to a new server will not return some JSON but actually will render a HTML page with which the user has to interact before being redirected back to the original page.  This is not going to work with a $http.post call followed by a $location.path call.  The server will have already forgotten what data was sent in the $http.post.
Pete

Chris Roebuck

unread,
Nov 7, 2012, 6:55:12 PM11/7/12
to ang...@googlegroups.com
Thanks for your reply Adam, however this wouldn't have worked in my case, as the POST from the form needs to be combined with page redirect (I was using saasy/fastspring for payments, and the only way to pre-populate user data in their payment form was via a POST with form data). I realised that this was getting a bit flakey and decided to switch to a better payment provider (spreedly if anyone is interested) which lets me do the same thing using simple query string parameters - so now I can just go back to using a server side redirect.

Chris Roebuck

unread,
Nov 7, 2012, 6:59:56 PM11/7/12
to ang...@googlegroups.com
Witold, 

This solution would have worked for me, so thanks for taking the time to explain it. As I mentioned in my previous reply.. I thought the solution was getting too hacky, so I dumped the crappy payment provider for a much more developer friendly one and this requirement for programatically submitting a form via angular is no longer needed. I'm sure this will come in handy the next time I want to do some quick and dirty dom manipulation though, so thanks!

Chris
Reply all
Reply to author
Forward
0 new messages