Metadata passthrough for ngResource queries

When using the $resource factory to access RESTful APIs it might by necessary to consume resources, that do not return the data as expected by $resource, this is often the case for server-side pagination, where the result is usually not a plain array but an object that contains the result as well as additional metadata such as a total count, page size, etc... This fiddle demonstrates how to circumvent the limitations of $resource as of version 1.2.12 to enable you to consume data with any structure and pass through any metadata while retaining all the behavior provided by $resource.

HTML

<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular-resource.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.12/angular-mocks.js"></script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css">
<div ng-app="w2MetadataDemo">
    <div ng-controller="demoController">
        <div class="panel panel-primary">
            <div class="panel-heading">Displaying {{items.length}} of {{items.$metadata.total}} items</div>
            <ul class="list-group">
                <li class="list-group-item" ng-repeat="item in items">{{item.text}}</li>
            </ul>
        </div>
    </div>
</div>

JavaScript

angular.module('w2MetadataDemo', ['ngResource'])

.service('Item', function ($resource) {
    return $resource('/api/resource/', {}, {
        list: {
            method: 'GET',
            isArray: true,

            // The response is not a JSON array as expected by $resource but rather a
            // JSON object that contains the actual result with additional metadata.
            // It is therefore necessary to extract the payload before it can be
            // processed by the resource.
            transformResponse: function (data) {
                var wrappedResult = angular.fromJson(data);
                wrappedResult.data.$metadata = wrappedResult.metadata;
                return wrappedResult.data;
            },

            // The response ist not a JSON array as expected by $resource but rather a
            // JSON object that contains the actual result with additional metadata.
            // It is therefore necessary to extract the payload before it can be
            // processed by the resource.
            
            // The array returned by transformResponse is not passed directly to the
            // application logic, $resource only copies its contents. Due to this we
            // cannot directly access the added metadata. But fortunately for us
            // we can register a response interceptor in addition to transformResponse.
            // Inside the interceptor we can access the array of instances that is
            // returned by the query and the original data we parsed in
            // transformResponse, so that we can add the metadata.
            //
            // CAVEAT: This depends on the fact that the actual result is exposed as
            // response.resource which might change in future versions
            interceptor: {
                response: function (response) {
                    response.resource.$metadata = response.data.$metadata;
                    return response.resource;
                }
    ...