When you have a JSON $resource how can you cast the resulting objects into more specific objects once obtained?
For example, right now they come back as an Array of Objects, but I want them to come back as an Array of "Appointment" objects, so that I can have some methods on that Appointment object which would answer questions about that Appointment object. Ex: Does This Appointment have any services associated with it? Is This appointment in the morning or afternoon?
At first I thought transformResponse hook would work from ngResource, but that doesn't appear to work. The return from that is not the actual objects. Seems that with that function you can only modify the actual data prior to the JSON parsing.
Finally, I question if this is even a proper angularJS technique? Or should these helper methods just show up in a controller or some other module and accept the object to work upon? I just think its cleaner to have them wrapped up in the object, but I admit that I'm not very experienced in angularJS.
If you're using a factory and want to add a function you can for example add the function to the prototype of the returned item (DEMO):
app.factory('Appointment', ['$resource', function($resource) {
var Item = $resource('appointments.json',{/*bindings*/},{/*actions*/});
Item.prototype.hasServices = function() {
if(this.services.length > 0) return true;
else return false;
};
Item.prototype.partOfDay = function() {
if(this.time.split(':')[0] > 12) return "afternoon";
else return "morning";
};
return Item;
}]);
And then access it on your resource in the controller:
$scope.appointments = Appointment.query({}, function() {
console.log($scope.appointments[0].partOfDay())
});
Or directly in the view inside for example an ng-repeat:
{{appointment.partOfDay()}}
To answer your last question, I think the above solution is a proper angularjs technique.
As soon as you have functions associated with a specific resource type it's in my opinion the best to directly append them to the respective resource object. Why should you create helper functions in the controller when you have to pass the resource as a parameter and additionaly the functions may be used in multiple controllers or scopes?!
Related
This question already has answers here:
Why use getters and setters/accessors?
(37 answers)
Closed 3 years ago.
I'm learning how to store API call response data in an AngularJS service. In all the examples that I saw, people used functions in the service to get or set values.
app.factory('dataFactory', function() {
let dataFactory = {};
let info;
dataFactory.setInfo = function(value){
info = value;
}
dataFactory.getInfo = function(){
return info;
}
return dataFactory;
});
But I realized that I could get and set values of variables in a service without the use of any functions.
app.factory('dataFactory', function() {
let dataFactory = {};
let dataFactory.info;
});
// Now I can get or set the value of this in my controller
app.controller('myCtrl', [dataFactory, function(dataFactory) {
dataFactory.info = "Value"; // setting the value
let test = dataFactory.info; // getting the value
}])
I would like to know if my approach could potentially lead to any problems. Is it considered a bad practice and if so why?
Preference to data accessors (getters and setters) over exposing a property directly is neither specific to AngularJS nor to JavaScript. Is generally a common practice in the object oriented programming.
One of the main reasons to prefer getters and setters over the direct access to a property is data encapsulation. When the data is defined as a local variable in the lexical environment of the service function (let info;) it is not possible to access it from outside (for example from controller).
Data accessors also give you a flexibility to add data access logic. For example you may want to implement some checks when the getter is called and make a decisions whether to return the data or not. Likewise, in the setter you may verify that the data (the setter was called with) meets the requirements and throw an error if it's not.
Further you may want to check this answer to see many other benefits getters and setters may provide.
When I use this it works:`
angular.module('app').service('DataService', function() {
return {theme: "amelia"}
});
But when I use this, there is no update? Can you tell me the difference?
angular.module('app').service('DataService', function() {
return {
theme: function() {
return {theme: "amelia"}
}
};
});
Controller
$scope.settings = DataService.theme();
Jade
select.form-control(ng-model="settings.theme", ng-options="theme for theme in themes")
Is it possible to get the second way working? Because I will share more data then one Object!
Thank you!
The first version of the code calls the function once to instantiate the service. After that, because services are singletons in angular the function isn't called again, but rather the return value (a "static" object) is accessed in every controller that uses the service after that.
The second version, each controller you inject the service into calls the theme function, which instantiates a brand new object each time. You have now effectively mitigated the fact that the service is a singleton. This is why data will not be shared with the second set of code.
If you put a break point on the function call in each case and run your code you should see the first version called once while the second version will be called many times.
"Get It Working"...
You can't really make it work with a function call but if you need to share multiple data objects there isn't any reason not to nest them. You could very easily do something like:
angular.module('app').service('DataService', function() {
return {
dataObjects: [
{"type":"theme", "theme":"amelia"},
{"type":"user", "id":123, "name":"ABC"}
]};
});
In the example I added a second object which is a user object to make shared "dataObjects" array. To find a specific object in the "dataObjects" array, you could loop till you find the correct type ("theme", for example). If necessary, you could even nest one level deeper if you needed the objects to be pristine (without the added type attribute).
Hope that helps!
It should be theme: function().... inside your service. Replace "=" with ":".
I've been playing with AngularFire, and I understand the documentation for collections. But I feel like I'm totally missing things when it comes to loading specific items inside the collection, by anything besides position in the array.
All of the examples in the Firebase data have pretty names for the api like user/name/first
But when I use angularFireCollection to save a collection I get my object inside a unique $id. (not as pretty)
Is that the expected behavior? And if so, how would I get() an item based on a value instead?
ex. I created a key called slug. That has 'my-theme' in the collection. And I want to load it by $routeParams.
.when('/themes/:slug/', {
templateUrl: 'views/theme.html',
controller: 'ThemesCtrl'
})
How would I load an object into themes/my-theme instead of themes/-J50neNBViK9l7P4QAYc
Thanks in advance...
angularFireCollection automatically creates a list of items with auto-generated incremental IDs (generated by Firebase's push() method). If you want to create a list of items with custom names, angularFire might be a better service to use (it uses set instead of push). For example:
function ThemesCtrl($scope, angularFire) {
$scope.themes = {};
angularFire(new Firebase(URL), $scope, 'themes');
$scope.addTheme = function() {
$scope.themes["my-theme"] = $scope.currentTheme;
}
}
I have a few models that don't just contain basic data attributes, but they might have one or two attributes that hold another models object.
This has been okay, but now I want to call
myRootModel.toJSON()
and I've noticed that it doesn't call .toJSON on the other models in my model that I'm trying to call toJSON() on.
Is there a way to override backbone model .toJSON to go through all fields, recursively, whether they are basic attributes, sub-models or collections? If not, can I override toJSON in each model / collection?
I'm aware of backbone-relational, but I don't want to go that route - I'm not using fetch/save, instead our API returns responses that I adjust in the models parse function and simply invoke new MyRootModel(data,{parse:true}).
Here's a way you can achieve such a thing (there's maybe another way):
Backbone.Model.prototype.toJSON = function() {
var json = _.clone(this.attributes);
for(var attr in json) {
if((json[attr] instanceof Backbone.Model) || (json[attr] instanceof Backbone.Collection)) {
json[attr] = json[attr].toJSON();
}
}
return json;
};
http://jsfiddle.net/2Asjc/.
Calling JSON.parse(JSON.stringify(model)) parses the model with all the sub-models and sub-collections recursively. Tried on Backbone version 1.2.3.
I'm using angular routing for a SPA with a sidebar (on index.html) that loads a list of categories from a categoryListController, which has a categoryData $resource service injected for retrieving the category list.
Then i have a template, addCategory.html which adds a category with the help of a addCategoryController, which also uses categoryData $resource service.
$scope.categories = categoryData.query(); //categoryListController (for sidebar)
categoryData.save(newCategory) // on addCategoryController (for addCategory.html)
The problem is, the sidebar won't update unless I refresh the entire page. I'm thinking i've got to somehow tell the categoryListController to refresh, but i'm not sure how to do that. I can do $scope.categories.push(newCategory) right after categoryData.save(newCategory), and get the new category showing immediately on addCategory.html, but i don't think that's the answer for my sidebar, unless this is something that needs to be handled with $rootscope? I'm not sure. Thanks
One of the approach that you can take here to update the list of categories in categoryListController would be to use $rootScope to broadcast message detailing the category added.
Catch this message in the list controller to either fetch the list again from server or use the newly added item send using the broadcast message to the list.
Something like this in the Add controller
$rootScope.$broadcast('categoryAdded', { 'category': newcategoryObject });
Something like this in list controller
$scope.$on('categoryAdded', function (event, args) {
$scope.categories.push(args.category);
});
You can inject $rootScope as a dependency into the controller.
You can do a similar thing by creating a CategoryList service too. Since service are singleton by nature and can be shared across controllers, using the service approach you would define a CategoryList service with methods to get and `add' categories and bind to data returned by this service.
You should create a service that share the data structure and care of managing the content.
Something like this:
angular.service('categoryService', function() {
var categories = [], initilized;
return {
this.getCategories = function() {
if (!initialized) {
// call resource to fulfill the categories array
initialized = true;
}
// you cold return a promise that would be resolved as soon
// as you get the first response from server
return categories;
});
this.addCategory = function(category) {
// code to call $resource, add the category and update the
// categories array, shared between both controllers
//
// you could return the promise for adding the content
});
this.removeCategory = ...
};
});
You wouldn't need to even call $resource, this service would care of any need of persisting. Of course, you might change and add more method if you need to expose the promises.