I'm trying to create small web application which uses WebSocket protocol to exchange the data
Here is my angular service
define(function () {
return function (module) {
return module.service("socketService", function($q, $timeout) {
var service = {},
listener = $q.defer(),
socket = {
client: null,
stomp: null
};
service.RECONNECT_TIMEOUT = 30000;
service.SOCKET_URL = "/service/booking";
service.CHAT_TOPIC = "/service/booking/updates";
service.CHAT_BROKER = "/service/booking";
service.receive = function() {
return listener.promise;
};
...
return service;
});
};
});
and also controller code
define(function () {
return function (module) {
socketService.receive().then(null, null, function(message) {
$scope.list.countDownTimer = 0;
if(message.resourceId === parseInt($routeParams.resourceId)) {
$scope.list.bookings = [];
}
...
});
My problem is that promise.then() callback function is invoked many times and the amount of this invocations is not repeatable: it might be 2 times, 27 or even 110.
One more thing that I should add is that I think that is somehow dependent with amount of users who tests and clicks on the application
Could anyone help?
Thank you in advance...
Related
I've been struggling with this for a few days now and can't seem to find a solution.
I have a simple listing in my view, fetched from MongoDB and I want it to refresh whenever I call the delete or update function.
Although it seems simple that I should be able to call a previously declared function within the same scope, it just doesn't work.
I tried setting the getDispositivos on a third service, but then the Injection gets all messed up. Declaring the function simply as var function () {...} but it doesn't work as well.
Any help is appreciated.
Here's my code:
var myApp = angular.module('appDispositivos', []);
/* My service */
myApp.service('dispositivosService',
['$http',
function($http) {
//...
this.getDispositivos = function(response) {
$http.get('http://localhost:3000/dispositivos').then(response);
}
//...
}
]
);
myApp.controller('dispositivoController',
['$scope', 'dispositivosService',
function($scope, dispositivosService) {
//This fetches data from Mongo...
$scope.getDispositivos = function () {
dispositivosService.getDispositivos(function(response) {
$scope.dispositivos = response.data;
});
};
//... and on page load it fills in the list
$scope.getDispositivos();
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo);
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
};
$scope.removeDispositivo = function (id) {
dispositivosService.removerDispositivo(id);
$scope.getDispositivos(); //... here
};
$scope.editDispositivo = function (id) {
dispositivosService.editDispositivo(id);
$scope.getDispositivos(); //... and here.
};
}
]
);
On service
this.getDispositivos = function(response) {
return $http.get('http://localhost:3000/dispositivos');
}
on controller
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo).then(function(){
$scope.getDispositivos(); //it should reload the view here...
$scope.dispositivo = '';
});
};
None of the solutions worked. Later on I found that the GET request does execute, asynchronously however. This means that it loads the data into $scope before the POST request has finished, thus not including the just-included new data.
The solution is to synchronize the tasks (somewhat like in multithread programming), using the $q module, and to work with deferred objects and promises. So, on my service
.factory('dispositivosService',
['$http', '$q',
function($http, $q) {
return {
getDispositivos: function (id) {
getDef = $q.defer();
$http.get('http://myUrlAddress'+id)
.success(function(response){
getDef.resolve(response);
})
.error(function () {
getDef.reject('Failed GET request');
});
return getDef.promise;
}
}
}
}
])
On my controller:
$scope.addDispositivo = function() {
dispositivosService.addDispositivo($scope.dispositivo)
.then(function(){
dispositivosService.getDispositivos()
.then(function(dispositivos){
$scope.dispositivos = dispositivos;
$scope.dispositivo = '';
})
});
};
Being my 'response' object a $q.defer type object, then I can tell Angular that the response is asynchronous, and .then(---).then(---); logic completes the tasks, as the asynchronous requests finish.
I have an angular service class : -
angular.module('triggerTips')
.service('userData', function ($rootScope, $http, $log, $firebase) {
this._log = {
service : 'userData'
};
// Synchronized objects storing the user data
var config;
var userState;
// Loads the user data from firebase
this.init = function(readyCallback) {
var log = angular.extend({}, this._log);
log.funct = 'init';
var fireRef = new Firebase('https://XYZfirebaseio.com/' + $rootScope.clientName);
config = $firebase(fireRef.child('config')).$asObject();
userState = $firebase(fireRef.child('userState').child($rootScope.userName)).$asObject();
Promise.all([config.$loaded(), userState.$loaded()]).
then(
function() {
if(config == null || Object.keys(config).length < 4) {
log.message = 'Invalid config';
$log.error(log);
return;
}
if(!userState.userProperties) {
userState.userProperties = {};
}
if(!userState.contentProperties) {
userState.contentProperties = {};
}
log.message = 'User Properties: ' + JSON.stringify(userState.userProperties);
$log.debug(log);
log.message = 'Content Properties: ' + JSON.stringify(userState.contentProperties);
$log.debug(log);
log.message = 'Loaded user data from firebase';
$log.debug(log);
readyCallback();
},
function() {
log.message = 'Unable to load user data from firebase';
$log.error(log);
}
);
};
// Returns the initial tip configuration
this.getConfig = function() {
return config;
};
// Set the value of a user property
// A user property is something about the user himself
this.setUserProperty = function(property, value) {
if(!userState.userProperties) {
userState.userProperties = {};
}
userState.userProperties[property] = value;
userState.$save();
$rootScope.$broadcast('user-property-change', property);
};
// Get the value of a user property
this.getUserProperty = function(property) {
if(userState.userProperties) {
return userState.userProperties[property];
}
};
// Set the value of a user content property
// A content property is something about a particular peice of content for a particular user
this.setContentProperty = function(contentName, property, value) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
userState.contentProperties[contentName][property] = value;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Increment a count property on the user state for a given tip
this.incrementContentProperty = function(contentName, property) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
if(!userState.contentProperties[contentName][property]) {
userState.contentProperties[contentName][property] = 0;
}
userState.contentProperties[contentName][property]++;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Returns the user state for a given tip and property
this.getContentProperty = function(contentName, property) {
if(userState.contentProperties) {
var t = userState.contentProperties[contentName];
if(t) {
return t[property];
}
}
};
});
I am trying to unit test this service using jasmine:-
my unit test is :-
'use strict';
describe('Service: userData', function () {
// load the service's module
beforeEach(function() {
module('triggerTips');
});
// instantiate service
var userData;
beforeEach(inject(function (_userData_) {
userData = _userData_;
}));
it('should load correctly', function () {
expect(!!userData).toBe(true);
});
describe('after being initialized', function () {
beforeEach(function(done) {
// Unable to get this working because the callback is never called
userData.init(function() {
done();
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
});
it('should have a valid config', function (done) {
setTimeout(function() {
expect(Object.keys(userData.getConfig()).length == 0);
done();
}, 1500);}); }); });
I read about the Asynchronous Support in Jasmine, but as I am rather new to unit testing with JavaScript couldn't make it work.
I am receiving an error :
Async callback was not invoked within timeout specified by
jasmine.DEFAULT_TIMEOUT_INTERVAL
Can somebody help me providing working example of my code with some explanation?
I would suggest that you replace setTimeout with $timeout so as to speed up your spec suite. You will need ngMock to be a part of your spec suite in order to get this working the intended way, but that seems to have already been taken care of looking at your spec. Good stuff.
Then in order to make the async nature of the spec "go away" you would call:
$timeout.flush([delay]) where delay is optional.
If no delay is passed, all the pending async tasks (inside the angular world) will finish what they're doing.
If a delay is passed, all pending tasks within the specified delay will finish. Those outside of the specified delay will remain in a 'pending' state.
With this, you can remove the done callback and write your tests as such:
describe('after being initialized', function () {
var $timeout;
beforeEach(function () {
// Unable to get this working because the callback is never called
userData.init();
inject(function ($injector) {
$timeout = $injector.get('$timeout');
});
}));
it('should have a valid config', function () {
$timeout.flush();
// callback should've been called now that we flushed().
expect(Object.keys(userData.getConfig()).length).toEqual(0);
});
});
What Promise implementation are you using? I see a call to Promise.all but for the sake of continuing on with my answer I'm going to assume it is equivalent to $q.all. Running $timeout.flush should take care of resolving those values.
If you want to write expectations on the rejected/resolved values of a promise in Jasmine, I would look into something like jasmine-promise-matchers to make it clean and pretty, but barring that you could do something like this:
// $q
function get () {
var p1 = $timeout(function () { return 'x'; }, 250);
var p2 = $timeout(function () { return 'y'; }, 2500);
return $q.all([p1, p2]);
}
// expectation
it('is correct', function () {
var res;
get().then(function (r) {
res = r;
});
$timeout.flush(2500);
expect(res).toEqual(['x', 'y']);
});
Depending on your setup, you may or may not have to stub out/spy on (depending on your frameworks definition of a spy) the promise in relation to your local config variable, but that's another story altogether I reckon.
I am not at all familiar with $firebase(something).$asObject.$loaded - as such I may have missed something here, but assuming it works 'just like any other promise' you should be good to go.
jsfiddle
I'm using a controller to load product data into an $rootScope array. I'm using $http service and works fine, but now I have a new function which fetch the number of products to be loaded. I can't use the function cause the response is slow.
I was wondering if I could use a provider to load the number of products to fetch in the config method before the apps start. And if I could move the $rootScope array to one service. I don't understand Angular docs, they are not really useful even the tutorial at least in providers and services...
app.controller('AppController', [ '$rootScope', '$http', function ( $rootScope,$http) {
$rootScope.empty = 0;
$rootScope.products = [];
$rootScope.lastId = 0;
$rootScope.getLastID = function () {
$http.get("app_dev.php/api/products?op=getLastId").success(function (data) {
$rootScope.lastId = data.lastId;
});
};
$rootScope.getProducts = function () {
if ($rootScope.empty === 0) {
for (i = 1; i < 100; i++) {
$http.get("app_dev.php/api/product/" + i).success(function (data) {
$rootScope.products.push(data);
});
}
}
$rootScope.empty.productos = 1;
};
}
I have done this with factory and service but is not working.
app.factory('lastProduct', ['$http', function lastProductFactory($http) {
this.lastId;
var getLast = function () {
$http.get("app_dev.php/api/products?op=getLastId").success(function (data) {
lastId = data.lastId;
});
return lastId;
};
var lastProduct = getLast();
return lastProduct;
}]);
function productList($http, lastProduct) {
this.empty = 0;
this.lastId = lastProduct();
this.products = []
/*this.getLast = function () {
lastId = lastProduct();
};*/
this.getProducts = function () {
if (empty === 0) {
for (i = 1; i < lastId; i++) {
$http.get("app_dev.php/api/product/" + i).success(function (data) {
products.push(data);
});
}
}
empty = 1;
return products;
};
}
app.service('productsList', ['$http', 'lastProduct' , ProductsList]);
services are not availables during configuration time, only providers hence you can not use $http to get a value inside the configuration block, but you can use the run block,
you can do
angular.module('app',['dependencies']).
config(function(){
//configs
})
.run(function(service){
service.gerValue()
})
setting the retrieved value inside a service or inside a value is a good idea to avoid contaminate the root scope, and this way the value gets retrieved before the services are instantiated and you can inject the retrieved value as a dependency
Making that many small $http requests does not seem like a good idea. But using a factory to store an array of data to be used across controllers would look something like this. To use a factory you need to return the exposed api. (The this style is used when using a service. I suggest googling the different but I prefer factories). And if you need to alert other controllers that data has changed you can use events.
angular
.module('myApp')
.factory('myData', myData);
function myData($http, $rootScope) {
var myArray = [], lastId;
return {
set: function(data) {
$http
.get('/path/to/data')
.then(function(newData) {
myArray = newData;
$rootScope.$broadcast('GOT_DATA', myArray);
})
},
get: function() {
return myArray
},
getLast: function() {
$http
.get('/path/to/data/last')
.then(function(last) {
lastId = last;
$rootScope.$broadcast('GOT_LAST', lastId);
})
}
}
}
And then from any controller you can inject the factory and get and set the data as you see fit.
angular
.module('myApp')
.controller('MainCtrl', MainCtrl);
function MainCtrl($scope, myData) {
$scope.bindableData = myData.get(); // get default data;
$scope.$on('GOT_DATA', function(event, data) {
$scope.bindableData = data;
})
}
I hope this helps. Let me know if you have any questions.
I done this but not working . rootScope total is undefined when set method is called from some controller.
http://imgur.com/qEl5WV5
But using 10 instead rootscope total
http://imgur.com/t6wB3JZ
I could see that the rootScope total var arrive before the others...
(app_dev.php/api/productos?op=ultimaIdProductos) vs (app_dev.php/api/producto/x)
var app = angular.module('webui', [$http, $rootScope]);
app.run (function ($http, $rootScope){
$http.get("app_dev.php/api/products?op=getLastId").success(function (data) {
$rootScope.total = data.ultima;
});
});
function myData($http, $rootScope) {
var myArray = [];
return {
set: function () {
console.log($rootScope.total);
for (i = 1; i < $rootScope.total; i++) {
$http.get("app_dev.php/api/product/" + i).success(function (data) {
myArray.push(data);
})
}
},
get: function () {
return myArray;
}
}
}
app.controller('AppController', ['$http', '$rootScope', 'myData', function ($http, $rootScope, myData) {
$rootScope.productos = [];
$rootScope.getProductos = function () {
console.log($rootScope.total);
myData.set();
$rootScope.productos = myData.get();
};
}]);
I would like to make a service which loads a JSON file and the provide some methods to work with the result.
Blog.service('ArticleService', ['$http', function ($http) {
this.loadArticles = function() {
return $http.get('data/articles.json');
};
this.getArticles = function () {
// return the json
};
this.getArticle = function (id) {
// work with the json
};
}]);
And the controller:
Blog.controller('BlogController', function ($scope, ArticleService) {
console.log(ArticleService.getArticles());
console.log(ArticleService.getArticle(1));
});
I'd like to cache the result of my request and then work with this result in my methods, getArticles and getArticle.
Following your comment, if you want to do only 1 request, then I'll suggest saving the $http promise in a variable in your service and returning that variable in getArticles.
Blog.service('ArticleService', ['$http', function ($http) {
this.loadArticles = function() {
return $http.get('data/articles.json');
};
var articles;
this.getArticles = function () {
if (!articles) {
articles = this.loadArticles();
}
return articles;
};
this.getArticle = function (id) {
// work with the json
};
}]);
Or better yet, load the articles variable on init:
Blog.service('ArticleService', ['$http' '$q', function ($http, $q) {
var articles = (function() {
return $http.get('data/articles.json');
})();
this.getArticles = function () {
return articles;
};
this.getArticle = function (id) {
// Return a promise that will be resolved with the correct article.
var deferred = $q.defer();
articles.then(function (arts) {
arts.forEach(function (art) {
if (art.id === id) {
deferred.resolve(art);
}
});
});
return deferred.promise;
};
}]);
Angular used to unwrap promises, but that was deprecated because it proved to be troublesome and very "magical" (leading to misunderstandings). See https://github.com/angular/angular.js/issues/5153
Alternatively, you could use $http's cache property:
Blog.service('ArticleService', ['$http', function ($http) {
this.getArticles = function() {
return $http.get('data/articles.json', {cache: 'myCache'});
};
this.getArticle = function (id) {
return $http.get('data/articles.json', {cache: 'myCache'}).then(function(response) {
// Parse the response, return the article in question.
});
};
}]);
Blog.service('ArticleService', ['$http', '$q', function ($http, $q)
{
var self = this;
this.loadArticles = function ()
{
var deffered = $q.defer();
$http.get("data/articles.json").then(
function (result)
{
deffered.resolve(result);
},
function ()
{
deffered.reject()
}
);
return deffered.promise;
};
this.getArticles = function ()
{
return self.loadArticles;
};
this.getArticle = function (id)
{
// some stuff to retrieve the object
};
}]);
then in your controller
Blog.controller('BlogController', function ($scope, ArticleService)
{
ArticleService.getArticles().then(function (data)
{
console.log(data);
}, then(function ()
{
alert("error")
})
);
});
});
If you want your cache to persist from session to session, then see user3904's answer.
If it is acceptable for the cache to die with the page, then you might consider the following approach :
Blog.service('ArticleService', ['$http', '$q', function ($http, $q) {
var articles = {};
var loadArticles = function () {
articles.multi = $http.get("data/articles.json").done(function(a) {
articles.multi = a;
});
return articles.multi;
};
var loadArticle = function (id) {
articles[id] = $http.get("data/articles.json/" + id).done(function(a) {
articles[id] = a;
});
return articles[id];
};
this.getArticles = function () {
return articles.multi ? $q.when(articles.multi) : loadArticles();
};
this.getArticle = function(id) {
id = '' + id;
return articles[id] ? $q.when(articles[id]) : loadArticle(id);
};
}]);
Explanation
The cache is the js plain object articles.
The two load...() functions are private - assuming, as seems reasonable, that they are to be called only by their corresponding get...() functions. (This is not particularly significant to the overall solution).
On first call, the two loadArticles() caches a promise, which is overwritten with the data delivered by the promise, when that data arrives. The promise is temporarily cached for one reason - in case a second request is made before the data is returned from the server. The $q.when(...) in this.getArticles() ensures that loadArticles() returns promise-wrapped data, regardless of what is currently cached - promise or data.
this.getArticle() works similarly to this.getArticles(), but accepts (and passes) an id.
This strategy is designed to be storage efficient. By caching only the data long term, the (small) overhead of promise wrappers is avoided. This might be an issue if users are likely to request many individual articles per session.
Storage efficiency is bought at the cost of slightly less efficient delivery of cached articles. $q.when(...) will take some extra clock cycles to complete.
It should be quite simple to adapt the code to work the other way round - ie to optimise for delivery at the cost of storage efficiency - ie to store promises long term and avoid the need for $q.when(...).
Both approaches will work and for most applications it's pretty academic which one is adopted.
I currently have an angular application which upon user login calls a service to begin a server call to refresh a count, only allowing for a server side return if the user is authenticated.
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
return resource.query({
username: username,
q: 'approvalsCount'
}).$then(function (response) {
resource.approvalsCount = response.data.count;
approvalsCountTimer = $timeout(resource.getApprovalsCount, 3000);
return resource.approvalsCount;
});
};
When a user logs out I am attempting to cancel that counter otherwise the server will return a 401 unauthorized error by calling a function based on the resource:
resource.cancelTimers = function () {
$timeout.cancel(approvalsCountTimer);
}
The issue is that the counter continues to run even after I call the cancel upon the $timeout which returns a 401 from the server. Console logging out the return the cancel function returns true (cancel has worked). I have tried several different placements of the begin of the $timeout to no avail, is there a way to ensure that all of the $timeouts are canceled? I don't understand what I am missing in this configuration.
EDIT
angular.module('resources.approvals.approvals', ['ngResource'])
.factory('Approvals', ['$timeout', '$resource', function ($timeout, $resource) {
var resource = $resource('/approvals/:username/:requestID', {}, {
'update': {
method: 'PUT'
}
});
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
return resource.query({
username: username,
q: 'approvalsCount'
}).$then(function (response) {
resource.approvalsCount = response.data.count;
approvalsCountTimer = $timeout(resource.getApprovalsCount, 3000);
return resource.approvalsCount;
});
};
resource.cancelTimers = function () {
$timeout.cancel(approvalsCountTimer);
};
return resource;
}]);
I think your code looks good. It got to be something else.
I simplified a bit and you can see it on the demo. it simulates the http call every half second and the cancelTimes will be called in 4 seconds.
app = angular.module('app', []);
app.factory('Approvals', ['$timeout', function ($timeout) {
var resource = {};
resource.approvalsCount = 0;
var approvalsCountTimer;
resource.getApprovalsCount = function (username) {
console.log(approvalsCountTimer);
approvalsCountTimer = $timeout(resource.getApprovalsCount, 500);
};
resource.cancelTimers = function () {
console.log("stopped");
$timeout.cancel(approvalsCountTimer);
};
return resource;
}]);
function Ctrl($scope, $timeout, Approvals) {
Approvals.getApprovalsCount();
$timeout(Approvals.cancelTimers, 4000)
}