Can't resolve the second promise on Facebook JS with Angular - javascript

I'm developing an application with Facebook JS and AngularJS. First of all I login and then get the Friends and show them in a list. Everything is working fine with this code:
var friendsController = app.controller(
"FriendsController",
function FriendsController($scope, friendsData, $location, $routeParams)
{
$scope.friendsData = friendsData.data;
$scope.params = $routeParams;
$scope.onClickFriend = function(friendId)
{
$location.path("friends/friend:" + friendId);
}
}
);
friendsController.loadFriends = function($q, $rootScope)
{
var deferred = $q.defer();
FB.api("/me/friends?fields=name,picture.type(square)", function(response)
{
$rootScope.$apply(deferred.resolve(response));
});
return deferred.promise;
}
Now I want to load the details of a friend in a new view, so I create a new routeProvider rule with a new controller and a new promise.
var friendDetailController = app.controller(
"FriendDetailController",
function FriendDetailController($scope, friendData, $routeParams)
{
$scope.friendData = friendData;
$scope.params = $routeParams;
console.log(friendData);
}
);
friendDetailController.loadFriend = function($q, $route, $rootScope)
{
var deferred = $q.defer();
var friendUrl = $route.current.params.friendId.split(":")[1] + "?fields=id,name,picture.type(large)";
FB.api("/" + friendUrl, function(response)
{
$rootScope.$apply(deferred.resolve(response));
});
return deferred.promise;
}
The problem is that I'm entering at loadFriend but it seems that it's not making the call to the FB.api, and it's really weird because it is working on FriendsController, which is basically the same.
In fact, it seems that is the second call the one that is not called, because if I invert the calls (first FriendDetail, afterwards, Friends), FriendDetail is working.

Related

AngularJS and angular factory

