I have two web services:
One returns "Articles" like this:
[
{
"id": "1",
"headline": "some text",
"body": "some text",
"authorId": "2"
},
{
"id": "2",
"headline": "some text",
"body": "some text",
"authorId": "1"
}
]
And the other one returns an "Author" like this, given an id:
{
"id": "1",
"name": "Test Name",
"email": "test@test.com",
"photo": "path/to/img"
}
I want to combine the two, so I can display the authors name and photo in an article overview list.
Like this:
[
{
"id": "1",
"headline": "some text",
"body": "some text",
"authorId": "2",
"author_info": {
"id": "2",
"name": "Another Test Name",
"email": "test2@test.com",
"photo": "path/to/img"
}
},
{
"id": "2",
"headline": "some text",
"body": "some text",
"authorId": "1"
"author_info": {
"id": "1",
"name": "Test Name",
"email": "test@test.com",
"photo": "path/to/img"
}
}
]
I have an "Articles" service that fetches the articles, but what's the best approach for enriching the returned JSON with the author info from the similar "Authors" service before returning the "Articles" service output?
factory('Authors', ['$http', function($http){
var Authors = {
data: {},
get: function(id){
return $http.get('/api/authors/' + id + '.json')
.success(function(data) {
Authors.data = data;
})
.error(function() {
return {};
});
}
};
return Authors;
}]).
factory('Articles', ['$http', 'Authors', function($http, Authors){
var Articles = {
data: {},
query: function(){
return $http.get('/api/articles.json')
.success(function(result) {
Articles.data = result; // How to get the author info into this JSON object???
})
.error(function() {
Articles.data = [];
});
}
};
return Articles;
}])
Please also tell me if this is an entirely wrong approach. :)
When communicating with API, I would recommend the following approach to structuring your services (as advised by Misko Hevery):
// Author model/service
angular.module('myApp').factory('Author', function($http) {
var Author = function(data) {
angular.extend(this, data);
};
Author.get = function(id) {
return $http.get('/authors/' + id).then(function(response) {
return new Author(response.data);
});
};
return Author;
});
// Article model/service
angular.module('myApp').factory('Article', function($http) {
var Article = function(data) {
angular.extend(this, data);
};
Article.query = function() {
return $http.get('/articles/').then(function(response) {
var articles = [];
angular.forEach(response.data, function(data){
articles.push(new Article(data));
});
return articles;
});
};
return Article;
});
// Your controller
angular.module('myApp')
.controller('Ctrl'
,[
'$scope'
,'Article'
,'Author'
,function($scope, Article, Author){
Article.query()
.then(function(articles){
$scope.articles = articles;
attachAuthors(articles);
});
function attachAuthors(articles){
angular.forEach(articles, function(article){
Author.get(article.authorId)
.then(function(author){
article.author = author;
});
});
}
}
]
);
But for sure, I would also advise against fetching all this data in separate calls. Instead, if possible, you should have your API return you the combined JSON. Server-side combining would be many times faster.
Check out JSOG. It is perfect for doing this sort of stuff. There are currently server implementations for Java, Python, Javascript and Ruby. On the client javascript side, you just call JSOG.decode(object). I use it heavily with Angular.
One option is to combine all this data on the server, maybe creating a separate json file. This will simplify the client-side and require only one HTTP request instead of two.
If you are keeping both services (which is not a bad approach) you could fire one, wait for the answer and fire the second. Maybe do the Articles first and then the Authors:
Articles.get(function (articles) {
$scope.articles = articles;
//fire query for Authors
Authors.query(function (authors) {
$scope.authors= authors;
//combine both articles and authors
combineData(articles, authors);
})
});
Here is the function that combines the data:
function combineData(articles, authors){
$.each(articles, function (i, article) {
//find author
var author = $.grep(authors, function(a, j){
return a.id == article.authorId;
});
//set article's author
article.author_info = author;
});
}
Notice that with this configuration your articles will render (by setting $scope.articles) even before making the call to Authors.query