Having problems unit testing a promise call in angularjs controller - javascript

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

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

Execute function after another AngularJS

I need to execute a function which fetches data after a kind of login function who provides the sessionId. This sessionId is necessary for the second function.
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID = loginService.getSessionID(); //Login function
var self = this;
this.items = [];
this.constructor = function() {
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
};
this.constructor(); //get the data
return this;
});
I tried several variations like:
loginService.getSessionID().then(function(sessionID){
this.constructor(); //also with just constructor();
});
But I always receive errors (in the case above: Illegal constructor).
So how can I manage to execute one function after another ? Maybe a callback structure would help here but I have no clue how to realize it.
EDIT
Here is the code for the login:
app.service('loginService', function($http, $q) {
this.getSessionID = function()
{
return $http({
method: "GET",
url: "http://localhost:8080/someRequestDoneHere"
}).then(function(response)
{
return response.data.sessionId; // for example rYBmh53xbVIo0yE1qdtAwg
});
};
return this;
});
Does your getSessionID() function return a promise? If so you want code like this:
app.controller('TestController',
function ($scope, dbObjectsDAO, loginService){
var sessionID;
var vm = this;
vm.items = [];
loginService.getSessionID()
.then(function(sid) {
sessionID = sid;
return dbObjectsDAO.getAll(sessionID);
})
.then(function(arrObjItems){
vm.items = arrObjItems;
});
});
So your login service returns a promise which resolves to the session id. You can save that in a variable for use elsewhere, and also use it to trigger fetching the items.
I also changed your self to vm as that naming is an Angular convention, and stored the items in vm.items rather than directly in the scope.
Edit:
Your login code already returns a promise, not a session id. return inside a then is simply going to return a new promise that resolves to the value you are returning.
There are several ways to chain multiple $http requests. If they are independent of each other just fire off a bunch of requests and use $q.all to handle when they have all completed.
var promise1 = $http(something)
.then(function(response) { vm.data1 = response.data; return vm.data1; });
var promise2 = $http(something)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
$q.all([promise1, promise2], function(values) {
// here can safely use vm.data1 or values[0] as equivalent
// and vm.data2 or values[1].
});
If one request depends on the result of another you could even do this:
var promise1 = $http(something)
.then(function(response) {
vm.data1 = response.data;
return { method:'GET', url: response.data.link}
});
var promise2 = promise1.then($http)
.then(function(response) { vm.data2 = response.data; return vm.data2; });
Your template needs to declare the controller using the 'controller as something' syntax:
<div ng-controller="TestController as test" ng-bind="test.items"></div>
Have you tried to nest the second function, like this ? without the constructor call ?
loginService.getSessionID().then(function(sessionID){
dbObjectsDAO.getAll(sessionID).then(function(arrObjItems){
$scope.items = arrObjItems;
});
});
Mb you have wrong scope in
..then(function(sessionID){...}) ?
you can try some this like this:
var vm=this;
loginService.getSessionID().then(function(sessionID){
vm.constructor();
});

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).

How do i handle multiple http requests with $q all angular.js

I am trying to wrapp my head around $q angular library. In my routeprovider i would like to get all my data from the server and store it in localStorage. But for some reason the resolve dont seem to wait for all http request to finish before routing to selectMedia. From my understanding reading the angular docs this should work but it doesn´t. Have i totally missunderstood the concept or am i my thinking right?
$routeProvider.
when('/', {
redirectTo : '/selectMedia',
resolve: {
data: function ($q, backendApi, localStorage, network, route, loginService){
var prices = function () {
var defer = $q.defer();
backendApi.prices.get(function (data) {
localStorage.setItem("videoPrice", data.VideoPrice);
localStorage.setItem("imagePrice", data.ImagePrice);
localStorage.setItem("prices", data.SliderPrices);
localStorage.setItem("priceSuffix", data.PriceSuffix);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var validFormats = function () {
var defer = $q.defer();
backendApi.validFormats.get(function (formats) {
localStorage.setItem("validFormats", formats);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var videoFormats = function () {
var defer = $q.defer();
backendApi.videoFormats.get(function (videoFormats) {
localStorage.setItem("videoFormats", videoFormats);
defer.resolve();
}, defer.resolve);
return defer.promise;
};
var categories = function () {
var defer = $q.defer();
backendApi.categories.get(function (data){
localStorage.setItem("categories", data.Categories);
defer.resolve();
},defer.resolve);
return defer.promise;
};
var renewToken = function () {
var defer = $q.defer();
loginService.renewToken(defer.resolve);
return defer.promise;
};
if(network.isOnline()){
var promises = [renewToken(), categories(), videoFormats(), validFormats(), prices()];
return $q.all(promises);
}
else if(!network.isOnline() && localStorage.length === 0){
route('/error');
}
}
}
});
}]);
I don't see the controller for the route being specified in the route definition. I guess you have set it via ngController at the view level. The resolve block is skipped when you don't have a controller specified in the definition.
4/23 UPDATE
The definition of the resolve property extracted from the official docs:
An optional map of dependencies which should be injected into the
controller. If any of these dependencies are promises, they will be
resolved and converted to a value before the controller is
instantiated and the $routeChangeSuccess event is fired.
According to the definition, the design purpose of the resolve property is to have a way to inject dependencies into the controller associated with a route.

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

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.

Categories

Resources