I have asked similar question before, this time I am stuck with recording data to the blockchain using Angular js and Angular Factory. Please see the code below and advise me where I am wrong
app.js
var app = angular.module('application', [])
app.controller('appController',function($scope, appFactory) {
$('success_create').hide()
$scope.recordData = function(){
appFactory.recordData($scope.data, function(data){
$scope.recordData = data
$("success_create").show()
})}}
app.factory('appFactory',function($http) {
var test = []
factory.recordData = function(data, errorCallback) {
test = data.field1+"-"+data.field2
$http.get('/record_data'+data).then(function(output) {
if (output) {
callback(output)
}).catch(function(error) {
errorCallback(error) })}
return factory
There are so many errors in you're code, that I was considering not to awnser.
But as I felt the need to help you, take the code below as a guide.
var app = angular.module('application', [])
app.controller('appController', function($scope, appFactory) {
// Use angular.element instead of the jQuery `$` selector
angular.element('success_create').hide();
$scope.recordData = function()
{
// The factory returns a promise,
// so you can do the same just as you would with $http
appFactory.recordData($scope.data).then(function(response) {
$scope.recordData = response.data;
angular.element("success_create").show()
});
}
});
app.factory('appFactory',function($http) {
// You define the var as array, but you assign a string later on
// So instead of var test = [] use var test = "" or just var test;
var test = ""; // Or var test;
var factory = {
recordData: function (data, errorCallback)
{
test = data.field1 + "-" + data.field2;
var promise = $http.get('/record_data' + data).then(function(output) {
return output.data;
});
// $http returns a promise, return this to your controller
// You can use the data it returns just like you
// would with the $http method
return promise;
}
}
// In your original code, you return the factory. But you never
// Defined the factory.
return factory;
});
Try out these simply tutorials to learn more about controllers, services ** and promises
https://www.w3schools.com/angular/angular_controllers.asp
https://www.w3schools.com/angular/angular_services.asp
https://docs.angularjs.org/api/ng/service/$q
** Confused about Service vs Factory
#Tabz: modified your code.
app.controller(appController,function($scope, appFactory) {
$("success_create").hide();
$scope.recordData = function(){
appFactory.recordData($scope.data, function(data){
$scope.recordData = data
$("success_create").show();
})
}
})
app.factory("appFactory", function($http) {
factory.recordData = function(data, errorCallback)
$http.get('/record_data'+data).then(function(output) {
if (output)
callback(output)
}).catch(function(error) {
errorCallback(error)
})};
return factory

Angular Queue Action From Directive To Service

I'm trying to get better understanding of how to code in Angular properly and am writing my own custom module to better understand how things are supposed to work.
I have an HTML markup consisting of images and a directive i.e.
<img ng-if="!post.tooBig" mydirective resize="0" ng-src="/img/#{{post.Content}}">
My directive is here:
.directive('mydirective', [
'$animate','$mediaStack',
function($animate,$mediaStack) {
return {
restrict: 'A',
compile: function(tElement, tAttrs) {
return loader;
}
};
function loader(scope, element, attrs) {
var source = attrs.ngSrc;
var tooLoadBig = parseInt(attrs.resize);
if(tooLoadBig){
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
}
}
}])
The idea is this: if the image has small appended to its filename, I know it is a thumbnail. I want to load it's big version (same file without the appended small) in the background so it is ready for a lightbox.
This works fine as is, but the problem is because I'm doing all the work in the compile, when I set bigImage.src = source.replace("small.","."); it fires off right away, and if I have many small images on the page, it causes the page to slow down because of all the loading that is going on.
I want to therefore use $q to make it so that it will load one image at a time.
So move
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
Into a promise. Is it best practice to do this in the directive? My understanding is it wouldn't make sense to do so and that I should use a service, but I'm not sure how to do that. I could play around with it more but I was hoping someone with more experience could instruct me as to best-practices for something like this and a code sample of a similar workflow, thank you.
Edits:
Directive:
.directive('mydirective', [
'$animate','$mediaStack',
function($animate,$mediaStack) {
return {
restrict: 'A',
compile: function(tElement, tAttrs) {
return loader;
}
};
function loader(scope, element, attrs) {
var source = attrs.ngSrc;
var tooLoadBig = parseInt(attrs.resize);
if(tooLoadBig){
/*var bigImage = new Image();
bigImage.src = source.replace("small.",".");*/
$mediaStack.load(source);
}
}
}])
My service:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
$mediaStack = {};
$mediaStack.load = function(source){
theImages.push(source);
};
$mediaStack.loadRequest = function(theImages) {
deferred.resolve(function(){
var bigImage = new Image();
bigImage.src = theImages[0].replace("small.",".");
});
return deferred.promise;
}
/*var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});*/
//above from docs
}
return $mediaStack;
}])
This works in that it gets the image urls into an array in the service, so I have all the images in the array, how do I use $q properly based off that array. I got started but most of the $mediaStack.loadRequest is based off the $q documentation, I'm not sure how to use it effectively in this case.
EDIT 2:
My service as is:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
var theLoadedImages = [];
var thePromises = [];
var loading = false;
$mediaStack = {};
$mediaStack.load = function(source){
theImages.push(source);
var mainDeferred = $q.defer();
if(loading)
{
thePromises.push(mainDeferred.promise);
console.log(thePromises);
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
mainDeferred.resolve();
});
});
}
if(!loading)
{
loading = true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
mainDeferred.resolve();
});
}
}
$mediaStack.loadRequest = function(source){
var deferred = $q.defer();
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
bigImage.onload = function() {
deferred.resolve(bigImage);
}
return deferred.promise;
}
//.race implementation in old angular
$mediaStack.myRaceFn = function (promises){
return $q(function(resolve, reject) {
promises.forEach(function(promise) {
console.log(promise);
promise.then(resolve, reject);
});
});
}
//.race implementation in old angular
return $mediaStack;
}])
I'm very close, I see an array of promises but I never get to the point of being able to fire them again, so I have 10 images, and I get an array of 9 promises. How do I fire them?
I expected it would happen around here:
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
But I never get that console.log().
Instead I get console messages in $mediaStack.myRaceFn() showing me each promise (9 at the end) but they just sit there? I'm missing something.
I think I may be resolving mainDeferred too early...
I think, that your hunch is good. You need a service to handle requests for your images. Remember that browsers are limited to send multiple concurrent requests to single server/proxy (Max parallel http connections in a browser?)
In theory you could create a promise and expose it thru two way bindings or even drive loading with an attribute. Something like:
HTML:
<img mydirective="booleanValue" />
In mydirective:
$scope.$watch('mydirective', function (newVal, oldVal) {
if (newVal === true) {
//perform the load
}
});
But I think, that a service would be better. You could decide what and when to load, from that single point. It wouldn't be limited to UI component, you could load bigger images not only from directives context.
So in the service the essential code would be:
module.factory('$mediaStack', ['$q', function($q) { //first of all you don't need all of these dependencies, keep the list short
var imgDict = {}; //Image dictionary where we're going to keep promises and loaded images
var loadingNow = 0;
function loadImage(img) {
//Send request and return promise
var deferred = $q.defer();
var bigImage = new Image();
bigImage.onload = function() {
deferred.resolve(bigImage);
}
bigImage.src = source.replace("small.", ".");
return deferred.promise;
}
var $mediaStack = function(img) {
var deferred = $q.defer(); //the main deferred task
if (loadingNow > 4) { //if we're loading 5 images or more, defer the task untill one of the Images is loaded
var promises = []; //an array of promises, list of images which are loading now
Object.keys(imgDict).forEach(function(key) {
if (!(imgDict[key] instanceof Element)) { //Check if the item from dictionary is a promise or loaded image
promises.push(imgDict[key]); //if it's a promise, add it to the list
}
});
$q.race(promises).then(function() { //use the list, to create a race: when one of the promises resolves (image is loaded) we can fire our function again (we probably have less than 5 Images loading at the same time)
$mediaStack(img).then(function(data) { //call the function again
deferred.resolve(data); //as we return a promise form that function we have to resolve main deferred object
});
});
}
if (!(img in imgDict)) { //when the image is not loaded yet
loadingNow++; //increase the number of images being loaded
imgDict[img] = loadImage(img).then(function(imgDOM) { //and load the image
imgDict[img] = imgDOM;
deferred.resolve(imgDOM); //once it's loaded resolve the main deferred task
loadingNow--;
});
} else {
deferred.resolve(imgDict[img]);
}
return deferred.promise; //promise to the main deferred task
}
return $mediaStack;
}]);
The general idea behind promises and deferred tasks:
Deferred object is a deferred task, which has a Promise parameter - a entry point of a callback, which is going to be invoked once the deferred task is completed.
Check $q docs for more: angular $q
And how to write a service: https://docs.angularjs.org/guide/services
What is Promise? The general idea and native API
Hope it helps.
Marking Oskar as accepted because it basically allowed me to figure out a solution.
Here is what I actually implemented, however:
.factory('$mediaStack', [
'$animate', '$timeout', '$document', '$compile', '$rootScope',
'$q',
'$injector',
function($animate , $timeout , $document , $compile , $rootScope ,
$q,
$injector) {
var OPENED_MEDIA_CLASS = 'media-open';
var theImages = [];
var theLoadedImages = [];
var thePromises = [];
var loading = false;
$mediaStack = {};
$mediaStack.load = function(source){
if(source)
{
theImages.push(source);
}
var mainDeferred = $q.defer();
if(loading)
{
thePromises.push(mainDeferred.promise);
console.log(thePromises);
$mediaStack.myRaceFn(thePromises).then(function() {
console.log("Fire!");
loading=true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
//mainDeferred.resolve();
});
});
}
if(!loading)
{
loading = true;
$mediaStack.loadRequest(theImages[0]).then(function(bigImage){
console.log(bigImage);
theImages.shift();
theLoadedImages.push(bigImage);
loading = false;
if(theImages.length>0)
{
$mediaStack.load();
};
});
}
}
$mediaStack.loadRequest = function(source){
var deferred = $q.defer();
var bigImage = new Image();
bigImage.src = source.replace("small.",".");
bigImage.onload = function() {
deferred.resolve(bigImage);
}
return deferred.promise;
}
//.race implementation in old angular
$mediaStack.myRaceFn = function (promises){
return $q(function(resolve, reject) {
promises.forEach(function(promise) {
console.log(promise);
promise.then(resolve, reject);
});
});
}
//.race implementation in old angular
return $mediaStack;
}])

Multiple http request in a single controller angular js

I have created different drupal views to create multiple json endpoints for multiple http request using angular. In my angular script I have created multiple controller for each http request as shown below. But I want to do multiple http request in single controller. I have got inclination that $q and promises or factory are way forward but not completely sure. Any help will be much appreciated. Thanks
var module = angular.module('main',[]);
module.directive("masonry", function () {
var NGREPEAT_SOURCE_RE = '<!-- ngRepeat: ((.*) in ((.*?)( track by (.*))?)) -->';
return {
compile: function(element, attrs) {
// auto add animation to brick element
var animation = attrs.ngAnimate || "'masonry'";
var $brick = element.children();
$brick.attr("ng-animate", animation);
// generate item selector (exclude leaving items)
var type = $brick.prop('tagName');
var itemSelector = type+":not([class$='-leave-active'])";
return function (scope, element, attrs) {
var options = angular.extend({
itemSelector: itemSelector
}, scope.$eval(attrs.masonry));
// try to infer model from ngRepeat
if (!options.model) {
var ngRepeatMatch = element.html().match(NGREPEAT_SOURCE_RE);
if (ngRepeatMatch) {
options.model = ngRepeatMatch[4];
}
}
// initial animation
element.addClass('masonry');
// Wait inside directives to render
setTimeout(function () {
element.masonry(options);
element.on("$destroy", function () {
element.masonry('destroy')
});
if (options.model) {
scope.$apply(function() {
scope.$watchCollection(options.model, function (_new, _old) {
if(_new == _old) return;
// Wait inside directives to render
setTimeout(function () {
element.masonry("reload");
});
});
});
}
});
};
}
};
});
angular.module('main',[]).controller('blogcontroller', function ($scope,$http) {
$http.get('/blog-filter').success(function(result){
$scope.blog = ( function () {
return result.nodes;
})();
});
});
angular.module('cs',[]).controller('cscontroller', function ($scope,$http) {
$http.get('/case-study-view').success(function(results){
$scope.cs = ( function () {
return results.nodes;
})();
});
});
Create a factory with all $http services and use $q like this:
(function(){
app.factory('CountriesServices', countriesServices);
countriesServices.$inject = ['$http', '$q', 'settings'];
function countriesServices($http, $q, settings) {
var self = {};
self.getCountries= function(){
var deferred = $q.defer();
var url = settings.baseService + 'api/country';
$http.get(url)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
};
self.getCountry = function(id){
var deferred = $q.defer();
var url = settings.baseService + 'api/country/' + id;
$http.get(url)
.success(deferred.resolve)
.error(deferred.reject);
return deferred.promise;
};
return self;
}
})();
Yes you should have your $http methods in a factory:
angular
.module('sharedServices')
.factory('blogRepository', blogRepository);
blogRepository.$inject = ['$http', '$q'];
function blogRepository($http, $q) {
var service = {
getBlogFilter: getBlogFilter
};
return service;
function getBlogFilter() {
var deferred = $q.defer();
$http.get('/blog-filter').success(function(result){
deferred.resolve(result);
});
return deferred.promise;
}
}
and in your controller:
blogRepository.getBlogFilter().then(function(data) {
$scope.blog = data.nodes;
});
It would be similar for the other $http call.

