Programmatically submitting Angularjs form to an external url

When building angular powered web applications, at a certain point you will want to submit a form to an external url. This is the case when redirecting to a payment gateway (like Paypal). When deeling with GET requests, you simply redirect to the url with your parameters sent as a query string.

But, when the service you’re using requires parameters sent in a POST request, things might get somewhat tricky, especially if you don’t want to pollute your angularjs controllers with jquery code (and this also is considered bad practice).

What you want to do is to create a dynamic form, populate it with your inputs + values, and then programmatically submit it.

In this tutorial, we will use angular directives to achieve this, the same directive can be used for multiple forms, you’ll need to specify a redirect url, a method (POST, GET)  and your data.

Trigger submission from your controller using angular event dispatcher

The best way I found for your Controller or Service to communicate with your directive is through events.

once your data is ready, trigger the event using angular $broadcast on the $rootScope (Do this in your service not controller).

$rootScope.$broadcast('gateway.redirect', data);

event name ‘gateway.redirect’ is just an example, name it anything you want, since I did this to submit POST request to a payment gateway, this is how I named it.

now it’s time to listen to that event and grab the data.

if you want your directive to be generic and reusable anywhere else, set your data variable to be as follows:

var data = {
    url: 'http://my-external-url.com',
    method: 'POST',
    params: {
        'input1': 'value1',
        'input2': 'value2'
    }
}

Listen to the event && grab the data to populate your form

We will name our directive auto-submit-form, and can be embedded like so:

<div auto-submit-form event="gateway.redirect"></div>

remember to replace gateway.redirect by whatever event you are listening to. This is all you do in your html code.

Now let’s code our angular directive code:

'use strict';

app.directive('autoSubmitForm', ['$timeout', function($timeout) {
    return {
        replace: true,
        scope: {},
        template: '<form action="{{ formData.url }}" method="{{ formData.method }}">' +
                  ' <div ng-repeat="(key,val) in formData.params">' +
                  '     <input type="hidden" name="{{ key }}" value="{{ val }}" />' +
                  ' </div>' +
                  '</form>',
        link: function($scope, element, $attrs) {
            $scope.$on($attrs['event'], function(event, data) {
                $scope.formData = data;
                console.log('redirecting now!');
                $timeout(function() {
                    element.submit();
                })
             })
        }
    }
}])

Here we use $scope.$on to listen to whatever event name you setup in your html directive.

The template uses an ng-repeat on your params, and grabs property name and their values to create hidden inputs.

The reason why we wrap the element.submit() in a $timeout function is to prevent form submission before Html is even compiled, submission will be delayed until your thread’s free.

So hopefully, you will be able to avoid page refresh just before your form is submitted, this was an annoying problem I encountered.

Thank you for visiting my blog, if you need any further help, feel free to ask in the comments below.


 

Update (18/01/2016):

I just created a plunker with some improvements:

https://plnkr.co/edit/lVflCxdL9RslGH1N74fR?p=preview

25 comments

  1. Tried this but I get an error when trying to submit the form.
    Says that “undefined is not a function”. Using breakpoints I can see its the element.submit() thats failing. Did you make your own submit function or can you think of other reason it wouldn’t be working.

    1. Hi Emrys,

      by default angular uses a lite version of Jquery (Jqlite), which does not have the submit function.

      Try to include jquery before angular in your project, and test again.

      if it still not working wrap your element with jquery like so $(element).submit().
      It will work.

  2. I get this error:

    Error: [$interpolate:interr] Can’t interpolate: {{formData.url}}
    Error: [$sce:insecurl] Blocked loading resource from url not allowed by $sceDelegate policy. URL:

    Any idea why this may be?

    1. try to inject “$sce” service to mark you redirect Url as being safe. Doc.
      and use it like so: data.redirectUrl = $sce.trustAsResourceUrl(data.redirectUrl)
      Before you broadcast the “gateway.redirect” event.

      1. Hi Jaouad,

        Great solution you proposed, could you assist me to use the same.

        Where should I add “data.redirectUrl = $sce.trustAsResourceUrl(data.redirectUrl) ” and its dependancy?
        Should I add it in the directive or the service from where I am triggering the event.

        Thanks in advance!

        1. Sorry I did not include that in the post.

          Just do it before you broadcast the event. It’s just a way to mark the url as being safe.

    1. No Vikram, you are performing a redirect here, the browser is making the request for you, and there is no way to force it to send custom headers.

      If you want to query an API with custom header, you’ll need to use $http service.

  3. I am using AngularJS and facing problem with payment gateway. I am using icici payment gateway. I formed the gateway url correctly and click url redirected to icici merchant site. Fill the details and click on PROCEED (if i fill the data and click on “proceed” amount deducted) or CANCEL. i unable capture the response from merchant site to send as post method to my Controller.

    Could you please help me how to get response data from merchant site while redirecting to my app.

    1. Define a route in your routes.js ( either using ngRoute or ui router, it does not matter).

      set a new route complete-purchase?param1&param2&param3 add all params that you expect to receive from the gateway, this will be your return url.

      all params will be available in $routeParams or $stateParams ( if you are using ui router ).

      and from there you can do whatever you want with those params.

    1. We are redirecting to an external url, if you are using this directive for a payment gateway for example, you supply a return url to the gateway, and once the payment is successful, it redirects back to your site with some data.

      1. Yes exactly, using this for a payment gateway. So I will receive xml data once redirected. So how do I get it? Where will it be? TIA 🙂

  4. Hi Jaouad, I am relatively new to angularjs, I followed your tutorial but somehow the broadcast event is failing in my case. I created a custom service and that service was calling the broadcast method on rootscope still the listener doesn’t seem to work, however when I console log the element I can see the listener is attached, so I think it is the event triggering that is failing, can you help me with that?
    How to actually broadcast the event can you elaborate on that, any help is appreciated. Thanks in advance

Leave a Reply