As i was watching this tutorial video, i noticed the following in FlightServices.js:
return {
getDeparture : function( user )
{
var dfd = $q.defer();
// Mock departure information for the user's flight
dfd.resolve({
userID : user.email,
flightID : "UA_343223",
date : "01/14/2014 8:00 AM"
});
return dfd.promise;
},
getFlight : function( flightID )
{
return $q.resolve ({
id : flightID,
pilot : "Captain Morgan",
plane : {
make : "Boeing 747 RC",
model : "TA-889"
},
status: "onTime"
});
}
};
As you can see, an instance of $q.defer() is used as the return value on getDeparture(), differing from getFlight, where $q.resolve() is directly returned.
Are there any (dis-)advantages in the latter? Or is this just a shorthand notation?
I have never seen this and would have thought that i need an instance of defer() in any case.
The second syntax actually isn't valid syntax with vanilla AngularJS.
The author used a concept called decorators to add this functionality to the $q service:
https://github.com/ThomasBurleson/angularjs-FlightDashboard/blob/master/lib/%24QDecorator.js
If you're not familiar with decorators, there's a good video that explains them on egghead.io:
https://egghead.io/lessons/angularjs-provide-decorator
Related
I'm using Restangular to make my API call for a single object, like this:
$scope.box = { name : "box_a" , id : 1 };
Restangular.all('boxes/')
.post($scope.box)
.then(function(){
...
});
But now, the user can select multiple boxes to add at once. So, I want to post multiple objects to my API, but I need to wait for each request until it's done, or my database will 'lock'...
My quick-add objects are the following:
$scope.boxes = [
{ name : "box_a" , id : 1 },
{ name : "box_b" , id : 2 },
{ name : "box_c" , id : 3 }
]
How can I create a promise chain by looping through my $scope.boxes? I can't quite figure out how to create an array of promises with Restangular...
I don't know much about restangular, but you could chain those requests with a reduce function like:
$scope.boxes.reduce(function(promise, newBox){
return promise.then(function(){
return Restangular.all('boxes/')
.post(newBox)
.then(function(){
...
});
});
}, $q.resolve());
I've made a fiddle (wihout restangular, just a post call) and it seems to work.
I inherited an Angular application that calls a Web API service. I am trying to keep the existing structure as close to the original as possible. A new requirement is to pass in a number of days, and I have modified the call:
(function () {
'use strict';
angular
.module('myApp')
.factory('summaryService', ['$resource', summaryService]);
function summaryService($resource) {
return $resource('/api/summary/?days=:days', { days: '#days' }, {
getUserSummary: { method: 'POST' }
});
}
}());
The code that calls the getUserSummary service is below. I have a drop down list that is populated with days at load time. The updateByDays function is what is called when the days DDL is changed. This fires correctly, and the "daysToReport" parameter is being updated to the correct value.
summaryService.getUserSummary({ Name: currentUser, days: 21 },
function (value) {
$scope.daysList = [{
name: '30 Days',
value: '30'
}, {
name: '60 Days',
value: '60'
}];
$scope.updateByDays = function (daysToReport) {
$scope.days = days;
...
};
},
function (httpResponse) {
...
}
);
As it is now, the call to the Web API method is successfully made, but it always has a "days" parameter value of 21", which is expected - it's the hard-coded value in the parameter list. Somehow, when the updateByDays method fires, I need to update the "days" parameter so the call to the Web API is made correctly. How can this be accomplished?
Thank you,
Scott
Pay attention on what are you trying to call resource with. Look at this docs: AngularJS $resource.
So, you call POST resource. You have to call resource functions with this params:
summaryService.getUserSummary(
{getParams},
{postParams},
function successCallback(){},
function errorCallback() {}
);
So, your call will be:
summaryService.getUserSummary(
{days: selectedDaysValue},
{},
function onSuccess() {}
);
Note to selectedDaysValue. It is model from ngModel directive of your select in html. It must be dynamic. You send 21 days all time because you call resource with static object { Name: currentUser, days: 21 }. Also, notice that Name will be added to query like &Name=currentUserValue.
Also, you want to use query in your request. You don't need to specify query in your resource parameters.
Resource definition will be:
return $resource('/api/summary');
Resource call:
myResource.get({days: 21}, function onSuccess() {});
Actual request will be: /api/summary?days=21
I probably didn't explain all the various pieces of the puzzle, but I tried the solution, and for my situation, it wasn't working. So I ended up cheating and using plain JavaScript.
In the method, I found the Days DDL and set the selected option to the matching text value:
$scope.updateByDays = function (days) {
var desiredValue = days.name;
var el = document.getElementById("Days");
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].text == desiredValue) {
el.selectedIndex = i;
break;
}
}
There goes my "Coder of the Year" award.
This is a part of my schema:
var IdeaSchema = new Schema({
ratings: [{rater: String, star: Number}]
And i am trying to add a rating to the ratings array like this:
$http.put('/api/ideas/' + idea._id, {ratings: [{rater: 'John', star: 5}] });
After the first put, the newer ratings overwrite the array, and i always have the latest rating in it. I tried the suggested extend (this is the main issue i think) instead of merge but it does not seem to work.. I am not sure if i call the api with the correct syntax at all regarding the parameter ratings. And my update method is the yeoman (generator-angular-fullstack) generated one:
exports.update = function(req, res) {
...
var updated = _.merge(idea, req.body);
updated.save(function (err) {
..
return res.json(200, idea);
});
Edit:
I have made it by changing the api with this one:
$http.put('/api/ideas/' + idea._id + '/ratings', {rater: 'John', star: 5} );
I can now tell exactly what i want to update. So it is the ratings array of my idea. And a new method in controller:
router.put('/:id/ratings', auth.isAuthenticated(), controller.addRating);
exports.addRating = function(req, res) {
...
idea.ratings.push(req.body);
idea.save(function (err) {
...
});
};
I am suspicious if this would be the best approach to achieve this anyway. Maybe a better api or a better function would handle this with the generated classic update method.
I'll do something like this.
return Promise.cast(Idea.findOne({_id: idea._id, 'ratings.rater': rating.name}).exec())
.then(function(d){
if (d) {
return Promise.cast(Idea.update({_id: idea._id, 'ratings.rater': rating.name}, {$set : {'ratings.$' : rating}}).exec());
} else {
return Promise.cast(Idea.update({_id: idea._id, 'ratings.rater': {$nin : [rating.name]}}, {$push : {ratings : rating}}).exec());
}
});
So it will search if John's rating is already in there and do $push or $set correspondingly.
I'm building an app, that is backed with node-mysql combo, and angularjs on the frontend part. The backend REST service is ready, but I'm struggling with modeling my relational data. There are some questions regarding this like : $resource relations in Angular.js or $resource relations in Angular.js [updated] . Are those approaches still the best approaches, or were there any significant changes in $resource ? Or maybe Restangular is the way to go?
Here is my technique:
I declare a factory called dataService, which is a wrapper around Restangular, extended with some other features.
First let me gave some code and then explain:
.factory('identityMap',
var identityMap = {};
return {
insert: function(className, object) {
if (object) {
var mappedObject;
if (identityMap[className]) {
mappedObject = identityMap[className][object.id];
if (mappedObject) {
extend(mappedObject, object);
} else {
identityMap[className][object.id] = object;
mappedObject = object;
}
} else {
identityMap[className] = {};
identityMap[className][object.id] = object;
mappedObject = object;
}
return mappedObject;
}
},
remove: function(className, object) {
if (identityMap[className] && identityMap[className][id]) delete identityMap[className][id];
},
get: function(className, id) {
return identityMap[className] && identityMap[className][id] ? identityMap[className][id] : null;
},
flush: function(){
identityMap = {};
}
};
}
.factory('modelService', ['Restangular', 'identityMap', '$rootScope', '$log', function(Restangular, identityMap, $rootScope, $log) {
var ENUM1 = {STATE:0, OTHER_STATE:1, OTHER_STATE2: 2},
ENUM2 = {OK:0, ERROR:1, UNKNOWN:2};
function extendModel(obj, modelExtension, modelName){
angular.extend(obj, modelExtension);
obj.initExtension();
obj = identityMap.insert(modelName, obj);
}
function broadcastRestEvent(resourceName, operation, data){
$rootScope.$broadcast(resourceName + $filter('capitalize')(operation), data);
}
var resource1Extension = {
_extensionFunction1: function() {
// ... do something internally ...
if (this.something){
// this.newValue ....
;
}
else {
// ....;
}
},
publicExtensionFunction: function(param1) {
// return something
},
initExtension: function() {
this._extensionFunction2();
extendModel(this.resource2, resource2Extension, 'resource2');
}
};
var resorce2Extension = {
_extensionFunction1: function() {
// do something internally
},
publicExtensionFunction = function(param1) {
// return something
},
initExtension: function(){
this._extensionFunction1;
}
};
var modelExtensions = {
'resource1': resource1Extension,
'resource2': resorce2Extension
};
var rest = Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('/api');
RestangularConfigurer.setOnElemRestangularized(function(obj, isCollection, what, Restangular){
if (!isCollection) {
if (modelExtensions.hasOwnProperty(what)) {
extendModel(obj, modelExtensions[what], what);
}
else {
identityMap.insert(what, obj);
}
if (obj.metadata && obj.metadata.operation) {
broadcastRestEvent(what, obj.metadata.operation, obj);
}
}
return obj;
});
RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
var newData;
if (operation === 'getList') {
newData = data.objects;
newData.metadata = {
numResults: data.num_results,
page: data.page,
totalPages: data.total_pages,
operation: operation
};
data = newData;
}
else if (operation === 'remove') {
var splittedUrl = url.split('/');
var id = splittedUrl.pop();
var resource = splittedUrl.pop();
identityMap.remove(resource, id);
broadcastRestEvent(resource, operation, id);
}
else {
data.metadata = {operation: operation};
}
return data;
});
});
return {
rest: rest,
enums: {
ENUM1: ENUM1,
ENUM2: ENUM2
},
flush: identityMap.flush,
get: identityMap.get
}
}]);
1) Let me explain identityMap (it's the code from this blog post with some extended features):
Let's consider a REST model which looks like this (each resource represents a database table):
resource 1:
id = Integer
field1 = String
field2 = String
resource2s = [] (List of resources2 which points to this resource with their foreign key)
resource 2:
id = Integer
field1 = String
field2 = String
...
resource1_idfk = Foreign Key to resource 1
Resource API is so smart that it returns resource1 relationships with resources2 with GET /api/resource1/1 to save the overhead that you would get with GET to resource2 with some query parameters to resource1_idfk...
The problem is that if your app is doing the GET to resource1 and then somewhere later GET to resource2 and edits the resource2, the object representing the resource2 which is nested in resource1 would not know about the change (because it is not the same Javascript object reference)
The identity map solves this issue, so you hold only one reference to each resource's instance
So, for example, when you are doing an update in your controller the values automatically updates in the other object where this resource is nested
The drawback is that you have to do memory management yourself and flush the identity map content when you no longer need it. I personally use Angular Router UI, and define this in a controller which is the root of other nested states:
$scope.$on("$destroy", function() {
modelService.flush();
});
The other approach I use within the Angular Router UI is that I give the id of the resource which i want to edit/delete within that controller as the parameter of nested state and within the nested state i use:
$scope.resource1instance = modelService.get('resource1', $stateParams.id);
You can than use
resource1.put(...).then(
function(){
// you don't need to edit resource1 in list of resources1
$state.go('^');
}
function(error){
handleError(error);
});
2) When I need to use some new functionality over resources I use `Restangular's setOnElemRestangularized. I think the code above is self explanatory and very similar to the one mentioned in blog post I have mentioned above. My approach is slightly different from the one in that post, that I don't use the mixin initialization before, but after I mix it to the object, so one could reference the new functions in initializer. The other thing I don't use, for example, he creates single factory for every resource, for example Proposal for extended logic and the other factory ProposalSvc for manipulating the instances. For me that's a lot of code you don't have to write and personally I think that Javascript is not suited very well for this object oriented approach, so I return just the whole Restangular object and do operations with it.
3) Another thing I have there is the broadcast of events when something in my model changes with Restangular, this is something I needed when I used ng-table. For example, when the model changed and rows in my table needed to be updated to reference the changes, so in the controller which manages the table I use $scope.on('eventName') and then change appropriate row. These events are also great when you have a multiuser live application and you use websockets for server notifications (code not included here in modelService). For example somebody deletes something in a database, so the server sends a notification to everyone who is alive through websocket about the change, you then broadcast the same event as used in Restangular and the controller does the same edits on its data.
This blog post should help you make your choice http://sauceio.com/index.php/2014/07/angularjs-data-models-http-vs-resource-vs-restangular/
I agree that there are a lot of good practices using http headers in Restangular, but you can pick them in the source and use them directly.
What you have to wonder is, will you be able to wrap your nested resources within a $resource and make instance calls while modifying the parent object. And that's not a given.
Your question seems to be asking whether you should be using ngResource, Restangular or some other framework or drop down to the low-level and use $http directly.
$resource is still widely used because it's included in the official docs and in all the popular tutorials and articles but Restangular is fairly popular.
The website ngModules shows a listing of REST API modules for AngularJS.
If you have a simple REST API, go with $resource for now and then switch to Restangular if you're doing lots of custom coding and filtering. It is a much nicer framework and more extensible.
Having some issues with pulling calendar events from Google Calendar using Backbone.
When I call collection.fetch() I am only getting a length of 1 returned, when there are 13 objects in the json.
I had a look at the parse:function(response) method that I am overriding in the Collection, and it is returning all 13 objects. I had a look at the add method in backbone.js, and the issue appears to occur on line 591:
models = _.isArray(models) ? models.slice() : [models];
When I wrap the line with console.log to check the status of the models variable:
console.log(models);
models = _.isArray(models) ? models.slice() : [models];
console.log(models);
I get the following result:
[Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object,Object] backbone.js:590
[child,undefined × 12]
I'm at a loss to explain why it would be failing on add. I have checked each model by changing the parse:function(response) method in the collection to return each object, and it works fine.:
parse: function(response) {
return response.feed.entry[5];
}
I have successfully parsed Google Calendar feeds with Backbone.js before, so I fear I am missing something really simple.
If I console.log response.feed the following is returned:
This is the full class:
/**
* Backbone
* #class
*/
var Gigs = Gigs || {};
Gigs.Backbone = {}
Gigs.Backbone.Model = Backbone.Model.extend();
Gigs.Backbone.Collection = Backbone.Collection.extend({
model: Gigs.Backbone.Model,
url: 'http://www.google.com/calendar/feeds/email#email.com/public/full?alt=json-in-script&orderby=starttime&callback=?',
sync: function(method, model, options) {
options.dataType = "jsonp";
return Backbone.sync(method, model, options);
},
parse: function(response) {
return response.feed.entry;
}
});
Gigs.Backbone.Controller = Backbone.View.extend({
initialize: function() {
var self = this;
this.collection = new Gigs.Backbone.Collection();
this.collection.on('reset', this.addElements, this);
this.collection.fetch();
},
addElements: function() {
log(this.collection);
}
});
var backbone = new Gigs.Backbone.Controller();
Apparently, Google Calendar provides its entries with an id wrapped in an object 1:
"id":{
"$t":"http://www.google.com/calendar/feeds/..."
}
which Backbone seems to dislike. A lot.
One simple solution would be to overwrite the id in your parse method:
parse: function(response) {
var entries=[];
_.each(response.feed.entry, function(entry,ix) {
entry.id=entry.id.$t;
entries.push(entry);
});
return entries;
}
And a Fiddle http://jsfiddle.net/bqzkT/
1 Check https://developers.google.com/gdata/docs/json to see how Google converts its XML data to JSON.
Edit : the problem comes from the way the data is returned with a straight XML to JSON conversion (requested via the alt=json-in-script parameter) wrapping the attributes in objects. Changing this parameter to alt=jsonc yields a much simpler JSON representation. Compare a jsonc output to the json-in-script equivalent.
See https://developers.google.com/youtube/2.0/developers_guide_jsonc#Comparing_JSON_and_JSONC for more info