I use factory to make database calls in angularjs
var app = angular.module('myApp', []);
app.factory("Service", function ($http) {
var obj = {};
$http.get('test.txt').then(function (data) {
obj.getApiKey = {'Authorization' : data.data};
});
return obj;
});
app.factory("Orders", function ($http, Service) {
var obj = {};
var api_key = Service.getApiKey;
console.log(api_key);
return obj;
});
In the above program, when i call property of Service factory and log the value of api_key = Service.getApiKey; it shows undefined. Probably its because the data is not received from $http call. How to perform the AJAX call synchronously.
var app = angular.module('myApp', []);
app.factory("Service", function ($http) {
var obj = {};
factory.get=function(success,error){
$http.get('test.txt').then(function (data) {
obj.getApiKey = {'Authorization' : data.data};
});
return obj;
}
});
app.factory("Orders", function ($http, Service) {
Service.get(suc,err);
var suc=function()
{
var obj = {};
var api_key = Service.getApiKey;
console.log(api_key);
return obj;
}
});
As noted by Jonathan, it's not best practice you not use synchronous calls with AJAX, instead, you can return a promisse as a funcion and use it later:
var app = angular.module('myApp', []);
app.factory("Service", function ($http) {
var obj = {};
obj.getApiKey = $http.get('test.txt').then(function (data) {
return {'Authorization' : data.data};
});
return obj;
});
app.factory("Orders", function ($http, Service) {
var obj = {};
obj.getApiKey = Service.getApiKey.then(function(data){
console.log(data);
return data;
});
return obj;
});
In this case, the service is returning a promisse object, this way you can ensure values will be called successfully and data will be available after the call.
Return a Promise from your service:
app.factory("Service", function ($http) {
return {
getApiKey: function() {
return $http.get('test.txt');
}
};
});
Inject your service, and use the Promise API to get your data asynchronously:
app.factory("Orders", function ($http, Service) {
var obj = {};
var api_key = {};
Service.getApiKey().then(function(apiKey) {
api_key = apiKey.data;
});
});
Related
I am trying to test my Angular with Jasmine but somehow i keep getting this error Expected undefined to be defined and i have followed that angular documentation example i am on mean stack
test.js
describe('Testing Ecdreport Controllers', function(){
var $scope, controller;
var app = angular.module('mean.ecdreport',[])
.controller('EcdreportController', ['$scope', '$http', 'Global', 'Ecdreport', function($scope, $http, Global, Ecdreport) {
$scope.global = Global;
$scope.query = "";
$scope.package = {
name: 'ecdreport'
};
$scope.startDate = null;
$scope.endDate = null;
$scope.currentPage = 1;
$scope.child= [];
$scope.maxSize = 5;
$scope.items = [];
$scope.itemsPerPage = 10;
$scope.totalItems = null;
$scope.direction = 1;
$scope.directionOld = 1;
$scope.sortAttributes = 0;
$scope.sortAttributesOld = 0;
$scope.datamodel = null;
$scope.getDataModel = function() {
$http({url:'/api/v1/getdatamodel', method:"GET"})
.success(function(data) {
console.log('Datamodel successful');
$scope.datamodel = data[0];
console.log('datamodel', data);
})
.error(function(error) {
$scope.datamodel =[];
});
}
// console.log("Trying to get datamodel");
$scope.getDataModel();
});
describe('Testing Ecdreport Controllers', function(){
var $scope, controller;
beforeEach(module('mean.ecdreport', function($controllerProvider){
$controllerProvider.register('EcdreportController', function(){
});
}));
beforeEach(inject(function(_$rootScope_,_$controller_){
$scope = _$rootScope_.$new();
controller = _$controller_('EcdreportController',
{$scope : $scope
});
}));
it('Should be registered', function(){
expect(controller).toBeDefined();
})
it('Testing Scope', function(){
expect($scope).toBeDefined()
expect($Scope.getDataModel).toBeDefined();
})
});
beforeEach(module('mean.ecdreport', function($controllerProvider){
$controllerProvider.register('EcdreportController', function(){
});
}));
beforeEach(inject(function(_$rootScope_,_$controller_){
$scope = _$rootScope_.$new();
controller = _$controller_('EcdreportController',
{$scope : $scope
});
}));
it('Should be registered', function(){
expect(controller).toBeDefined();
})
it('Testing Scope', function(){
expect($scope).toBeDefined()
expect($scope.getDataModel).toBeDefined();
})
});
You get that error because your controller in test is never defined. You need to use var controller = ...
You should use controller injection like this :
beforeEach(inject(function(_$rootScope_,_$controller_){
$scope = _$rootScope_.$new();
createController = function() {
return _$controller_('EcdreportController', {
$scope : $scope
});
};
}));
and initialize the controller in each test like this :
it('Should be registered', function(){
var controller = new createController();
expect(controller).toBeDefined();
})
This way you can also pass on different parameters in each test if your controller requires any data to be passed on to.
Hopefully the code below communicates the problem clearly. The issue is that in the module which uses the get method of fetchData, the value being returned is the actual Promise, rather than the JSON as desired. Any thoughts on this?
// fetchData.js module
var _ = require('lodash');
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return fetch(endpoint1)
.then((endpoint1Response) => {
return endpoint1Response.json()
.then((endpoint1JSON) => {
return fetch(endpoint2)
.then((endpoint2Response) => {
return endpoint2Response.json()
.then((endpoint2JSON) => {
var data = _.merge({}, {json1: endpoint1JSON}, {json2: endpoint2JSON});
console.log('data in fetch', data); // this logs the json
return data;
});
});
});
});
}
exports.get = get;
// module which uses get method of fetchData get
var fetchData = require('fetchData');
var data = fetchData.get();
console.log('returned from fetchData', data); // this logs a Promise
Yes, that's exactly what's supposed to happen. The whole point of promises is that their result value is not immediately available and that doesn't change just because you're obtaining one from a separate module.
You can access the value like this:
var fetchData = require('fetchData');
fetchData.get().then(data =>
console.log('returned from fetchData', data);
);
Also note that you are using promises in a non-idiomatic way and creating a "tower of doom." This is much easier on the eyes and accomplishes the same thing:
function fetchJson(endpoint) {
return fetch(endpoint)
.then(endpointResponse => endpointResponse.json());
}
function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
return Promise.all([fetchJson(endpoint1), fetchJson(endpoint2)])
.then(responses => {
var data = { json1: responses[0], json2: responses[1] };
console.log('data in fetch', data); // this logs the json
return data;
});
}
Edit I haven't used async/await in JavaScript, but to answer your question, I presume this would work:
async function fetchJson(endpoint) {
var res = await fetch(endpoint);
return res.json();
}
async function get() {
var endpoint1 = `/endpoint1`;
var endpoint2 = `/endpoint2`;
var data = {
json1: await fetchJson(endpoint1),
json2: await fetchJson(endpoint2)
};
console.log('data in fetch', data); // this logs the json
return data;
}
// module which uses get method of fetchData get
async function main() {
var fetchData = require('fetchData');
var data = await fetchData.get();
console.log('returned from fetchData', data);
}
return main();
Is it possible to use $q to fire ajax requests synchronously in AngularJS?
I have a long list of vehicles, each vehicle has events associated with them and I need to retrieve the eventdetails of each event when the user expands the listing.
Right now, if the user expands the listing, I am firing up to 15 calls asynchronously and it seems to be causing issues with the API I'm consuming, so I'd like to see if performance is improved if I wait for each request finishes before firing the next.
I'm attempting to implement $q to delay the next request until the previous is finished, however I can't seem to wrap my head around using the service, here is what I currently have:
// On click on the event detail expander
$scope.grabEventDetails = function(dataReady, index) {
if (dataReady == false) {
retrieveEventDetails($scope.vehicles[index].events);
}
}
var retrieveEventDetails = function(events) {
// events is array
var deferred = $q.defer();
var promise = deferred.promise;
var retrieveData = function(data) {
return $http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: data.number
},
isArray: true
});
}
_.each(events, function(single_event) {
promise.then(retrieveData(single_event).success(function(data) {
console.log(data);
}));
});
}
This is still firing asynchronously, Where am I going wrong with this?
I understand firing the requests synchronously isn't the best idea, at the moment I just want to see if performance is improved with the API at all.
You don't need $q to implement a promise as $http returns one.
_.each fires all the callbacks without especially waiting the promise.
All you do is call retrieveData for all events whenever your promise is resolved, and since you don't do a first call, it shouldn't even be working
You could do some recursive call like this :
var retrieveEventDetails = function(events) {
var evt = events.shift();
$http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: evt.number
},
isArray: true
}).then(function(response){
console.log(response.data);
retrieveEventDetails(events);
});
}
I do think you should use $q as some other part of your application might need to get a promise.
A good example would be $routeProvider resolve option.
I made a little demo in plunker.
Solution:
retrieveData function should return a function (which returns a promise) instead of a just a promise.
That way we can create a promise chain: promise.then(fn).then(fn).then(fn).then(null,errorFn)
We must resolve the first promise to kick the chain.
var retrieveEventDetails = function(events) {
// events is array
var deferred = $q.defer();
var promise = deferred.promise;
var retrieveData = function(data) {
return function(){
return $http({
url: '/api/eventdetails',
method: 'POST',
data: {
event_number: data.number
},
isArray: true
})
}
}
deferred.resolve();
return events.reduce(function(promise, single_event){
return promise.then(retrieveData(single_event));
}, promise);
}
I'm not sure you even need $q here. In this example, each piece of data is registered in the controller as soon as it comes back from the call.
Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
$scope.datas = myService.get();
});
app.factory('myService', function($http) {
var myService = {
get: function() {
var datas = {};
var i=0;
var length = 4;
makeCall(i, length, datas);
return datas;
}
}
function makeCall(i, length, datas) {
if (i < length) {
$http.get('test.text').then(function(resp) {
datas[i] = resp.data+i;
++i;
makeCall(i, length, datas);
});
}
}
return myService;
});
Here's a way using $q.all() that you can wait for all of the data to come through before passing it to the controller: Live demo (click).
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, myService) {
myService.get().then(function(datas) {
$scope.datas = datas;
})
});
app.factory('myService', function($q, $http) {
var myService = {
get: function() {
var deferred = $q.defer();
var defs = [];
var promises = [];
var i=0;
var length = 4;
for(var j=0; j<length; ++j) {
defs[j] = $q.defer();
promises[j] = defs[j].promise;
}
makeCall(i, length, defs);
$q.all(promises).then(function(datas) {
deferred.resolve(datas);
});
return deferred.promise;
}
}
function makeCall(i, length, defs) {
if (i < length) {
$http.get('test.text').then(function(resp) {
defs[i].resolve(resp.data+i);
++i;
makeCall(i, length, defs);
})
}
}
return myService;
});
As I want to implement a chat in AngularJS, I want to use the promise/deferred principle. My ChatService looks like the following:
factory('ChatService', ['$q', '$resource', function($q, $resource) {
var Service = {};
var connected = false;
var connection;
var chatResource = $resource('/guitars/chat/:action', {action: '#action'}, {
requestChatroomId: {
params: {
action: 'requestChatroomId'
},
method: 'GET'
},
sendMessage: {
params: {
action: 'sendMessage'
},
method: 'POST'
}
});
Service.connect = function(cb) {
var deferred = $q.defer();
chatResource.requestChatroomId(function(data) {
connection = new WebSocket('ws://127.0.0.1:8888/realtime/' + data.chatroomId);
connection.onerror = function (error) {
deferred.reject('Error: ' + error);
};
connection.onmessage = function (e) {
cb.call(this, e.data);
deferred.notify(e.data);
};
connected = true;
});
return deferred.promise;
};
Service.sendMessage = function(msg) {
if(!connected) {
return;
}
chatResource.sendMessage({message: msg});
}
return Service;
}])
My controller using the ChatService is:
app.controller('ChatCtrl', ['$scope', 'ChatService', function($scope, ChatService) {
$scope.chat = {};
$scope.chat.conversation = [];
var $messages = ChatService.connect(function(message) {
$scope.$apply(function() {
// #1 THIS FIRES EVERY TIME
$scope.chat.conversation.push(message);
});
});
$messages.then(function(message) {
console.log('Finishes - should never occur!')
}, function(error) {
console.log('An error occurred!')
}, function(message) {
// #2 THIS FIRES ONLY IF THERE IS AN INTERACTION WITH THE ANGULAR MODEL
console.log(message);
});
$scope.sendMessage = function(event) {
ChatService.sendMessage($scope.chat.message);
$scope.chat.message = '';
};
}]);
If something is pushed from the server, callback #1 is called, but callback #2 wont be called until there is some interaction with the angular-model, i.e. start writing something in the input-Box. What is the reason for that behaviour?
Okay the reason was, that AngularJS was not aware of a change. So I injected the $rootScope to my ChatService:
factory('ChatService', ['$q', '$resource', '$rootScope', function($q, $resource, $rootScope) {
and in connection.onmessage I called $apply() on $rootScope:
connection.onmessage = function (e) {
deferred.notify(e.data);
$rootScope.$apply();
};
How do I do this asynchronously?
var getData, myFunc;
getData = function() {
var data = "";
$.get("http://somewhere.com/data.xml", function(d) {
data = $("#selector", d).html();
});
return data; // does not work, because async callback not yet fired
};
myFunc = function() {
var data = getData();
// do something with data here
};
I am happy to completely re-factor to achieve what I want. I am just don't know what design pattern achieves this.
Well, you can't. You can return a promise though:
var getData, myFunc;
getData = function () {
var d = $.Deferred();
$.get("http://somewhere.com/data.xml", function (data) {
d.resolve($("#selector", data).html())
});
return d.promise();
};
getData().then(function (data) {
alert(data);
});
demo http://jsfiddle.net/W75Kt/2/