Having problems unit testing a promise call in angularjs controller

SupportedLanguagesServices get method returns a promise which is resolved in the controller as follows:
angular.module('App').controller('MyController', ['SupportedLanguagesService',
function(SupportedLanguagesService) {
var self = this;
self.supportedLanguages = [];
SupportedLanguagesService.get().success(
function(response) {
self.supportedLanguages = response;
});
}]);
And here is the test I wrote, but it is not working:
describe('Controller: MyController', function() {
beforeEach(module('App'));
var rootScope, controller;
beforeEach(inject(function($controller, $rootScope, SupportedLanguagesService, $q) {
var deferred = $q.defer();
deferred.resolve([{name:"English", code:"en"},{name:"Portugues", code:"pt_BR"]);
spyOn(SupportedLanguagesService, 'get').andReturn(deferred.promise);
rootScope = $rootScope;
controller = $controller;
}));
it('should get SupportedLanguages', function() {
rootScope.$digest();
var ctrl = controller('MyController');
expect(ctrl.supportedLanguages.length).toEqual(2);
});
});
It throws an exception on the statement: var ctrl = controller('MyController');
Thank you for your assistance.
Intead of success (which is an $http callback), you can change to a then, which is available on all promises. This will allow you to easily mock the promise (and not concern yourself with $httpBackend:
SupportedLanguagesService.get().then(
function(response) {
self.supportedLanguages = response.data;
});
Then, you need to use the controller's constructor and then call a $digest. So, switching to this should get you there:
it('should get SupportedLanguages', function() {
var ctrl = controller('MyController');
rootScope.$digest();
expect(ctrl.supportedLanguages.length).toEqual(2);
});
You can also simplify the setup code using $q.when:
var response = [{name:"English", code:"en"},{name:"Portugues", code:"pt_BR"}];
spyOn(SupportedLanguagesService, 'get').andReturn($q.when(response));

Angular - $q not working as intended / help waiting for $http to finish

I am working through my first Angular project (rails gem version 1.2.16.) and can't seem to figure out how to use $q correctly.
I have a directive where on click: 1. calls a directive function that calls a factory fucntion to run a $http request 2. uses the response from the directive function / $http in the template that is appended to the page.
DIRECTIVE:
d2jive.directive('getSpotifyTracks', ['spotifyFactory', '$compile', '$sce', '$q',
function (spotifyFactory, $compile, $sce, $q) {
'use strict';
var getTracks = function(artistName){
var deferred = $q.defer();
var spotifyTracks = spotifyFactory.getArtistTracks(artistName)
deferred.resolve(spotifyTracks);
return deferred.promise;
}
return {
scope: {
artistName: '#'
},
compile: function(tElement, tAttrs, artistName){
return function(scope, iElement) {
iElement.click(function(){
var tracks = getTracks(scope.artistName);
tracks.then(function(tracks){
var t = '<iframe src="https://embed.spotify.com/?uri=spotify:trackset:Playlist:"' + tracks.spotifyTracks+ 'width="300" height="300" frameborder="0" allowtransparency="true"></iframe>';
iElement.after($compile(t)(scope));
});
});
}
}
}
}]);
FACTORY:
d2jive.factory('spotifyFactory', ['$http','$q', function($http, $q){
var factory = {}
factory.getArtistTracks = function(artistName){
var tracks = {}
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?q=";
var deferred = $q.defer();
var getTracks = function(artistName){
$http.get(spotifyUrl + encodeURIComponent(artistName))
.success(function (data) {
deferred.resolve(data);
});
return deferred.promise;
};
// tracks.spotifyTrakcs = getTracks(artistName);
var spotifyTracks = getTracks(artistName);
spotifyTracks.then(function(result){
var trackArray = [];
var tracks = result.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track].href.slice(
14, tracks[track].href.length);
trackArray.push(grabbedTrack);
}
tracks.spotifyTracks = trackArray;
console.log(tracks.spotifyTracks);
});
return tracks;
}
return factory;
}]);
HTML:
<div class="eventContainer row" ng-controller="VenueResultsCtrl">
<div ng-repeat="event in events">
<h4>
{{event.displayName}}
</h4>
<p>
Buy Tickets
</p>
<div ng-repeat="artist in event.performance">
<button get-spotify-tracks artist-name="{{artist.displayName}}">Discover
{{artist.displayName}}<br> -- {{artist.billing}}</button><br><br>
<div class='spotify'></div>
</div>
</div>
</div>
THE PROBLEM: The template loads before the factory function returns the response.
MY SOLUTION: I created a function in my directive to call the factory and tried using $q to wait for the response before appending the template, but it does not seem to be working.
Any tips or leads will be much appreciated, thanks!
I think you might be able to use some thing simular to this to get it to work:
d2jive.factory('spotifyFactory', function($http){
var spotifyFactory = {
var spotifyUrl = "http://ws.spotify.com/search/1/track.json?q=";
getTracks: function(artstName){
var promise = $http.get(spotifyUrl + encodeURIComponent(artistName))
.then(function (result){
//this is where you modifiy the results
var tracks = result.tracks.slice(0,9);
for (var track in tracks){
grabbedTrack = tracks[track]
.href.slice(14,tracks[track].href.length);
trackArray.push(grabbedTrack);
}
tracks.spotifyTracks = trackArray;
console.log(tracks.spotifyTracks);
return tracks
});
return promise;
});
return spotifyFactory
});
and then in your controller/directive you would call this function like this
spotifyFactory.getTracks(someArtist).then(function(d) {
$scope.data = d;
});
A few pointers:
$http is already based on $q. It returns a promise. So you don't need to wrap $http by a promise, it already returns one. See the doc with examples.
your getArtistTracks function will return 'tracks' before the code in 'then' is executed, because that code is asynchronous. As you use async code, you cannot return the result. Instead you could:
call a callback with the track value you calculated
or, fancier nowadays, return a promise and fulfil it later (recommended, as it would give you a better understanding of how to use promises).

Categories

Resources