Feeling that my AngularJS Controller relies on one Promise - angularjs

I have made 2 promises in my service. The first one connects to the websocket
connect : function() {
var deferred = $q.defer();
var promise = deferred.promise;
this.socket = new WebSocket('ws://localhost');
this.socket.addEventListener('open', function (e) {
deferred.resolve("connected")
});
return promise;
}
and the second one sends a message over the websocket
send: function (msg, scope) {
var deferred = $q.defer();
var promise = deferred.promise;
this.socket.send(angular.toJson(msg))
return promise
}
In my controller i want to send only when the connection is open. To accomplish that my code looks like this
myService.connect()
.then(function() {
var myData = myService.send(data);
}
myData.then(function(d) {
// do something with the data
});
It works this way, but why do i have the feeling that this is not the way to be done? I'm pretty new to angularJS and Promises. I feels like creating a new promise inside of a .then of another promise is dirty coding.
Found this great explanation that does the exact same thing, but still i'm not convinced. If i want to send 20 messages over the websocket, it's like i physically work inside that one myService.connect promise the whole controller.
myService.connect()
.then(function() {
var myData = myService.send(data);
}
.then(function() {
var myData = myService.send(data1);
}
.then(function() {
var myData = myService.send(data2);
}
.then(function() {
var myData = myService.send(data3);
}

Related

how to use $q.all for async forEach requests?

I need to call the function after 2 request is done. So I try to use $q.all for this. I have service on Angular it calls submitForm, before submit I need to do 2 another request, and after they response - make post request to submit form. It looks like this:
submitForm : function(form, params) {
var self = this;
var callback = function () {
// this is submit post req
};
angular.forEach(form, function(item) {
// I search tables (type of field) and then send req for saving tables on backend
self.uploadTables(item)
})
this.uploadFiles(params).then(callback)
}
uploadFiles : function(params) {
// this func upload files before submit
}
uploadTables : function(params) {
// for uploading tables I have another service on backend, so I need another request, before submit
}
In $q.all I guess I need to call it like $q.all(this.uploadFiles, this.uploadTables)? But I cant do that couse uploadTables I call in forEach. How can I call callback function after complete uploadFiles and uploadTables?
var promises = [];
angular.forEach(form, function(item) {
promises.push(self.uploadTables(item).then(uploadTableCallback));
})
promises(this.uploadFiles(params).then(uploadFilesCallback));
$q.all(promises).then(allCallback)
I want also to notice that there is nothing special in $q.all and you can always do it manually:
var promisesToResolve = arr.length;
var promisesResolve = 0;
angular.forEach(arr, function(obj) {
$http.post(obj).then(function() {
promisesResolve++;
if (promisesResolve == promisesToResolve) {
// all resolved
}
})
})
You can return and collect promises in array
var promises = [];
angular.forEach(form, function(item) {
var deferred = $q.defer();
//do your code
deferred.resolve(result);
promises.push(deferred.promise);
});
// execute all the promises and do something with the results
$q.all(promises).then(
function(results) {
//do your stuff
},
// error
function(response) {
}
);

how to wait until promise returns when using $resource

I have a my controller with an activate method which waits for promises to return before going forward.
function activate() {
var promises = [getNodesGroup(), getNodes()];
common.activateController(promises, controllerId)
.then(function () {
log('Activated');
});
}
Both of the promises had similar approach to get the data and that was using $http in datacontext.getNodeGroups() and datacontext.getNodes() methods.one of which is like this
function getNodesGroup() {
return datacontext.getNodeGroups().then(function (response) {
$scope.nodeGroups = response.data;
//other logic
});
All was working fine until now when i tried to use $resource for one of teh promises (getNodes()). my resource for node is setup like this
(function () {
'use strict';
var nodeServiceRoot = '/api/Nodes';
var GetAllNodesUrl = nodeServiceRoot + '/GetAllNodes';
angular.module('common.service')
.factory("nodeResource",["$resource","appSettings", nodeResource])
function nodeResource($resource, appSettings) {
return $resource(appSettings.serverPath + GetAllNodesUrl);
}}());
and i am trying to consume it like this
function getNodes() {
$scope.nodes = nodeResource.query();
$scope.nodes.$promise.then(function (response) {
$scope.nodes = response;
//other logic
});
i don't know how to return getNodes() so that my activate functions wait for it before proceeding. right now whats happening is that sometime both functions are run when it hits log('Activated'); line of activate functions and some time only one promise with old $http approach (getNodeGroups()) is run but not the other.
well i guess the answer was very simple. i was missing 'return' in getNodes()
so what worked for me is
function getNodes() {
$scope.nodes = nodeResource.query();
**return** $scope.nodes.$promise.then(function (response) {
$scope.nodes = response;
//other logic
});

AngularJS : remove $rootScope from a service

I have a service wrapped around WebSocket, I wanted to do it with promises and coupling requests with responses, here is what I came up with:
(function () {
var app = angular.module('mainModule');
app.service('$wsService', ['$q', '$rootScope', '$window', function($q, $rootScope, $window) {
var self = this;
// Keep all pending requests here until they get responses
var callbacks = {};
// Create a unique callback ID to map requests to responses
var currentCallbackId = 0;
var ws = new WebSocket("ws://127.0.0.1:9090");
this.webSocket = ws;
ws.onopen = function(){
$window.console.log("WS SERVICE: connected");
};
ws.onmessage = function(message) {
listener(JSON.parse(message.data));
};
var listener = function (messageObj) {
// If an object exists with callback_id in our callbacks object, resolve it
if(callbacks.hasOwnProperty(messageObj.Request.ID)) {
$rootScope.$apply(
callbacks[messageObj.Request.ID].cb.resolve(messageObj));
delete callbacks[messageObj.Request.ID];
}
};
// This creates a new callback ID for a request
var getCallbackId = function () {
currentCallbackId += 1;
if(currentCallbackId > 10000) {
currentCallbackId = 0;
}
return currentCallbackId;
};
//sends a request
var sendRequest = function (request, callback) {
var defer = $q.defer();
var callbackId = getCallbackId();
callbacks[callbackId] = {
time: new Date(),
cb:defer
};
request.ID = callbackId;
$window.console.log("WS SERVICE: sending " + JSON.stringify(request));
ws.send(JSON.stringify(request));
if(typeof callback === 'function') {
defer.promise.then(function(data) {
callback(null, data);
},
function(error) {
callback(error, null);
});
}
return defer.promise;
};
this.exampleCommand = function(someObject, callback){
var promise = sendRequest(someObject, callback);
return promise;
};
}]);
}());
And I use it in a controller like so:
(function () {
'use strict';
var app = angular.module('mainModule');
app.controller('someController', ['$scope', '$wsService', function ($scope, $wsService) {
$scope.doSomething = function(){
$wsService.exampleCommand(
{/*some data for the request here*/},
function(error, message){
//do something with the response
}
);
};
}]);
}());
After implementing this, I have been told that the service should not really operate on any kind of scope. So my question is - how would I go about removing the $rootScope from the service? I am not even sure if I should get rid of it, and if the conventions say I should, how to approach it. Thanks
I have been told that the service should not really operate on any kind of scope.
Who told you that? It's completely wrong.
Your service is receiving callbacks outside of a digest cycle from the websocket. To work with angular, those updates need to be applied inside a digest cycle - this is exactly what you're doing.
For reference, see the built in $http service. That wraps XMLHttpRequest analogously to how you're wrapping web sockets and it depends on $rootScope for exactly the functionality your code depends on $rootScope for.
Your code demonstrates the canonical use of $rootScope inside a service.

Angularjs promise with jquery ajax

The factory sends data to the server and its processed correctly but after that the ".then" in the controller is not being called below.
Kindly let me know why the "then" part is not being executed in the controller here after the successful ajax call.
factory
myapp.factory('startCampFactory',function($http,$q,$rootScope){
return {
startNewCampaign : function(){
var e = $("input#email");
var email = e.val();
var campname = $("input#campaignname").val();
var about = $("textarea#about").val();
var tamt = $("input#targetamount").val();
var edate = $("input#enddate").val();
var invitees = $("input#invitees").val();
var file_data = $("#file").prop("files")[0];
var form_data = new FormData();
form_data.append("file",file_data);
form_data.append("email",email);
form_data.append("campaignname",campname);
form_data.append("about",about);
form_data.append("targetamount",tamt);
form_data.append("enddate",edate);
form_data.append("invitees",invitees);
console.log(email+about+campname);
var deferred = $q.defer();
$.ajax({
type:'POST',
url: "http://localhost:8080/startcampaign",
data:form_data,
contentType:false,
processData:false,
cache:false,
dataType:"json",
success:function(msg,status)
{
//if(status=="success")
deferred.resolve("success");
$rootScope.$apply();
},
error:function()
{
deferred.reject();
$rootScope.$apply();
}
});
return deferred.promise;
}
}
});
conrtoller
function startCampCtrl($scope,startCampFactory)
{
$scope.startcamp = function(){
$("#submit").prop('disabled',true);
startCampFactory.startNewCampaign().then(function(d){
alert("here");
var temp = "<div class=\"alert alert-dismissable alert-success\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\">×</button> <strong>Campaign Saved successfully</strong></div>";
$(temp).prependTo("#startcamp");
$("#submit").prop('disabled',false);
$("input#campaignname").val('');
$("textarea#about").val('');
$("input#targetamount").val('');
$("input#enddate").val('');
$("input#invitees").val('');
$("input#file").val('');
},
function(){//On error
var temp = "<div class=\"alert alert-dismissable alert-warning\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-hidden=\"true\">×</button> <strong>Campaign could not be saved, please try again</strong></div>";
$(temp).prependTo("#startcamp");
$("#submit").prop('disabled',false);
});
}
}
You're using $.ajax() to trigger the call. The correct way to do it is to use the $http service. When a call is made through that service an $apply is automatically trigerred , and all your promises will get executed in that $apply cycle.
If you want to trigger the promises from your success function inside the $.ajax() call,
I suppose you can do it inside an $apply cycle:
.....
success:function(msg,status){
$rootScope.$apply( function() {
deferred.resolve("success");
});
}
....
Here is the working fiddle with correct way to invoke promise

AngularJS : chaining http promises $q in a service

i have problems when it comes to $http promises in angularjs. i am doing this in my service: (the getSomething function should chain two promises)
the second function uses a external callback function!
app.service('blubb', function($http, $q) {
var self = this;
this.getSomething = function(uri, data) {
return self.getData(uri).then(function(data2) {
return self.compactData(uri, data2);
});
};
this.getData = function(uri) {
var deferred = $q.defer();
$http.get(uri).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject();
});
return deferred.promise;
};
this.compactData = function(uri, data) {
var deferred = $q.defer();
/* callback function */
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
console.log(err);
deferred.reject(err);
}
/* end of function */
return deferred.promise;
};
});
when i use the service in my controller it doesn't output the console.log:
blubb.getSomething(uri, input).then(function(data) {
console.log(data)
});
edit:
if i define the callback function by myself in 'compactData' it works, but i am using "jsonld.compact" from https://raw.github.com/digitalbazaar/jsonld.js/master/js/jsonld.js and THIS doesn't work!
jsonld.compact(input, context, function(err, compacted) {
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
deferred.reject('JSON-LD compacting');
}
});
i am getting the console.log output in jsonld.compact but the resolve doesn't work and i don't know why..
it only works with $rootScope.$apply(deferred.resolve(compacted));
I'm using chaining promises like this:
$http.get('urlToGo')
.then(function(result1) {
console.log(result1.data);
return $http.get('urlToGo');
}).then(function(result2) {
console.log(result2.data);
return $http.get('urlToGo');
}).then(function(result3) {
console.log(result3.data);
});
Chaining promises works here : jsfiddle
In your implementation, if $http.get or compactData goes wrong your console.log(data) will not be call.
You should maybe catch errors :
blubb.getSomething(uri, input).then(function(data) {
console.log(data);
}, function(err) {
console.log("err: " + err);
});
Whenever you use an external (external to AngularJS) callback that runs in a new turn/tick, you have to call $apply() on the appropriate scope after it has been invoked. This lets AngularJS know it has to update. You'll probably want to make sure you're only calling it once -- after all of the promises have been resolved. As an aside, jsonld.js provides a promises/future API, so if you're already using promises, you don't have to do that wrapper code above. Instead you can do:
var promisesApi = jsonld.promises();
var promise = promisesApi.compact(input, context);
// do something with the promise
I would suggest you to use a Factory instead of a service.
Just return the function from the factory and use it in your controller

Resources