Previously I was calling an ASP.NET Web API controller from an AngularJS controller using the $http service.
$http.get(serviceUrl).success(function (data) { $scope.videos = data; });
With a little bit of work I can use a higher level of abstraction in Angular – a custom service based on $resource. $resource is a factory that can create an object with methods that map to the WebAPI action methods I need to invoke. If I scaffold a VideoController for the WebAPI, the default actions will let me:
- GET all videos
- GET a video by ID.
- POST a new video
- PUT an updated video
- DELETE a video
To configure a service that will map JavaScript methods to these controller actions on the server, I'll need to use the .module API.
angular.module("videoService", ["ngResource"]). factory("Video", function ($resource) { return $resource( "/api/videos/:Id", {Id: "@Id" }, { "update": {method:"PUT"} } ); });
These lines of code are registering a videoService that depends on ngResource (another module in Angular). When a controller requires a video service, the factory method will take care of configuring the service using $resource. There are a few interesting lines of code here.
- "/api/videos/:Id" is the URL to interact with the resource on the server (you could send this value to the client instead of hard coding the value). The :id part tells Angular how part of the URL path is parameterized with data.
- {Id: "@Id"} tells Angular to grab the ID parameter for the URL from an object's Id property.
- { "update": {method:"PUT"}} tells Angular to add a custom method to the resource. The custom method has the name "update" and it will use HTTP PUT when sending a message to the server. The default methods for a resource include query, save, and delete, which map to GET, POST, and DELETE verbs, so we needed one more for PUT.
Another call to .module will tell Angular that my app requires the videoService as a dependency.
angular.module("videoApp", ["videoService"]);
And now the controller can "ask" for Angular to inject a video service by adding another parameter named "Video" to the constructor (Video was the first parameter to .factory, above):
var VideoController = function ($scope, Video) { /// ... }
What used to require three lines of code to grab all videos from the server now requires a single line of code*.
$scope.videos = Video.query();
The resource service returns an empty array in this scenario, but there is a promise working behind the scenes to populate the array with data (which data binding will then automatically push into the view). The delete, save, and update methods are also available on these objects once they return from the server.
$scope.createVideo = function (newVideo) { newVideo.$save(); $scope.videos.push(newVideo); }; $scope.updateVideo = function(video) { video.$update(); }; $scope.deleteVideo = function (video) { video.$delete(); $scope.videos = _.without($scope.videos, video); };
The code is naïve and assumes a call to the server will always work. There are a couple strategies for error handling, including passing an error handler to the resource methods, or registering an $http interceptor to process HTTP messages at a global level (that's foreshadowing). For now, the heart of the code is in an updated gist.
* Still not entirely comfortable with the naming conventions I want to use for this scenario.