Skip to content

Instantly share code, notes, and snippets.

@nathanbarrett
Last active January 9, 2017 20:50
Show Gist options
  • Select an option

  • Save nathanbarrett/cfe31f4ad3e1235872f6dc3c4c901bf4 to your computer and use it in GitHub Desktop.

Select an option

Save nathanbarrett/cfe31f4ad3e1235872f6dc3c4c901bf4 to your computer and use it in GitHub Desktop.
Angular 1.X helper service for helping with crud tasks
/*
* A backend service for other services wanting crud operations
*
* @params
*
* @returns none
*/
(function () {
'use strict';
angular.module('app').service('CrudService', CrudService);
CrudService.$inject = ['$http', '$log', '$timeout', '$q'];
function CrudService ($http, $log, $timeout, $q){
var self = this;
var logger;
/**** Initializations ****/
self.$onInit();
/**** Methods ****/
/*
* Initialization of the service
*
* @params none
*
* @returns none
*/
function $onInit () {
self.config = {
modelName: null,
pluralModelName: null,
defaultInclude: null,
actionHttp: {
create: {
method: 'PUT',
url: null
},
createMany: {
method: 'PUT',
url: null
},
get: {
method: 'GET',
url: null
},
getMany: {
method: 'GET',
url: null
},
update: {
method: 'POST',
url: null
},
updateMany: {
method: 'POST',
url: null
},
delete: {
method: 'DELETE',
url: null
},
deleteMany: {
method: 'DELETE',
url: null
}
},
defaultWhere: null,
testing: {
enabled: false,
delay: 500,
generator: null
},
logging: {
events: false,
errors: false
},
responseDataKey: 'data',
responseSuccessKey: 'success',
responseSuccessValue: true,
responseSuccessStrict: true,
attachQueryItemsToRequestBody: true, // on put and post requests send 'include' and 'where' queries in the body vs the url
idPlaceholder: ':id'
};
if (window.errorLogger) {
logger = window.errorLogger;
}
else {
logger = {
error: function (description, context) {
$log.error(description, context);
throw description;
}
};
}
}
/*
* Helper for setting a config object
*
* @params object config
*
* @returns none
*/
self.setConfig = setConfig;
function setConfig (config) {
self.config = overrideValues(angular.copy(self.config), config);
}
/*
* Sets the only required configuration item, modelName
*
* @params string modelName
*
* @returns none
*/
self.model = model;
function model (modelName, pluralName) {
self.config.modelName = modelName;
if (pluralName) {
self.config.pluralModelName = pluralName;
}
}
/*
* Fetches a model by id
*
* @params int id, (object|array) include
*
* @returns Promise<object response>
*/
self.get = get;
function get (id, include) {
if (angular.isArray(id) || angular.isObject(id)) {
return self.getMany(id, include);
}
return call('get', id, include);
}
/*
* Fetches many models, confined to ids if needed
*
* @params array ids
*
* @returns Promise<model>
*/
self.getMany = getMany;
function getMany (ids, include, where) {
if (!angular.isArray(ids)) {
ids = [];
}
return call('getMany', ids, include, where);
}
/*
* Creates a new model
*
* @params object data, include
*
* @returns Promise<model>
*/
self.create = create;
function create (data, include) {
return call ('create', data, include);
}
/*
* Creates many new models
*
* @params object data, array include
*
* @returns Promise<Array<model>>
*/
self.createMany = createMany;
function createMany (data, include, where) {
return call ('createMany', data, include, where);
}
/*
* Updates an existing model
*
* @params object data, array include
*
* @returns Promise<model>
*/
self.update = update;
function update (data, include) {
return call ('update', data, include);
}
/*
* Updates many existing models
*
* @params object data, array include, array where
*
* @returns Promise<Array<model>>
*/
self.updateMany = updateMany;
function updateMany (data, include, where) {
return call ('updateMany', data, include, where);
}
/*
* Deletes an existing model
*
* @params int id
*
* @returns Promise<model>
*/
self.delete = del;
function del (id) {
return call ('delete', id);
}
/*
* Deletes many existing models
*
* @params array ids
*
* @returns Promise<Array<model>>
*/
self.deleteMany = deleteMany;
function deleteMany (ids) {
if (!angular.isArray(ids)) {
ids = [];
}
return call ('deleteMany', ids);
}
/*
* Private function that all of the calls go through
*
* @params string action, string method, (object|array) data, (object|array) include, (object|array) where
*
* @returns Promise<object response>
*/
function call (action, data, include, where) {
if (!angular.isString(self.config.modelName) || self.config.modelName === '') {
logger.error('You need to give a model name before making calls', {config: self.config, args: arguments});
}
var callMethod = self.config.actionHttp[action].method;
var callModelName = self.config.modelName;
if (action.indexOf('Many') >= 0 && self.config.pluralModelName) {
callModelName = self.config.pluralModelName;
}
var callId = null;
if (angular.isNumber(data) || angular.isString(data) || angular.isArray(data)) {
callId = data;
}
var httpConfig = {
method: callMethod,
url: generateUrl(action, callId, include, where)
};
if ((angular.isObject(data) || angular.isArray(data)) && (callMethod === 'PUT' || callMethod === 'POST')) {
httpConfig.data = data;
}
if (self.config.testing.enabled) {
if (['create', 'createMany'].indexOf(action) >= 0) {
var model = data;
model.id = Math.ceil(Math.random() * 1000);
}
else {
if (angular.isFunction(self.config.testing.model)) {
model = self.config.testing.model();
}
else {
model = self.config.testing.model;
}
}
var testingDefer = $q.defer();
$timeout(function () {
testingDefer.resolve(model);
}, self.config.testing.delay);
return testingDefer.promise;
}
return $http(httpConfig)
.then(function (response) {
var data = response[self.config.responseDataKey];
if (self.config.responseSuccessKey &&
(self.config.responseSuccessStrict ?
data[self.config.responseSuccessKey] !== self.config.responseSuccessValue :
data[self.config.responseSuccessKey] != self.config.responseSuccessValue)) {
logger.error(callModelName + ': non success response', {httpConfig: httpConfig, response: response});
return null;
}
if (self.config.logging.events) {
console.group('Succesfully ' + getActionVerb(action) + ' ' + callModelName);
console.info(callModelName + ':');
console.log(data[callModelName]);
if (data.previous) {
console.info('Previous ' + callModelName + ':');
console.log(data.previous);
}
console.info('response:');
console.log(response);
console.groupEnd();
}
return data[callModelName];
}, function (error) {
if (self.config.logging.errors) {
logger.error(callModelName + ': error response', {error: error, httpConfig: httpConfig});
}
return error;
})
}
/*
* Generates the call url given the parameters
*
* @params string action, include, where
*
* @returns string url
*/
function generateUrl (action, id, include, where) {
var url = null;
switch (action) {
case 'create':
if (self.config.actionHttp.create.url) {
url = self.config.actionHttp.create.url;
}
else {
url = '/' + self.config.modelName;
}
break;
case 'createMany':
if (self.config.actionHttp.createMany.url) {
url = self.config.actionHttp.createMany.url;
}
else if (self.config.pluralModelName) {
url = '/' + self.config.pluralModelName;
}
else {
url = '/' + self.config.modelName + 's';
}
break;
case 'get':
if (self.config.actionHttp.get.url) {
url = injectIdIntoUrl(self.config.actionHttp.get.url, id);
}
else {
url = '/' + self.config.modelName + '/' + id;
}
break;
case 'getMany':
if (self.config.actionHttp.getMany.url) {
url = self.config.actionHttp.getMany.url;
}
else if (self.config.pluralModelName) {
url = '/' + self.config.pluralModelName;
}
else {
url = '/' + self.config.modelName + 's';
}
break;
case 'update':
if (self.config.actionHttp.update.url) {
url = injectIdIntoUrl(self.config.actionHttp.update.url, id);
}
else {
url = '/' + self.config.modelName + '/' + id;
}
break;
case 'updateMany':
if (self.config.actionHttp.updateMany.url) {
url = self.config.actionHttp.updateMany.url;
}
else if (self.config.pluralModelName) {
url = '/' + self.config.pluralModelName;
}
else {
url = '/' + self.config.modelName + 's';
}
break;
case 'delete':
if (self.config.actionHttp.delete.url) {
url = injectIdIntoUrl(self.config.actionHttp.delete.url, id);
}
else {
url = '/' + self.config.modelName + '/' + id;
}
break;
case 'deleteMany':
if (self.config.actionHttp.deleteMany.url) {
url = self.config.actionHttp.deleteMany.url;
}
else if (self.config.pluralModelName) {
url = '/' + self.config.pluralModelName;
}
else {
url = '/' + self.config.modelName + 's';
}
break;
default:
logger.error('Not a defined action for crud service: ' + action, {args: arguments});
break;
}
var requestMethod = self.config.actionHttp[action].method;
if (angular.isArray(id) && id.length > 0 && (requestMethod === 'DELETE' || requestMethod === 'GET')){
var idAppend = url.indexOf('?') >= 0 ? '&' : '?';
url = url + idAppend + 'ids=' + JSON.stringify(id);
}
if ((include || where) && !(self.config.attachQueryItemsToRequestBody && (requestMethod === 'POST' || requestMethod === 'PUT'))) {
if (include) {
var includeAppend = url.indexOf('?') >= 0 ? '&' : '?';
url = url + includeAppend + 'include=' + JSON.stringify(include);
}
if (where) {
var whereAppend = url.indexOf('?') >= 0 ? '&' : '?';
url = url + whereAppend + 'where=' + JSON.stringify(where);
}
}
return url;
}
/*
* Finds any optional id placeholder in the given url and replaces it with the given id
*
* @params string url, int id
*
* @returns string injectedUrl
*/
function injectIdIntoUrl (url, id) {
if (!id || url.indexOf(self.config.idPlaceholder) < 0 || angular.isArray(id)) return url;
return url.replace(self.config.idPlaceholder, id);
}
/*
* Overrides values in objectA with any matching values of object B
*
* @params object a, object b
*
* @returns object
*/
function overrideValues (a, b) {
if (!angular.isObject(a) || !angular.isObject(b)) {
return a;
}
angular.forEach(a, function (value, key) {
if (angular.isObject(a[key])) {
if (angular.isObject(b[key])) {
a[key] = overrideValues(a[key], b[key]);
}
else {
a[key] = b[key];
}
}
else if (b[key] !== undefined) {
a[key] = b[key];
}
});
return a;
}
function getActionVerb (action) {
if (action === 'get') return 'got';
else if (action === 'getMany') return 'got many of';
else if (action.indexOf('Many') >= 0) {
var baseAction = action.replace('Many', '');
return baseAction + 'd many of';
}
else {
return action + 'd';
}
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment