Create a gist now

Instantly share code, notes, and snippets.

@alanctkc /01-new-post.md Secret
Last active Nov 5, 2016

What would you like to do?

Adding custom methods to data models with Angular $resource

This post is a follow-up post to this one, where we examined three different approaches to modeling data in AngularJS. One of our commenters made mention of a cleaner approach to adding custom methods to $resource models when our API response response allows it, using angular.extend().

In this implementation, we're imagining an API response that looks like this:

[
  {
    "breakpointed": null, 
    "browser": "android", 
    "browser_short_version": "4.3", 
    ...
  }, 
  {
    ...
  }
  ...
]

Each of the response objects in the list is a "Job" that contains a whole lot of metadata about an individual job that's been run in the Sauce cloud.

We want to be able to iterate over the jobs to build a list for our users, showing the outcome of each: "Pass," "Fail," etc.

Our template looks something like this:

<div class="jobs" ng-controller="jobsController" when-scrolled="loadJobs()">
    <table class="table table-striped">
        <tr ng-repeat="job in jobs">
            <td>
                <span class="badge {{ job.getResult()|lowercase }}">{{ job.getResult() }}</span>
            </td>
            <td>
                {{ job.name }}
            </td>
        </tr>
    </table>
</div>

Note the job.getResult() call. In order to get this convenience, however, we need to be able to attach a getResult() method to each Job returned in the response.

So, here's what the model looks like, using Angular $resource:

angular.module('job.models', [])
    .factory('Job', ['$resource', function($resource) {
        var Job = $resource('/api/jobs/:jobId', {
            full: 'true',
            jobId: '@id'
        });

        angular.extend(Job.prototype, {
            getResult: function() {
                if (this.status == 'complete') {
                    if (this.passed === null) return "Finished";
                    else if (this.passed === true) return "Pass";
                    else if (this.passed === false) return "Fail";
                }
                else return "Running";
            }
        });

        return Job;
    }]);

Note that since each resulting object returned by $resource is a Job object itself, we can simply extend Job.prototype to include the behavior we want for every individual job instance.

Then, our controller looks like this (revised from the original post to make use of the not-so-obvious promise):

angular.module('job.controllers', [])
    .controller('jobsController', ['$scope', '$http', 'Job', function($scope, $http, Job) {
        $scope.loadJobs = function() {
            $scope.isLoading = true;
            var jobs = Job.query().$promise.then(function(jobs) {
                $scope.jobs = jobs;
            });
        };

        $scope.loadJobs();
    }]);

The simplicity of this example makes $resource a much more attractive option for our team's data-modeling needs, especially considering that for simple applications, custom behavior isn't incredibly unwieldy to implement.

$resource

UPDATE: Per Micke's suggestion in the comments section below, we've posted a follow-up with a cleaner implementation of the $resource version of the Job model. It parses an API response similar to the one shown in the Restangular scenario and allows for much cleaner method declaration using angular.extend.

Angular provides its own $resource factory, which has to be ...

...

This approach also makes for a pretty elegant controller, except we really didn't like that the query() method took a callback instead of giving us a promise didn't return a promise directly, but gave us an object with the promise in a $promise attribute (thanks Louis!). It felt pretty un-Angular a little ugly. Also, the process of transforming result objects and wrapping them felt like a strange dance to achieve some simple behavior (UPDATE: see this post). We'd probably end up writing more boilerplate to abstract that part away.

...

srigi commented Jul 23, 2014

This looks elegant but there is one catch - your API response is array. In original article domain objects are wrapped in items property. So this is clean approach, but only if you don't send some meta info (paginantion) from API.

I'm very confused. In the original post we see Job.prototype.getResult = function() {...}; and in this post we see angular.extend(Job.prototype, { getResult: function() { ... } }); The result of those two things is identical...you add a property to the object Job.prototype named 'getResult' the value of which is the function you defined. Also the statements that begin var jobs = Job.query().$promise.then(function(jobs) { ... }); can just be replaced with $scope.jobs = Job.query(); That is the point of ngResource; you can just assign its result directly to your scope.

@carlgieringer Very good points and nicely explained. I'm prompted to simplify some $resource coding for a business app of mine. I appreciate you taking the time to comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment