ngResource custom model actions - angularjs

I'm using ngResource to handle my models in my Ionic/Angular app and I'm having trouble figuring out how/if I can make custom actions on the resource.
I'm storing my model instances in local storage and when I update a record, I want to update the local storage as well. I have this working, but I'm having to copy and paste code for multiple instances and would like to keep it DRY.
LogEntry.update($scope.timeLog, function(data) {
// update local storage
for ( var i = 0; i < logEntries.length; i++) {
if(logEntries[i].id == $scope.timeLog.id){
logEntries[i] = $scope.timeLog;
}
};
localStorageService.set('LogEntries', logEntries);
});
Here is a situation where I update a record, and after the promise returns I update local storage. I would like to make this repeatable, how I envision it being possible (based on other things I've seen in other frameworks and other languages) is something like:
LogEntry.update($scope.timeLog, function(data) {
// update local storage
LogEntry.updateLocalStorage($scope.timeLog);
});
My resource looks like:
.factory('LogEntry', function(config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/', {}, {
'update': {
method:'PUT',
params: { id: '#id' }
}
});
})
Maybe I'm missing something in the docs, but it's pretty short and I'm not seeing a way to do this. Is something like LogEntry.updateLocalStorage($scope.timeLog); possible to store with ngResource, or do custom actions like that need to come from somewhere else? I'd like to keep my model-related actions together if possible.

You could use the transformResponse method in your resource definition. It's kind of a hack since you don't actually need to alter the response, but it allows you to preform actions with the returned data:
{function(data, headersGetter)|Array.<function(data, headersGetter)>}
transform function or an array of such functions. The transform function takes the http response body and headers and returns its transformed (typically deserialized) version. By default, transformResponse will contain one function that checks if the response looks like a JSON string and deserializes it using angular.fromJson. To prevent this behavior, set transformResponse to an empty array: transformResponse: []
https://docs.angularjs.org/api/ngResource/service/$resource
.factory('LogEntry', function (config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/', {}, {
'update': {
'method': 'PUT',
'params': {
'id': '#id'
},
'transformResponse': function (data, header) {
// do stuff with data
return data;
}
}
});
})
That way it will always execute when you get a response but you could also use the transformRequest method, which will always fire on request regardless if you're getting a response.
{function(data, headersGetter)|Array.<function(data, headersGetter)>}
transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version. By default, transformRequest will contain one function that checks if the request data is an object and serializes to using angular.toJson. To prevent this behavior, set transformRequest to an empty array: transformRequest: []
https://docs.angularjs.org/api/ngResource/service/$resource
Which you choose depends on your usecase. Using transformRequest you could always save to localstorage, even when remote is down.

Related

Angularjs $resource Cache

I have a more generic implementation of $resource as "Datarepository" factory in my application and all the REST API calls calls this factory to do the "REST" operation
myapp.factory('DataRepository', function ($resource) {
var resourceFactory = function (url) {
return $resource(url, {}, {
update: { method: 'PUT' }
}
);
}
return {
invokeAPI: resourceFactory
}
});
A sample call to the repository get method looks like this
DataRepository.invokeAPI(myappURL).get();
For a specific scenario alone, i would like to "cache" the data.. I dont want to disturb the "Datarepository" factory method and just would like to add the cache paramter for those URL's which i would like to cache the data
something like this
DataRepository.invokeAPI(myappURL).get({cache:true});
The above implementation doesnt work the way it is expected and it passed the cache as query string paramter. I read the angularjs documentation for $resource. I got it how to set it at $resource level but i m not sure how to pass it to the resource through normal function call without disturbing the Factory implementation

$http(config) vs $http.get(...): What is the difference?

According to the Angular documentation for $http, it seems that the constructor
$http(config)
and the functions
$http.get(...)
both return the same things - HttpPromises.
So what is the difference between them? Or are they just two ways to write the exact same thing?
$http.get(...), as said in the doc you gave link to, is a shortcut method to perform GET request. You won't be able to make any other type of request with $http.get - GET only. Note that POST, PUT, HEAD and DELETE have their corresponding shortcut methods too.
All these methods, however, are essentially the $http(config) calls - with method parameter pre-specified. Here's how it's done (1.3.6 source):
createShortMethods('get', 'delete', 'head', 'jsonp');
// ...
function createShortMethods(names) {
forEach(arguments, function(name) {
$http[name] = function(url, config) {
return $http(extend(config || {}, {
method: name,
url: url
}));
};
});
}
Still, sometimes it's more convenient to use $http(config) syntax - for example, if one has to choose the request method based on some external conditions that should be easy to switch. Note that if you don't specify method property in config, GET is used:
function $http(requestConfig) {
var config = {
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse
};
// ... some checks skipped
extend(config, requestConfig);
}

Setting default fields on an ngResource model

