Angular Facebook service hits API call limit - javascript

I am trying to call my Facebook service within a ng-repeat but somehow the Facebook API call limit is hit very quickly.
I have a service as so:
angular.module('core').factory('Facebook', ['$q',
function ($q) {
return {
getMutualFriends: function (fbUserId) {
var deferred = $q.defer();
var path = "/" + fbUserId;
FB.api(
path,
{
'fields': 'context.fields(mutual_friends)'
},
function (response) {
if (!response || response.error) {
deferred.reject('Error occured');
} else {
deferred.resolve(response);
}
}
);
return deferred.promise;
}
};
}
]);
And within my controller, I have a function that calls the service:
$scope.getMutualFriendsCount = function (fbUserId) {
if (fbUserId === '' || fbUserId === undefined) return 0;
Facebook.getMutualFriends(fbUserId)
.then(function (response) {
// untested response
return response.context['mutual_friends']['summary']['total_count'];
});
}
In my template, I have data-ng-repeat="profile in profiles" and for each profile, I try to bind the results data-ng-bind=getMutualFriends(profile.fbId).
The service manages to communicate with the FB servers until I start noticing that there are way too many calls during the loop and the call limit is hit very quickly (within 1 or 2 refreshes on my dev machine on page of 20 profiles only). Does anyone have any idea on how I could better design the approach to obtain mutual friends for multiple ids?

