We are migrating our site from old angularjs to using Vuejs. Step one is to modify our services used throughout the site which all rely heavily on ngResource and convert them into vanilla js code that can be called by Vue.
The challenge is that in addition to making API calls using ngResource they also extending the returning object via prototype. While using a the module pattern in regular javascript I can mimick the API behaviour of the ngResource service. But I am not clear how to set this up so that it can also support the prototype extensions that are being applied to the returning object (whether a single object or an array).
For example one of our current services might look like this
"use strict";
angular.module("myApp")
.factory("PortfolioService",
[
"$resource", "$rootScope", "$http",
function($resource,
$rootScope,
$http) {
var Portfolio = $resource("services/portfolios/:Uid",
{
'_': function() { return Date.now() }
}, {
'query': {
method: "GET",
url: "services/portfolios/",
transformResponse: $http.defaults.transformResponse.concat([
function (data) { return data.Data; }
])
}
});
Portfolio.prototype.getPicUrl= function() {
return this.ImgBasePath + this.ImgUrl;
};
return Portfolio;
}
]);
Note: that it mames a service call called query but also extends the returning object with a new function called getPicUrl.
I have created a JS equivalent that looks like this
const vPortfolioService = (() => {
var baseapipath = "http://localhost:8080/services/";
var Portfolio = {
query: function() {
return axios.get(baseapipath + "portfolios/");
}
};
Portfolio.prototype.getPicUrl= function () {
return this.ImgBasePath + this.ImgUrl;
}
return Portfolio;
})();
The service part works fine but I dont know how to do what ngResource seems to do which is to return a resource from the API which includes the prototype extensions.
Any advice would be appreciated. Thanks
As I mentioned in my replies to @Igor Moraru, depending on how much of your code base you're replacing, and how much of that existing code base made use of the full capabilities of ngResource, this is not a trivial thing to do. But just focusing on the specific example in your question, you need to understand some vanilla JS a bit more first.
Why does the Portfolio
object have a prototype
property when it's returned from $resource()
, but not when it's created via your object literal? Easy: The object returned by $resource()
is a function, which in turn also means it's a class, and those have a prototype
property automatically.
In JavaScript, regular functions and classes are the same thing. The only difference is intent. In this case, the function returned by $resource()
is intended to be used as a class, and it's easy to replicate certain aspects of that class such as the static query
method and the non-static (i.e., on the prototype) getPicUrl
method:
const vPortfolioService = (() => {
var baseapipath = "http://localhost:8080/services/";
class Portfolio {
constructor(params) {
Object.assign(this, params);
}
static query() {
return axios.get(baseapipath + "portfolios/").then(res => {
// this convert the objects into an array of Portfolio instances
// you probably want to check if the response is valid before doing this...
return res.data.map(e => new this(e));
});
}
getPicUrl() {
return this.ImgBasePath + this.ImgUrl;
}
}
return Portfolio;
})();
But the problem is, this probably isn't enough. If you're migrating/refactoring an entire application, then you have to be certain of every instance in which your application uses ngResource, and based on your question, I'm fairly certain you've used it more than this class would allow.
For example, every class created by $resource
also has static methods such as get
, post
, etc., as well as corresponding instance methods such as $get
, $post
, and so on. In addition, the constructor
I've provided for the class is just a very lazy stop-gap to allow you to create an instance with arbitrary properties, such as the properties referenced by the getPicUrl
method.
So I think you have three options:
$http
and $resource
. An example implementation is below, but this is probably the worst option. There's additional overhead by bootstrapping pieces of angular, and tbh it probably needs to bootstrap other pieces I haven't thought of... but it's neat I guess lol:const vPortfolioService = (() => {
var inj = angular.injector(["ng", "ngResource"]);
var $http = inj.get("$http"), $resource = inj.get("$resource");
var Portfolio = $resource("services/portfolios/:Uid",
{
'_': function () { return Date.now() }
}, {
'query': {
method: "GET",
url: "services/portfolios/",
transformResponse: $http.defaults.transformResponse.concat([
function (data) { return data.Data; }
])
}
});
Portfolio.prototype.getPicUrl = function () {
return this.ImgBasePath + this.ImgUrl;
};
return Portfolio;
})();