How do you resolve circular dependencies in Angular - angularjs

I have a NewsService and PersonService and a model for Person and for NewsItem. The following is a simplified representation excluding services.
//angular service to expose Person
var Person = function(data) {
angular.copy(data, this);
//some other initialisation
}
Person.prototype.getNews = function() {
//fetch news
.then(function(newsItems) {
return newsItems.map(function(newsItemData){
newsItemData.author = new Person(newsItemData.author);
return new NewsItem(newsItemData);
});
});
}
//Angular service to expose NewsItem
var NewsItem = function(data) {
angular.copy(data, this);
//some other initialisation
}
NewsItem.prototype.getComments = function() {
//fetch comments
.then(function(commentsData){
return comments.map(function(commentData) {
commentData.author = new Person(commentData.author);
return new Comment(commentData);
});
});
}
//Angular services that reference these objects
NewsService
PersonService
NewsItem and Person reference each other because the data fetched from their respective APIs includes data that should be instantiated by the other object. There are numerous pairs of services and models referencing each other and I have a problem with circular dependencies forcing me to instantiate objects elsewhere, which I want to avoid. I can't think of an elegant solution and could really use your help.

Related

Calling an Angular Factory recursively gives a circular dependency error

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.

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;
}
])
;

Backbone/Underscore _.each(...) doesn't seem to work according to docs

In this code...
_.each(this.photos, function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
element is the entire this.photos collection. Why is not just one photo element of the 10 in the collection?
EDIT: Here is my method that populates the photos collection....
loadPhotos: function(memberId) {
var self = this;
this.photos = new PhotosCollection([]);
this.photos.on('error', this.eventSyncError, this);
this.photos.fetch({
url: this.photos.urlByMember + memberId,
success: function(collection,response,options) {
console.log('Fetch photos success!');
self.render();
}
});
},
The collection loads with models just fine. In the Chrome console, I can see the collection of models. I'm not sure what's wrong. I cannot iterate the collection with any of the methods recommended by posters below.
You are using the _.each method incorrectly. The underscore methods needs to called directly on the collection:
this.photos.each(function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
Or you if want to use the _.each from you need to pass in the models property and not the collection object itself as the list:
_.each(this.photos.models, function(element,index,list) {
console.log('element...');
console.log(element);
var photoView = new PhotoView({photo:element});
self.$el.append(photoView.render());
});
One should use this.photos.each(function(elt, index, list){...}) instead of _.each(this.photos,...) because this.photos is not an underscorejs _.chain object.
Thank you for your suggestions! I would never have figured this out without all your advice above. So here was the problem...
In the parent view, this loads up photo records for a particular member...
loadPhotos: function(memberId) {
var self = this;
this.photos = new PhotosCollection([]);
this.photos.on('error',this.eventSyncError,this);
this.photos.fetch({
url: this.photos.urlByMember + memberId,
success: function(collection,response,options) {
self.render();
}
});
},
Still in the parent view, Backbone.Subviews uses this to call each child view when it renders. Note how I'm passing this.photos to the subvw-photos...
subviewCreators: {
"subvw-profile": function() {
var options = {member: this.member};
// do any logic required to create initialization options, etc.,
// then instantiate and return new subview object
return new ProfileView( options );
},
"subvw-photos": function() {
var options = {photos: this.photos};
return new PhotosView( options );
},
"subvw-comments": function() {
var options = {};
return new CommentsView( options );
}
},
This is in the subvw-photos child view. Note how the intialize is accepting the collection as a parameter. See this problem?...
initialize: function(photos) {
Backbone.Courier.add(this);
this.photos = photos;
},
render: function() {
console.log('rendering photosview now...');
var self = this;
this.photos.each(function(element,index,list) {
var photoView = new PhotoView({photo:element});
$(self.el).append(photoView.render());
});
return this;
},
I was passing an object wrapping the photos collection in to initalize but then treating it like it was just a ref to the photos collection. I had to change the subvw-photos initialize to the following...
initialize: function(args) {
Backbone.Courier.add(this);
this.photos = args.photos;
},
Then of course all the other code magically began working :-/
Thank you again for your tips! You definitely kept me on track :-)

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.

How to trigger the fetch method and how to set the url

I'am redesigning my backbone application based on the answer of #20100 to this question The best way to fetch and render a collection for a given object_id.
Please read the comment on the code because I think is more clear, and my question looks better in smaller sizes.
// My View
define([
"js/collections/myCollection",
"js/models/myFeed"
], function (MyCollection, MyModel) {
var MyView = Backbone.View.extend({
tagName: 'ul',
initialize: function () {
this.collection = new MyCollection();
this.collection.on('add', this.onAddOne, this);
this.collection.on('reset', this.onAddAll, this);
// when I make myView = new MyView(_.extend( {el:this.$("#myView")} , this.options));
// myView.render is not called
// in order to trigger the render function I make the following… but probably there is a better way …
var that = this;
this.collection.fetch({
success: function () {
that.render();
}
});
}
});
return MyView;
});
// MyCollection
define([
"js/models/myModel"
], function (MyModel) {
var MyCollection = Backbone.MyCollection.extend({
model: MyModel, // add this
url: function () {
var url = "http://localhost/movies";
return url;
// if I look to the GET request the url is without idAttribute
// how can I attach the idAttribute to this url?
// should bb takes care of this?
}
});
return MyCollection;
});
//MyModel
define([
], function () {
var MyModel = Backbone.MyModel.extend({
idAttribute: 'object_id'
});
return MyModel
});
There's two paths you want to explore
Pre-populate your collection with your model data
In your example you're already doing this, but you're fetching a collection, the collection URL is http://localhost/movies, if you want an individual model take a look at the next point
Fetch each individual model only when you need it
In the assumption that you're trying to get an ID on a collection that is not pre-populated and are loading 1 model at a time, you will have to approach this a bit in a custom way by adding a method to your collection somewhat similarly to this
getOrFetch: function(id, options)
{
var model;
if (this.get(id))
{
model = this.get(id);
}
else
{
model = new this.model({
id: id
});
this.add(model);
model.fetch(options);
}
return model;
}
or add the function as Backbone.Collection.prototype.getOrFetch so you can use it on every Backbone Collection if you need it.

Resources