Skip to content

Instantly share code, notes, and snippets.

@jelbourn
Last active February 25, 2024 12:51
Show Gist options
  • Save jelbourn/6276338 to your computer and use it in GitHub Desktop.
Save jelbourn/6276338 to your computer and use it in GitHub Desktop.
Example of using an angular provider to build an api service. Subject of August 20th 2013 talk at the NYC AngularJS Meetup. http://www.meetup.com/AngularJS-NYC/events/134578452/See in jsbin: http://jsbin.com/iWUlANe/5/editSlides: https://docs.google.com/presentation/d/1RMbddKB7warqbPOlluC7kP0y16kbWqGzcAAP6TYchdw
/**
* Example of using an angular provider to build an api service.
* @author Jeremy Elbourn (@jelbourn)
*/
/** Namespace for the application. */
var app = {};
/******************************************************************************/
/**
* Interface for a model objects used with the api service.
* @interface
*/
app.ApiModel = function() {};
/**
* Data transformation done after fetching data from the server.
* @type {Function}
*/
app.ApiModel.prototype.afterLoad;
/**
* Data transformation done before posting / putting data to the server.
* @type {Function}
*/
app.ApiModel.prototype.beforeSave;
/******************************************************************************/
/**
* Configuration object for an api endpoint.
* @constructor
*/
app.ApiEndpointConfig = function() {
/**
* Map of actions for the endpoint, keyed by action name. An action has a HTTP
* method (GET, POST, etc.) as well as an optional set of default parameters.
* @type {Object.<string, {method: string, params: Object}>}
*/
this.actions = {};
/** The default actions defined for every endpoint. */
var defaultActions = {
'GET': 'get',
'PUT': 'update',
'POST': 'save',
'PATCH': 'patch',
'DELETE': 'remove'
};
// Add the default actions to this endpoint.
var self = this;
angular.forEach(defaultActions, function(alias, method) {
self.addHttpAction(method, alias);
});
};
/**
* Set the route for this endpoint. This is relative to the server's base route.
* @param {string} route
* @return {app.ApiEndpointConfig}
*/
app.ApiEndpointConfig.prototype.route = function(route) {
this.route = route;
return this;
};
/**
* Set the route for this endpoint. This is relative to the server's base route.
* @param {function(): app.ApiModel} model
* @return {app.ApiEndpointConfig}
*/
app.ApiEndpointConfig.prototype.model = function(model) {
this.model = model;
return this;
};
/**
* Adds an action to the endpoint.
* @param {string} method The HTTP method for the action.
* @param {string} name The name of the action.
* @param {Object=} params The default parameters for the action.
*/
app.ApiEndpointConfig.prototype.addHttpAction = function(method, name, params) {
this.actions[name] = {method: method.toUpperCase(), params: params};
};
/******************************************************************************/
/**
* An api endpoint.
*
* @constructor
* @param {string} baseRoute The server api's base route.
* @param {app.ApiEndpointConfig} endpointConfig Configuration object for the
* endpoint.
* @param {!Object} $injector The angular $injector service.
* @param {!Function} $resource The angular $resource service.
*/
app.ApiEndpoint = function(baseRoute, endpointConfig, $injector, $resource) {
this.config = endpointConfig;
this.$injector = $injector;
this.resource = $resource(baseRoute + endpointConfig.route, {},
endpointConfig.actions);
// Extend this endpoint objects with methods for all of the actions defined
// in the configuration object. The action performed depends on whether or
// not there is a model defined in the configuration; when there is a model
// defined, certain request types must be wrapped in order to apply the
// pre/post request transformations defined by the model.
var self = this;
angular.forEach(endpointConfig.actions, function(action, actionName) {
var actionMethod = self.request;
if (endpointConfig.model) {
if (action.method === 'GET') {
actionMethod = self.getRequestWithModel;
} else if (action.method === 'PUT' || action.method === 'POST') {
actionMethod = self.saveRequestWithModel;
}
}
self[actionName] = angular.bind(self, actionMethod, actionName);
});
};
/**
* Instantiates a model object from the raw server response data.
* @param {Object} data The raw server response data.
* @return {app.ApiModel} The server response data wrapped in a model object.
*/
app.ApiEndpoint.prototype.instantiateModel = function(data) {
var model = this.$injector.instantiate(this.config.model);
angular.extend(model, data);
model.afterLoad();
return model;
};
/**
* Perform a standard http request.
*
* @param {string} action The name of the action.
* @param {Object=} params The parameters for the request.
* @param {Object=} data The request data (for PUT / POST requests).
* @return {angular.$q.Promise} A promise resolved when the http request has
* a response.
*/
app.ApiEndpoint.prototype.request = function(action, params, data) {
return this.resource[action](params, data).$promise;
};
/**
* Perform an HTTP GET request and performs a post-response transformation
* on the data as defined in the model object.
*
* @param {string} action The name of the action.
* @param {Object=} params The parameters for the request.
* @return {angular.$q.Promise} A promise resolved when the http request has
* a response.
*/
app.ApiEndpoint.prototype.getRequestWithModel = function(action, params) {
var promise = this.request(action, params);
var instantiateModel = this.instantiateModel.bind(this);
// Wrap the raw server response data in an instantiated model object
// (or multiple, if response data is an array).
return promise.then(function(response) {
var data = response.data;
response.data = angular.isArray(data) ?
data.map(instantiateModel) : instantiateModel(data);
});
};
/**
* Performs an HTTP PUT or POST after performing a pre-request transformation
* on the data as defined in the model object.
*
* @param {string} action The name of the action.
* @param {Object=} params The parameters for the request.
* @param {Object=} data The request data (for PUT / POST requests).
* @return {angular.$q.Promise} A promise resolved when the http request has
* a response.
*/
app.ApiEndpoint.prototype.saveRequestWithModel = function(action, params, data) {
// Copy the given data so that the beforeSave operation doesn't alter the
// object state from wherever the request was triggered.
var model = angular.copy(data);
if (model && model.beforeSave) {
model.beforeSave();
}
return this.request(action, params, model);
};
/******************************************************************************/
/**
* Angular provider for configuring and instantiating as api service.
*
* @constructor
*/
app.ApiProvider = function() {
this.baseRoute = '';
this.endpoints = {};
};
/**
* Sets the base server api route.
* @param {string} route The base server route.
*/
app.ApiProvider.prototype.setBaseRoute = function(route) {
this.baseRoute = route;
};
/**
* Creates an api endpoint. The endpoint is returned so that configuration of
* the endpoint can be chained.
*
* @param {string} name The name of the endpoint.
* @return {app.ApiEndpointConfig} The endpoint configuration object.
*/
app.ApiProvider.prototype.endpoint = function(name) {
var endpointConfig = new app.ApiEndpointConfig();
this.endpoints[name] = endpointConfig;
return endpointConfig;
};
/**
* Function invoked by angular to get the instance of the api service.
* @return {Object.<string, app.ApiEndpoint>} The set of all api endpoints.
*/
app.ApiProvider.prototype.$get = ['$injector', function($injector) {
var api = {};
var self = this;
angular.forEach(this.endpoints, function(endpointConfig, name) {
api[name] = $injector.instantiate(app.ApiEndpoint, {
baseRoute: self.baseRoute,
endpointConfig: endpointConfig
});
});
return api;
}];
/******************************************************************************/
// Example of creating an angular module to house "core" functionality. It is
// here that you add any custom providers.
app.core = angular.module('core', ['ngResource']);
app.core.config(function($provide) {
$provide.provider('api', app.ApiProvider);
});
/******************************************************************************/
// Example of creating an angular module for your app or part of your app.
// A provider can be injected into a config function, which is run before
// normal services are instantiated.
app.component = angular.module('component', ['core']);
app.component.config(function(apiProvider) {
apiProvider.setBaseRoute('my/app/api/');
apiProvider.endpoint('songs')
.route('songs/:id')
.addHttpAction('POST', 'favorite', {isFavorite: true})
.model(app.Song);
apiProvider.endpoint('albums')
.route('albums/:id')
.model(app.Album);
});
/******************************************************************************/
// Example of making a request with the api service in a controller.
app.SongController = function($scope, $routeParams, api) {
var songsPromise = api.songs.get({id: $routeParams.id});
};
@redlehnewo
Copy link

This is awesome. Thanks for sharing.

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