I have an ngResource model representing a REST API endpoint. I'd like to set some default fields on this model, so that the data that's POSTed is always correct. Ideally I'd like somethings like this:
resource = new Resource({field1: 'value1');
resource.$save();
// POST with data: { field1: 'value1', field2: 'defaultvalue' }
This answer seems to cover the same case, but I'd rather have the defaults in place immediately after constructing the Resource, rather than filling in at the last minute before POSTing. (The field I'd like to default is an array, in case that makes a difference).
My factory looks like this:
.factory('Resource', ['$resource',
function($resource) {
var Resource = $resource('/api/resource/:resourceId', {}, {
query: {method: 'GET', isArray: true}
}}
return Resource;
}
])
I've tried poking around in the prototype of the Resource before returning it, but can't figure out how to insert a default value there.

Can a call to $http be made more concise?

I find myself frequently using this construct in my controllers:
$http({method: 'GET', url: '../ajax/mycallback', cache: false}).
success(function(data) {
$scope.data = data;
});
Is there any way to make this more concise? In particular, is there a kind of "default success method" that just stores the result on the scope? Also, is there a way to globally set cache to false, so I can use $http.get() ?
Option 1: you only need to pass the value to $scope
$http returns a promise, so you can share the returning result directly with the scope:
$scope.data = $http({method: 'GET', url: '../ajax/mycallback', cache: false})
Angular has automatic promise fulfillment support in templates. So if you assign the data to templates, it will automatically trigger the digest cycle upon fulfillment (read: it will trigger the update when the $http will succeed).
But in this case I would suggest you to use $resource instead of $http and incapsulate the $resource creation as a service.
You also can use $resource the same way, as it has natural automatic promise fulfillment. Natural states that promises will be replaced with actual values (this logic includes arrays support!):
$scope.data = MyResourceService(...)
Option 2: you have a more complex callback
You can simply create a factory function to create a closure within a specific scope:
function cb(scope, scopeObjName) {
return function(data){
scope["scopeObjName"] = data;
// common logic goes here
}
}
and use it like this:
$http({method: 'GET', url: '../ajax/mycallback', cache: false})
.success(cb($scope, "data"))
As you can see, the answer depends on the actual usecase and can be improved further.
You could do the following:
myApp.config(function($httpProvider){
$httpProvider.defaults.cache = false;
});
as for your callback, If that is all you're doing, the anonymous function is the best option. Otherwise you'd have to bind a generic function every http invocation and pass it a context..like this:
$http.get(...).success(standardSuccess.bind({$scope:$scope,p:'data'}));
function standardSuccess(data){
this.$scope[this.p] = data;
}
Then you would just keep that in an injectable somewhere. But as you can see is more verbose and less readable. And slower.
Another option is to pass the scope into the httpConfig itself. and use an interceptor to set your property. (as the config is received in an interceptor. Roughly like:
myApp.config(function($httpProvider){
$httpProvider.defaults.cache = false;
$httpProvider.interceptors.push('defaultHttpSetter');
});
myApp.factory('defaultHttpSetter',function($q){
return {
'response': function(response){
response.config.scope[response.config.prop] = response.data;
return $q.when(response);
}
};
});
Which would be used like..
$http.get('/myurl',{scope:$scope,prop:'data'});
I don't have issue with the other answers, but just in case they are over-thinking it. You can do this:
var genericSuccessFunction = function(data) {
$scope.data = data;
}
You can create your result function as part of the controller and save it as a controller local variable.
Then you just need to pass the result function into the success:
$http({method: 'GET', url: '../ajax/mycallback', cache: false}).success(genericSuccessFunction);
In my experience, I often find myself using this approach for an error function, but I don't often find myself able to use generic success functions.

Passing a path with '/' to $resource or $http factory

Is there a way to pass a parameter containing / to Factory? Want to accomplish something like
.factory('MyData', ['$resource', function ($resource) {
return $resource('http://1.2.3.4/:urlFragment', {
urlFragment : '' // default empty
}, {
getData : {
method : 'GET'
},
And calling it
$scope.scopeVar = MyData.getData({urlFragment : '/some/path/to/data'});
Looking at the network console, I see that / are replaced with %2.
Can I encode the passed parameter inside Factory? (Using $http or $resource).
Or in general, how can I execute any functions on parameters inside factory?
No, you can't really get access to the url inside of your factory because $resource automatically handles it. But thankfully Angular gives you a way to get access to the url before it is called by using the $resource directly. Looking at the docs here, one of the actions you can supply in your $resource declaration is a transformRequest property.
return $resource('http://1.2.3.4/:urlFragment', {urlFragment: ''}, {
getData: {method: 'GET', transformRequest: function(data, headers){
// make your modifications here to either data or headers
}}
});
Although I haven't actually run this code, I believe that should allow you to do what you want. Let me know if it doesn't.

Resources