Using Spring MVC and Angular? You might need this.

If you are using Spring MVC & @ModelAttributes for the back end of your application along with AngularJS, then you will need the following service to encode your HTTP requests properly.

First, check out this post to make sure your request's content type is properly defined: http://www.josebalius.com/make-angularjs-requests-work-with-php-post-without-refactoring-php-code/ (you will also need this for Spring).

Next step is to encode your parameters properly. A lot applications rely on jQuery's $.param for this, we tried to do the same, however we found that the jQuery param encoding routine was not sufficient for Spring and model bindings.

What we did is develop the following Angular service to build our requests.

angular.module('Spring', []).service("UtilService", function() {

    var SpringUtilService = this;

    var r20 = /%20/g,
        rbracket = /\[\]$/;

    SpringUtilService.encodeParams = function( a ) {
        var s = [],
        add = function( key, value ) {
            // If value is a function, invoke it and return its value

            value = angular.isFunction( value ) || (value === null) ? "" : value;
            s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );

        };

        // If an array was passed in, assume that it is an array of form elements.
        if ( angular.isArray( a ) ) {
            // Serialize the form elements
            angular.forEach(a, function(value, key){
                add(value.name, value.value)
            });

        } else {
            for ( var prefix in a ) {
                SpringUtilService.buildParams( prefix, a[ prefix ], add );
            }
        }

        // Return the resulting serialization
        return s.join( "&" ).replace( r20, "+" );
    };

    SpringUtilService.buildParams = function( prefix, obj, add ) {
        if ( angular.isArray( obj ) ) {
            // Serialize array item.
            angular.forEach(obj, function(v, i){
                if (rbracket.test( prefix ) ) {
                    // Treat each array item as a scalar.
                    add( prefix, v );

                } else {
                    var brackets = ( typeof v === "object" || angular.isArray(v) ? "[" + i + "]": "" );
                    SpringUtilService.buildParams( prefix + brackets, v, add );
                }     
            });

        } else if (obj != null && typeof obj === "object" ) {
            // Serialize object item.
            for ( var name in obj ) {
                SpringUtilService.buildParams( prefix + "." + name, obj[ name ], add );
            }

        } else {
            // Serialize scalar item.
            add( prefix, obj );
        }
    };

});

What exactly does this do? Let's say you have a request with the following payload:

var payload = {  
    collection: [{
        id: 1
    }, {
        id: 2
    }]
}

$http.post('/myUrl', UtilService.encodeParams(payload))

If you were to make this request with jQuery params, it would be converted to:

collection[0][id]=1&collection[1][id]=2  

This simply doesn't work with Spring and JPA models from our experience. You will start to find bugs because Spring cannot correctly build these objects once the request comes in.

Using the service above, the request params will be encoded to:

collection[0].id=1&collection[1].id=2  

This seems to be the way that Spring MVC needs parameters to be passed in.

You might be asking, why not simply post JSON? You are right, this is legacy stuff that lives in our application and we had to support it while we write shiny new code :)

Hopefully it helps those of you out there with the same problem.