Calling an Angular Factory recursively gives a circular dependency error - angularjs

I have an application with multithreaded comments, and I'm trying to use an Angular factory to create objects with all the necessary functions and properties recursively, since of course, a comment can have many replies, each of which can have more replies...
Below is my factory representing a comment:
app.factory("Comment", ["commentHttpService", "Comment", function(commentHttpService, Comment) {
return function(comment) {
var self = comment;
// lots of other properties and methods here
self.children = self.children.map(function(reply) {
return new Comment(reply);
});
return self;
}
}]);
Of course, this doesn't work since I'm injecting Comment into itself.
How can I resolve this and still accomplish my original goal?

app.factory("Comment", ["commentHttpService", function(commentHttpService) {
function Comment(comment) {
var self = comment;
// lots of other properties and methods here
self.children = self.children.map(function(reply) {
return new Comment(reply);
});
}
return Comment;
}]);

Another way of achieving what you want could be creating a factory that lets yu create a Comment instance which has all its apis attached to it:
app.factory("CommentFactory", function() {
function Comment(comment) {
this.message = comment;
this.replies = [];
this.getMessage = function() {
return this.message;
}
this.addReply = function(reply) {
var replyComment = new Comment(reply);
this.replies.push(replyComment);
return replyComment;
}
this.getReplies = function() {
return replies;
}
}
return {
createComment: function(message) {
return new Comment(message);
}
}
});
Here is a small plunk:
http://plnkr.co/edit/BmfCm9?p=preview
You can create comments using factory and add replies to comments which are also instances of Comments.

Related

Creating angular base service and sub services

I'm trying to create a general service for dynamic listing objects i angular and for different types of Objects I need slightly different methods for this service. So I thought it would be the best to have a base service and some sub-services. The problem is, that I need to initialize the base service with different Objects depending on sub-service.
So that what I got so far:
Base List-Service (shortened to the relevant)
App.factory('List', ['$q',
function (){
var List = function(Item, searchParams){
this.Item = Item;
this.searchParams = searchParams;
//....
this.nextPage();
};
//.....
List.prototype.nextPage = function () {
//.....
this.Item.find({
//.....
}.bind(this));
};
return List;
}]);
Sub-service of List-Service
App.factory('UserList', [
'User', 'List','$q',
function (User, List) {
UserList = function(){
var searchParams = {
// params Object
};
return new List(User, searchParams);
};
// extend base class:
UserList.prototype.updateUser = function(id){
//.....
}
//....
return UserList;
}]);
Currently just the UserList is loaded, but: Of course it loads every time a new instance, due the new operator when it's called, but I just want one instance. But leaving the new operator throw's an error that this.nextPage(); would be undefined function. Beside this it seems the extension function updateUser is not applied.
So what's the best practice to inherit from other service with passing arguments to parent service in angular?
I gotta work it.
changed sub service to this to inherit proper from base:
App.factory('UserList', [
'User', 'List','$q',
function (User, List) {
var UserList = function(){
var searchParams = {
//.....
};
List.call(this, User, searchParams);
};
// inherit from List service
UserList.prototype = Object.create(List.prototype);
UserList.prototype.updateUser = function(id) {
//.....
};
return UserList;
}
])
;

Make multiple calls to $resource after the return of another one in AngularJS 1.4