You shouldn't call a function that makes an HTTP request from a watched expression, whether it's ng-bind or the equivalent {{ }}. This expression, and the subsequent HTTP call` will get called on every digest cycle - clearly not what you expect or need.
Instead, get the data that you need and store it, for example, with each profile, and access that value from within the ng-repeat.
Also, as per the comment above, you should consider calling Facebook in a batch. Create a separate service to handle all of this complexity and expose a simple API to the controller.

Related

Angular ngResource $save Method Clears $resource Object

Using Angular 1.5.5 here:
Is there any way to tell Angular to ignore response body for particular requests (such as $save)? It drives me crazy that after I call $save, angular updates the model with the object returned by a server, which initially was supposed to be used to distinguish between different resolutions of the request. It results in unwanted form clear. Interestingly enough, this behaviour remains even if I send a 400 or 500 http status code.
In case you need more info, relevant code is below.
Controller:
'use strict';
angular
.module('app.operators')
.controller('OperatorNewController', OperatorNewController);
OperatorNewController.$inject = ['operatorsService', 'notify'];
function OperatorNewController(operatorsService, notify) {
var vm = this;
vm.done = done;
activate();
function activate() {
vm.operator = new operatorsService();
}
function done(form) {
if (form.$invalid) {
// do stuff
return false;
}
vm.operator.$save(function(response) {
if (response.success && response._id) {
$state.go('app.operators.details', {id: response._id}, { reload: true });
} else if (response.inactive) {
// do stuff
} else {
// do other stuff
}
}, function (error) {
// do other stuff
});
}
}
Service:
'use strict';
angular
.module('app.operators')
.service('operatorsService', operatorsService);
operatorsService.$inject = ['$resource'];
function operatorsService($resource) {
return $resource('/operators/:id/', {id: '#_id'}, {
'update': { method: 'PUT' }
});
}
Server request handler is also fairly simple:
.post('/', function (req, res) {
if (!req.operator.active) {
return res.status(500).json({ inactive: true, success: false });
}
// do stuff
return res.json({ success: true });
});
In either way I don't like the idea of having to send the entire object from server (particularily when it's a failed request), and even if I have to, I still need a way to send some extra data that will be ignored by Angular.
Your help is very much appreciated!
The $save method of the resource object empties and replaces the object with the results of the XHR POST results. To avoid this, use the .save method of the operatorsService:
//vm.operator.$save(function(response) {
vm.newOperator = operatorsService.save(vm.operator, function(response),
if (response.success && response._id) {
$state.go('app.operators.details', {id: response._id}, { reload: true });
} else if (response.inactive) {
// do stuff
} else {
// do other stuff
}
}, function (error) {
// do other stuff
});
UPDATE
It results in unwanted form clear. Interestingly enough, this behaviour remains even if I send a 400 or 500 http status code.
This behavior is NOT VERIFIED.
I created a PLNKR to attempt to verify this behavior and found that the $save method does not replace the resource object if the server returns a status of 400 or 500. However it does empty and replace the resource object if the XHR status code is 200 (OK).
The DEMO on PLNKR
It drives me crazy that after I call $save, angular updates the model with the object returned by a server
It helps to understand how browsers handle traditional submits from forms.
The default operation for a submit button uses method=get. The browser appends the form inputs to the URL as query parameters and executes an HTTP GET operation with that URL. The browser then clears the window or frame and loads the results from the server.
The default operation for method=post is to serializes the inputs and place them in the body of an HTTP POST. The browser then clears the window or frame and loads the results from the server.
In AngularJS the form directive cancels the browser default operation and executes the Angular Expression set by either the ng-submit or ng-click directive. All $resource instance methods including $get and $save, empty and replace the resource object with XHR results from the server if the XHR is successful. This is consistent with the way browsers traditionally handle forms.
In RESTful APIs, HTTP GET operations return the state of a server resource without changing it. HTTP POST operations add a new resource state to the server. APIs usually return the new resource state, with additional information such as ID, Location, timestamps, etc. Some RESTful APIs return a redirect (status 302 or 303) in which case browsers transparently do an HTTP GET using the new location. (This helps to Solve the Double Submission Problem.)
When designing RESTful APIs, it is important to understand how traditional browsers behave and the expectations of RESTful clients such as AngularJS ngResource.

Initialize Current User Service on Application Start in AngularJS

I’m developing a Single Page Application with AngularJS.
When a user successfully logs in, a security token is stored in a cookie. Now, when he refreshes the page, the token will be sent to the backend, which returns a JSON object "currentUser" containing all the relevant information about the current user (as name, access-groups, profile picture, etc.).
The problem is, this is an asynchronous process of course, so when the controller starts another operation, say, just alerting the user’s name, this value will be undefined at that time.
Of course, I could set a timeout but is there a better solution?
I thought about a "currentUserService", which initializes first (sending the cookie and filling the user information with the backend response) and can only be processed after this initialization is completed.
But how can this be done? Or are there any other possibilities?
edit:
Hi guys,
thanks for the input!
Both of your suggestions seem to be very promising for asynchronous requests in general, but I think they might not fit perfectly for my concern:
The information about the current user only have to be requested once, so I would like to store them for the whole application (e.g. in the rootScope or a service) accessible from any controller without having to request them again in every controller (as in the callback or resolve-solution) but make sure that there won’t be any „timeout“ problems. Do you have any ideas?
You can resolve the user's data before the view loads either with ng-route or ui-router:
This example is written for ui-router:
.state('profile', {
url: '/profile',
controller: 'profileCtrl as vm',
resolve: {
user: function(AuthService) {
//Return a promise or an object to be resolved.
return AuthService.getUserFromToken(); //Say this is asynchronous and returns a promise
}
}
});
//In controller:
.controller('profileCtrl', function(... , user) {
//User data available here
this.user = user;
});
Please note if any errors arise during the resolve stage the state will not be loaded so you'll have to take care of the errors!
If a user refreshes you have to initialize everything. I assume the token is stored in localstorage or something and I assume this is angular 1.*. To do this I think you should call user-related functions from your http call-callback:
$scope.user = {};
$scope.getUser = function(){
$http({
method: 'GET',
url: '/someUrl'
}).then(function (res) {
$scope.user = res.data; //or whatever the response is
$scope.handleUserRelatedThings();
}).catch(function(err) {
//handle error
})
}
$scope.handleUserRelatedThings = function(){
//do things with $scope.user
alert($scope.user.name);
}
//on init
$scope.getUser();

Keep reading from Angular JS HTTP call

I am working on a web system, but fairly new to Angular JS.
The feature I am working on currently needs to do a POST call using Angular. That's not really difficult to do in Angular. But I want to keep reading from the socket.
The call I am doing takes a very long time (as intended) but I want the status of the call to be visible in my Angular app. So I'd like to send data back with the status, and this to be live visible in my app.
What's the best approach for this? I found the WebSocket demonstrations for Angular, but in that case I'd have to create my own HTTP implementation on top of WebSockets to be able to send POST requests.....
HTTP requests using the $http service take place in the background, so you can show anything you want while the request is actually being made. To get a sense of the status of the request it depends on how you'd like to measure that status.
Progress events on the $http object aren't slated to be added to Angular until 1.6, so if the delay is related to a large file upload you might need to create a service that wraps a raw XMLHttpRequest object instead of using $http. Here's an example
from the MDN docs:
var oReq = new XMLHttpRequest();
oReq.addEventListener("progress", updateProgress);
oReq.addEventListener("load", transferComplete);
oReq.addEventListener("error", transferFailed);
oReq.addEventListener("abort", transferCanceled);
// progress on transfers from the server to the client (downloads)
function updateProgress (oEvent) {
if (oEvent.lengthComputable) {
var percentComplete = oEvent.loaded / oEvent.total;
// ...
} else {
// Unable to compute progress information since the total size is unknown
}
}
If the delay is serverside - waiting for a long running database query, for example, you might want to just fake a progress meter based on the average runtime of the query or show an indeterminate bar:
It sounds like you somehow have a way to monitor the progress on the serverside. In that case, you can make another request while the first one is in progress to get the progress information. You will probably need to send some state (like a query ID or request ID) to correlate the two requests.
XHRCallToTheRestService()
.then(function(result){
//Hide status bar, data is back
});
readMyStatus()
.then(function(status){
if(status == "finished"){
//Do nothing
} else{
readMyStatus();
}
});
You can use interceptor. Whenever you do http call, this will intercept the call before send and after return from the server.
$httpProvider.interceptors.push(['$q', '$location', 'localStorageService', function ($q, $location, localStorageService) {
return {
request: function (config) {
// You can do some stuff here
return config;
},
response: function (result) {
return result;
},
responseError: function (rejection) {
console.log('Failed with', rejection.status, 'status');
return $q.reject(rejection);
}
}
}]);
Or you can use https://github.com/McNull/angular-block-ui
you could create a custom service using $HTTP to do a POST request like this:
site.factory('customService', ['$http', function($http){
var httpProto = {
success : function(response, status, headers, config) {
return response;
},
error : function(error, status, headers, config) {
return error;
}
};
thePostRequest: function(dataRequest){
var output = $http.post(
'http://74.125.224.72/',
dataRequest
);
output.prototype = httpProto;
return output;
}
}
For web sockets a friend pointed me to socket.io and I have used them successfully for Angular in several SPAs. Check this material for more info.
G00d 1uck.

Async calls using AngularJS

We are making multiple HTTP requests using Angular:
$scope.GetTest1 = function () {
$http.get("/test/GetTest1/").success(function (response) {
$scope.res = response.aaData;
});
}
$scope.GetTest2 = function () {
$http.get("/test/GetTest2/").success(function (response) {
$scope.res = response.aaData;
});
}
$scope.GetTest3 = function () {
$http.get("/test/GetTest3/").success(function (response) {
$scope.res = response.aaData;
});
}
// This is called from an onclick of a button
$scope.LoadAll = function () {
$scope.GetTest1();
$scope.GetTest2();
$scope.GetTest3();
}
We assumed that these were all called async, however, we have log4net enabled and we log the datetime when the 'gets' are received, and the times for all 3 are:
19:05:26
19:05:27
19:05:28
This was an unexpected surprise as we assumed the time would all be within 1 second. ie async.
Not sure if we're missing something,
Sorry, question is, how do we make these async calls?
I suppose that the reason of that perhaps is on the server side. I had almost the same result when server could serve only one request from one client. If response from server fulfils your $http requests in one second then that could be a problem. Please check your network statistics and if you see that they were called simultaneously but were served not immediately then it's server side problem.
You can easily track this on browser's devtools' timeline

How to avoid $compile:tpload errors on 401 status code response

We are developing a Single Page Application with AngularJS and ASP.NET MVC Json Rest API.
When an unauthenticated client tries to navigate to a private route (Ex: /Foo/Home/Template) to get a template, it gets a 401 response from the Web API and our AngularJS app automatically redirects it to the login page.
We are handling the 401 with $http interceptor with something like this:
if (response.status === 401) {
$location.path(routeToLogin);
return $q.reject(response);
}
Entering the correct credentials allows the client to get the template.
Everything is working perfectly except for one detail; the Javascript console reports this error:
Error: [$compile:tpload] http://errors.angularjs.org/1.3.0/$compile/tpload?p0=%Foo%2FHome%2FTemplate%2F
AngularJs documentation states this:
Description
This error occurs when $compile attempts to fetch a template from some
URL, and the request fails.
In our AngularJs app the request fails but it is by design because the resource is there but it cannot be accessed (401).
Should I move on and accept this kind of error on console or is it possible to mute or shield it in some way?
EDIT:
I have debugged the angular source a little bit and I found what part of the code is raising the exception.
Since we are using TemplateUrl to declare our templates, we are indirectly using the function compileTemplateUrl that makes this call:
$templateRequest($sce.getTrustedResourceUrl(templateUrl))
this leaves the second parameter (ignoreRequestError) of templateRequest undefined.
ignoreRequestError(optional)boolean
Whether or not to ignore the exception when the request fails or the
template is empty
When our http interceptor, handling the 401 status code, rejects the promise, the $http.get inside the $TemplateRequestProvider fails and calls this function:
function handleError() {
self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
return $q.reject();
}
I believe we can't do anything to prevent the error on console as TemplateUrl does not allow to set the ignoreRequestError flag to false.
I've tried to bypass the reject in case of 401 status code; this fixes the error on console but sadly it has a side effect: an empty template is wrongly cached into the TemplateCache causing othe problems.
After some thinking I remembered about decorating in Angular, it solved this problem perfectly:
app.config(['$provide', function($provide) {
$provide.decorator('$templateRequest', ['$delegate', function($delegate) {
var fn = $delegate;
$delegate = function(tpl) {
for (var key in fn) {
$delegate[key] = fn[key];
}
return fn.apply(this, [tpl, true]);
};
return $delegate;
}]);
}]);
You should be able to intercept the call for the template by status and url.
Plunker
app.config(function($httpProvider) {
var interceptor = function($location, $log, $q) {
function success(response) {
// The response if complete
$log.info(response);
return response;
}
function error(response) {
// The request if errors
$log.error(response);
return $q.reject(response);
}
return function(promise) {
return promise.then(success, error);
}
}
$httpProvider.responseInterceptors.push(interceptor);
});
As I see it, you have two options:
Option A)
go with the interceptors. However, to eliminate the compile you need to return success status code inside response error (BAD) OR redirect to the login page inside the interceptor (Good):
app.factory('authInterceptorService', function () {
var interceptor = {};
interceptor.responseError = function (rejection) {
if (rejection.status === 401 && rejection.config.url === "home template url") {
//BAD IDEA
//console.log("faking home template");
//rejection.status = 200;
//rejection.data = "<h1>should log in to the application first</h1>";
//GOOD IDEA
window.location = "/login.html";
}
return rejection;
}
return interceptor;
});
and on app config:
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptorService');
}
Option b)
make the home template public. After all it should be just html mark-up, without any sensible information.
this solution is clean...and perhaps is also possible.

Categories

Resources