I'm currently trying to find a kind of best practice to make multiple calls to $resource in function of the return of a first one.
Consider the following schema : we have a database of authors, that have books, that have multiples tags (sf,thriller,horror,...).
angular.module('myApp',[ngResource])
.controller('myCtrl',['$scope', function($scope){
$scope.results = [];
apiService.Authors.getAll().$promise //return array of authors
.then(function(authors){
$scope.results.push(authors);
angular.forEach(authors,function(akey,author){
apiService.BooksBy.getAll({authorID:author.id}).$promise //return array of books
.then(function(books){
$scope.results[akey].books = [];
$scope.results[akey].books.push(books);
angular.forEach(books,function(bkey,book){
apiService.Tags.getAll({bookID:book.id}).$promise //return array of tags
.then(function(tags){
$scope.results[akey].book[bkey].tags = [];
$scope.results[akey].book[bkey].tags.push(tags);
});
});
});
});
});
}]);
Is this the only way to make multiple nested calls ?
I was thinking of something more flattened and more readable, something like a workaround with chained promises.
Any ideas would be welcomed, as I'm unable to find how to do this in a better way.
Regards !
Here is one possible solution: If you utilise $q.all, but for each promise you push to the array you add a little 'sub'-.then, you can wrap the result of the each individual api call and pass it along with the key (or whatever else) you require to be consumed by your next .then-handler.
I have tried to fairly closely mirror your original code example, with a few changes:
I've created a little mockApiService, but I've fudged a couple of the calls for simplicity
I've replaced the inline .then-handlers with named functions, to more clearly demonstrate that we've flattened out the chain
I'm using angular.extend to update a json structure on scope, which is being displayed within <pre> tags. Your original code sample was doing something with arrays, but I'm not sure it was going to work as intended - my use of angular.extend is meant to approximate what I thought you were going for.
In my applyBooksGetTags function, we're trying to pass both the akey and the bkey along with the result. However, the akey is actually gotten from the previous result - accessed as result.akey. This exists in scope outside the angular.forEach loop we're using to compile our promise array - and by the time the forEach function executes, result.akey will have been changed by the outside for loop. For this reason, I'm wrapping the call to forEach in a self-executing function - a closure, to ensure the value of result.akey is maintained by the time forEach does its thing.
The angular.forEach calls in the original sample may have swapped the order of the (value, key) params - so have swapped back in the below snippet
There are a lot of } //end blah blah comments... this is mostly because the snippet editor 'tidy' function made the code a little tricky to follow otherwise; sorry!
(function() {
"use strict";
angular.module('myApp', [])
.controller('myCtrl', ['$scope', '$q', 'apiService', MyCtrl])
.service('apiService', ['$timeout', mockApiService]);
function MyCtrl($scope, $q, apiService) {
$scope.results = {};
apiService.Authors.getAll().$promise
.then(applyAuthorsGetBooks)
.then(applyBooksGetTags)
.then(applyTags);
function applyAuthorsGetBooks(authors) {
var promises = [];
angular.extend($scope.results, authors);
angular.forEach(authors, function(author, akey) {
promises.push(apiService.BooksBy.getAll({
authorID: author.id
}).$promise
.then(function(books) {
return $q.when({ // < MAGIC HERE: return the books, but also the aKey
books: books,
akey: akey
}); //end return
}) //end then
); //end push
}); //end foreach
return $q.all(promises);
} // end authorsGetBooks
function applyBooksGetTags(results) {
var promises = [];
for (var i = 0; i < results.length; i++) {
var result = results[i];
$scope.results[result.akey].books = angular.extend({}, result.books);
(function(akey) { //anon func to wrap the current value of akey in a closure, so when inner loop accesses it hasn't been changed by outer loop
angular.forEach(result.books, function(book, bkey) {
promises.push(apiService.Tags.getAll({
bookID: book.id
}).$promise
.then(function(tags) {
return $q.when({ // < MAGIC HERE, again: return the tags, but also the bkey and akey
tags: tags,
akey: akey,
bkey: bkey
}); //end return
}) //end then
); //end push
}); //end foreach
})(result.akey) //pass our current akey value to our anon func
} //end for
return $q.all(promises);
} //end booksGetTags
function applyTags(results) {
for (var i = 0; i < results.length; i++) {
var result = results[i];
$scope.results[result.akey].books[result.bkey].tags = angular.extend({}, result.tags);
} //end for
}
} //end MyCtrl
function mockApiService($timeout) {
function _simulateResource(data) {
return {
$promise: $timeout(function timeoutHandler() {
return data;
}, 1000) //end $timeout
}; //end return
} //end _simulateResource()
return {
Authors: {
getAll: function authorsGetAll() {
return _simulateResource({
Author1: {
id: 'Author 1'
},
Author2: {
id: 'Author 2'
}
}); //end return
} //end getAll
}, //end Authors
BooksBy: {
getAll: function booksByGetAll(params) {
var authorId = params.authorID;
return _simulateResource({
Book1: {
id: 'Book 1 by ' + authorId
},
Book2: {
id: 'Book 2 by ' + authorId
}
}); //end return
} //end getAll
}, //end BooksBy
Tags: {
getAll: function tagsGetAll(params) {
var bookId = params.bookID;
return _simulateResource({
Rom: {
id: 'Rom'
},
Zom: {
id: 'Zom'
},
Com: {
id: 'Com'
}
}); //end return
} //end getAll
} //end Tags
}; //end return
} // end MockService API
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.28/angular.min.js"></script>
<div ng-app="myApp">
<pre ng-controller='myCtrl'>{{results|json}}</pre>
</div>
You can make multiple calls by using $q. Its best way to call multiple calls and it ends if all calls are return promises
If you are calling multiple call based on call on previous result then its best way to do this.
function httpCall()
{
var promises=[];
promises.push(yourhttpCall1())
return $q.all(promises);
}
$q.all([httpCall]).then(function(result){});

AngularJS model best practice

I have been looking at this document:
understanding-service-types
Because I am new to AngularJS I am having some problems understanding everything in there. I still don't understand the difference between a factory and a service, but I will leave that for another day.
The problem I have now, is that I created a model as a factory and now I think I may have done it wrong.
Here is my model:
commonModule.factory('optionsModel', function () {
var _options = angular.fromJson(sessionStorage.siteOptions);
var _defaults = {
rotateBackground: false,
enableMetro: true
};
if (_options) {
_defaults.rotateBackground = _options.rotateBackground;
_defaults.enableMetro = _options.enableMetro;
}
var _save = function (options) {
console.log(options);
sessionStorage.siteOptions = angular.toJson(options);
}
return {
options: _defaults,
save: _save
};
});
As you can see here, what I am doing is setting the defaults and then I check to see if we have anything in our session, if we do I then overwrite our options with the new settings.
I also have a save function which is used to save the options to the session.
Is this the best way to make this model or should I be doing it another way?
I don't think you should think about a model in the way you're doing it.
For your purpose, you can do it in a more "angular" way :
commonModule.factory('optionsModel', function () {
var factory = {
getOptions: getOptions,
saveOptions: saveOptions
}
// If you need default values, you can assign those here,
// but you can also think about adding a dependency into your factory,
// that would be bound to your default settings.
return factory;
function getOptions(){
return angular.fromJson(sessionStorage.siteOptions);
}
function saveOptions(options){
sessionStorage.siteOptions = angular.toJson(options)
}
});

Restangular addElementTransformer / extendModel for ALL routes

How can I addElementTransformer for all models? I want all my models have extra method for delayed save, for example:
Restangular.extendModel('*', function(model){
model.delayed_save = _.debounce(model.save, 1000, {maxWait: 5000})
}
But I need to make it for every route I have.
I know I can create factory for wrapping Restangular service with route parameter like this:
angular.module('app')
.service('restangularService', restangularService)
function restangularService(Restangular) {
var self = this;
self.init = init
function init(route) {
Restangular.extendModel(route, function(model){
model.deferred_save = _.debounce(model.save, 1000, {maxWait: 5 * 1000})
return model
})
return Restangular.service(route)
}
}
and this will work, but I believe there have to be a better way
I needed this to remove the parentResource after saving a nested resource so used setOnElemRestangularized:
RestangularConfigurer.setOnElemRestangularized(function(elem, isCollection, what, Restangular) {
if (!isCollection && elem.fromServer && elem.parentResource) elem.parentResource = null;
return elem;
});
You can use setOnElemRestangularized to achieve this.
Something like this should work (untested)
.config(function(RestangularProvider) {
RestangularProvider.setOnElemRestangularized(function(elem, isCollection, what, Restangular) {
if (isCollection) { return; }
console.log("Path: ", what);
elem.newMethod = function() { console.log("TODO: Add code here..."); };
return elem;
});
})

Recommended way of getting data from the server

What is the recommended way to connect to server data sources in AngularJS without using $resource.
The $resource has many limitations such as:
Not using proper futures
Not being flexible enough
There are cases when $resource may not be appropriate when talking to backend. This shows how to set up $resource like behavior without using resource.
angular.module('myApp').factory('Book', function($http) {
// Book is a class which we can use for retrieving and
// updating data on the server
var Book = function(data) {
angular.extend(this, data);
}
// a static method to retrieve Book by ID
Book.get = function(id) {
return $http.get('/Book/' + id).then(function(response) {
return new Book(response.data);
});
};
// an instance method to create a new Book
Book.prototype.create = function() {
var book = this;
return $http.post('/Book/', book).then(function(response) {
book.id = response.data.id;
return book;
});
}
return Book;
});
Then inside your controller you can:
var AppController = function(Book) {
// to create a Book
var book = new Book();
book.name = 'AngularJS in nutshell';
book.create();
// to retrieve a book
var bookPromise = Book.get(123);
bookPromise.then(function(b) {
book = b;
});
};
I recommend that you use $resource.
It may support (url override) in next version of Angularjs.
Then you will be able to code like this:
// need to register as a serviceName
$resource('/user/:userId', {userId:'#id'}, {
'customActionName': {
url:'/user/someURI'
method:'GET',
params: {
param1: '....',
param2: '....',
}
},
....
});
And return callbacks can be handled in ctrl scope like this.
// ctrl scope
serviceName.customActionName ({
paramName:'param',
...
},
function (resp) {
//handle return callback
},
function (error) {
//handler error callback
});
Probably you can handle code on higher abstraction level.

Resources