Why do my socket.on calls multiply whenever i recenter my controller - sockets

I've been using the socket factory described here by the brian ford here
http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets/
here is the factory
myApp.factory('socket', function ($rootScope) {
var socket = io.connect('url');
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
});
I have a socket.emit in my controllers initialization function and whenever i reenter this controller from another page the receiving socket.on function executes +1 times. This happens until I manually refresh the page then it resets to 1. I don't explicitly store my socket in session. So what could be causing my socket.on to call multiple times.
Here is my socket.emt in my controller
this always executes once.
$scope.init = funciton (){
...
socket.emit('getSignedSlidesFromUrl', $scope.slideLocation);
}
Here is my socket.on that will be recieve 'getSignedSlidesFromUrl'
socket.on('signedUrls', function (signedSlides){
console.log('signedUrls socket hit');
$scope.slides = signedSlides;
console.log($scope.slides[0]);
console.log($scope.display);
});
Here is an example of my console log after reentering the controller
about to emit getSignedSlidesFromUrl from init controllers.js:71
display after called $scope.first slide is0 controllers.js:574
flash object is controllers.js:537
signedUrls socket hit controllers.js:816
0 controllers.js:823
signedUrls socket hit controllers.js:816
0 controllers.js:823
if I reenter the controller again my log would change to
signedUrls socket hit controllers.js:816
0 controllers.js:823
signedUrls socket hit controllers.js:816
0 controllers.js:823
signedUrls socket hit controllers.js:816
0 controllers.js:823

You have to add removeAllListeners to your factory (see below) and have the following code in each of your controllers:
$scope.$on('$destroy', function (event) {
socket.removeAllListeners();
});
Updated socket factory:
var socket = io.connect('url');
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
},
removeAllListeners: function (eventName, callback) {
socket.removeAllListeners(eventName, function() {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
}
};
});

I tried #michaeljoser's solution, but it didn't work for me.
Then I found another solution and it worked
var socket = io.connect('url');
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
},
getSocket: function(){
return socket;
}
};
});
And in controller, I called
$scope.$on('$destroy', function (event) {
socket.getSocket().removeAllListeners();
});

Related

If use async/await, it transpiles code above my "imports" and makes my func undefined

I currently have this code:
// Imports
const {utils: Cu, Constructor: CC} = Components;
Cu.import('resource://gre/modules/Services.jsm');
Services.scriptloader.loadSubScript('chrome://trigger/content/webextension/scripts/3rd/polyfill.min.js');
function install() {}
function uninstall() {}
function shutdown() {}
function startup() { // this gets automatically called by the environment i am designing for
Services.prompt.alert(Services.wm.getMostRecentWindow('navigator:browser'), 'title', 'body');
}
async function doAsync() {
return await new Promise(() => setTimeout(()=>resolve('done'), 5000));
}
However after compiling with babel with this .babelrc:
{
"presets": ["es2015", "es2017"],
"plugins": ["transform-object-rest-spread"],
"ignore": [
"3rd/**/*",
]
}
It moves my doAsync function to the top. Before the imports. Which is a problem. Because the import needs to happen right away. As startup gets called right away.
The compiled code becomes this:
'use strict';
var doAsync = function () {
var _ref = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return new Promise(function () {
return setTimeout(function () {
return resolve('done');
}, 5000);
});
case 2:
return _context.abrupt('return', _context.sent);
case 3:
case 'end':
return _context.stop();
}
}
}, _callee, this);
}));
return function doAsync() {
return _ref.apply(this, arguments);
};
}();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }
// Imports
var _Components = Components,
Cu = _Components.utils,
CC = _Components.Constructor;
Cu.import('resource://gre/modules/Services.jsm');
Services.scriptloader.loadSubScript('chrome://trigger/content/webextension/scripts/3rd/polyfill.min.js');
function install() {}
function uninstall() {}
function shutdown() {}
function startup() {
// this gets automatically called by the environment i am designing for Services.prompt.alert(Services.wm.getMostRecentWindow('navigator:browser'), 'title', 'body');
}
Is there anyway so that if I use async/await that it doesn't move stuff to above my imports? If my imports at the top of the file, it fails to run. Because the imports brings in the polyfill.min.js

Index out of bounds when using element.all(by.binding)

User flow: Search for a case >> Search for an item within that case >> expect returned number of results matches value
describe('Search', function () {
beforeEach(function () {
loginPage.signIn();
loginPage.login(username, password);
});
afterEach(function () {
homePage.logOut();
});
it('alecxes suggestion code', function () {
var presenceOfAll = function(elementArrayFinder) {
return elementArrayFinder.count().then(function (count) {
return count > 0;
});
};
homePage.searchForCase(test_case);
filterSearchMenu.searchWithinCase(search_argument);
var hits = element.all(by.binding('response.hits.total'));
browser.wait(presenceOfAll(hits), TIMEOUT);
expect(element.all(by.binding('response.hits.total')).count()).toEqual(4);
});
});
This fails with expected 4 but was 0
describe('Search', function () {
searchResultTotal = element(by.binding('response.hits.total'));
beforeEach(function () {
loginPage.signIn();
loginPage.login(username, password);
});
afterEach(function () {
homePage.logOut();
});
it('should be able to search', function () {
homePage.searchForCase(test_case);
filterSearchMenu.searchWithinCase(search_argument);
browser.wait(EC.visibilityOf(searchResultTotal), TIMEOUT).then(function () {
expect(element.all(by.binding('response.hits.total')).count()).toEqual(4);
});
});
});
This works but comes back with a warning that more then one element found for locator by.binding('response.hits.total').
describe('Search', function () {
beforeEach(function () {
loginPage.signIn();
loginPage.login(username, password);
});
afterEach(function () {
homePage.logOut();
});
it('should be able to search', function () {
homePage.searchForCase(test_case);
filterSearchMenu.searchWithinCase(search_argument);
browser.wait(EC.visibilityOf(element.all(by.binding('response.hits.total')).first()), TIMEOUT).then(function () {
expect(element.all(by.binding('response.hits.total')).count()).toEqual(4);
});
});
});
This Fails and throws an index out of bounds.
Second set of eyes and any help would be appreciated.
You can also solve it with a custom Expected Condition that would check if there are more than 0 elements matching a locator found:
var presenceOfAll = function(elementArrayFinder) {
return elementArrayFinder.count().then(function (count) {
return count > 0;
});
};
var hits = element.all(by.binding('response.hits.total'));
browser.wait(presenceOfAll(hits), TIMEOUT);
expect(element.all(by.binding('response.hits.total')).count()).toEqual(4);
I'm thinking visibilityOf doesn't like multiple elements. Try:
browser.wait(EC.visibilityOf(element.all(by.binding('response.hits.total')).first()), TIMEOUT);

ionic service init before loading controller

I have service which has asynchronous init of parameter and I want that every controller to wait until the init will be finished.
The problem is that the getObject method is being called by the controller before the init of parameters variable is finished.
The service:
angular.module('core')
.factory('localstorage', ['$window', '$cordovaSQLite', '$q',
function ($window, $cordovaSQLite, $q) {
var db;
var parameters = {};
if (window.cordova) {
db = $cordovaSQLite.openDB({name: "my.db"}); //device
} else {
db = window.openDatabase("my.db", '1', 'life', 1024 * 1024 * 100);
}
var promise = $q(function (resolve, reject) {
$cordovaSQLite.execute(db, "SELECT * FROM life_table")
.then(function (res) {
if (res.rows.length == 0) {
$cordovaSQLite.execute(db,
"CREATE TABLE IF NOT EXISTS life_table (key text primary key, value text)");
}
else {
for (var i = 0; i < res.rows.length; ++i) {
parameters[res.rows[i].key] = JSON.parse(res.rows[i].value);
}
}
resolve(parameters);
});
});
return {
promise: promise,
getObject: function (key) {
var query = "SELECT value FROM life_table where key = ?";
$cordovaSQLite.execute(db, query, [key]).then(function (res) {
if (res.rows.length > 0) {
console.log("getObject: " + res.rows[0].value);
}
}, function (err) {
console.error(err);
});
return parameters[key];
}
}
}]);
the config:
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('navigator', {
url: '/',
abstract: true,
controller: 'NavigatorCtrl',
resolve: {
'MyServiceData': function (localstorage) {
// localstorage will also be injectable in the controller
return localstorage.promise;
}
}
})
.state('login', {
url: '/login',
templateUrl: 'templates/login.html',
controller: 'LoginCtrl',
controllerAs: 'loginCtrl',
resolve: {
'MyServiceData': function (localstorage) {
// localstorage will also be injectable in the controller
return localstorage.promise;
}
}
})
Shalom Dor!
Option 1
Make a chain of promises and return the factory only after all promises are resolved. This will prevent the controller from running as the factory dependency will not be returned until the desired promises are resolved.
Option 2
Create some sort initialization function in your service that returns a promise (you can use $q). Execute it inside $ionicPlaform.ready() and only run the controller logic after you got the promise.
In the controller:
MyService.initialize().then(function () {
// Service initialized, do stuff...
});
In the service:
returned.initialize = function () {
var d = q.defer();
// Do stuff...
// Resolve 'd' when you finished initializing the service using 'd.resolve()'.
return d.promise;
};

Protractor specs leaking

I'm still quite new to promises and the like and I need some help with this problem. One of my it blocks does not end before the next one begins ending up in a StaleElementReferenceError a whole specfile later from where the code was supposed to be called.
listView.js (I know it looks weird but I set it up this way for an unrelated reason):
module.exports = function () {
var public = {};
public.checkFilters = function (filters) {
var promises = [];
for (var i = 0; i < filters.length; i++) {
promises[i] = getFilterPromise(filters[i]);
}
return protractor.promise.all(promises);
};
var getFilterPromise = function (filter) {
return public.getHeaderIndex(filter.on).then(function (headerIndex) {
return checkRows(filter.values, headerIndex);
});
};
public.getHeaderIndex = function (text) {
var headers = table.all(by.tagName('th'));
var correctHeaderIndex;
return headers.each(function (header, index) {
header.getText().then(function (actualHeaderText) {
if (actualHeaderText === text) {
correctHeaderIndex = index;
}
})
}).then(function () {
return new Promise(function (resolve, reject) {
if (correctHeaderIndex) {
resolve(correctHeaderIndex);
} else {
reject('Header not found');
}
});
});
};
public.getWorkflowCount = function () {
return workflows.count();
};
var checkRows = function (matchers, headerIndex) {
var mismatch = false;
return workflows.each(function (element, index) {
public.getTextFromCell(index, headerIndex).then(function (actual) {
if (!anyMatch(actual, matchers)) {
mismatch = true;
}
});
}).then(function () {
return new Promise(function (resolve, reject) {
if (mismatch) {
reject('Header not found');
} else {
resolve('all rows matched');
}
});
});
};
var anyMatch = function (actual, matchers) {
var match = false;
for (var j = 0; j < values.length; j++) {
if (text === values[j]) {
match = true;
}
}
return match;
};
public.getTextFromCell = function (row, column) {
return workflows.get(row).all(by.tagName('td')).get(column).getText();
};
return public;
}();
LV_00:
describe('LV_00:', function () {
it('statusfilter', function () {
P.listView.filter('status', H.regStatus.S.inProgress);
});
it('statusfilter works', function () {
P.listView.checkFilters([{
on: H.lang.S.status,
values: [H.regStatus.S.inProgress]
}]);
});
});
I think you should move the test preparation code into the beforeEach():
describe('LV_00:', function () {
beforeEach('statusfilter', function () {
P.listView.filter('status', H.regStatus.S.inProgress);
});
it('statusfilter works', function () {
P.listView.checkFilters([{
on: H.lang.S.status,
values: [H.regStatus.S.inProgress]
}]);
});
});
You may also need to use the done callback function:
describe('LV_00:', function (done) {
beforeEach('statusfilter', function () {
P.listView.filter('status', H.regStatus.S.inProgress).then(function () {
done();
});
});
it('statusfilter works', function () {
P.listView.checkFilters([{
on: H.lang.S.status,
values: [H.regStatus.S.inProgress]
}]);
});
});
assuming filter() returns a promise.
Found the solution thanks to alecxe proposing to use done() I used the following after some googling around.
it('statusfilter', function () {
P.listView.filter('status', H.regStatus.S.inProgress);
});
it('statusfilter works', function () {
protractor.promise.controlFlow().execute(function () {
return P.listView.checkFilters([{
on: H.lang.S.status,
values: [H.regStatus.S.inProgress]
}]);
});
});
Found here: Prevent Protractor from finishing before promise has been resolved

KnockoutJS : initial values are not posted to server when using ko.toJSON(this)?

I've this javascript viewmodel defined:
function PersonViewModel() {
// Data members
this.Name = ko.observable();
this.Function_Id = ko.observable();
this.SubFunction_Id = ko.observable();
this.Functions = ko.observableArray();
this.SubFunctions = ko.observableArray();
// Whenever the Function changes, update the SubFunctions selection
this.Function_Id.subscribe(function (id) {
this.GetSubFunctions(id);
}, this);
// Functions to get data from server
this.Init = function () {
this.GetFunctions();
this.Function_Id('#(Model.Function_Id)');
};
this.GetFunctions = function () {
var vm = this;
$.getJSON(
'#Url.Action("GetFunctions", "Function")',
function (data) {
vm.Functions(data);
}
);
};
this.GetSubFunctions = function (Function_Id) {
var vm = this;
if (Function_Id != null) {
$.getJSON(
'#Url.Action("GetSubFunctions", "Function")',
{ Function_Id: Function_Id },
function (data) {
vm.SubFunctions(data);
}
);
}
else {
vm.SubFunction_Id(0);
vm.SubFunctions([]);
}
};
this.Save = function () {
var PostData = ko.toJSON(this);
var d = $.dump(PostData);
alert(d);
$.ajax({
type: 'POST',
url: '/Person/Save',
data: PostData,
contentType: 'application/json',
success: function (data) {
alert(data);
}
});
};
}
$(document).ready(function () {
var personViewModel = new PersonViewModel();
personViewModel.Init();
ko.applyBindings(personViewModel);
});
When the Submit button is clicked, the data from the select lists is posted, but NOT the 'Function_Id'.
When I choose a different value in the Function dropdown list, and the click the Submit button, the value for 'Function_Id' is correctly posted.
How to fix this ?
It's because the scope of the this keyword in javascript
this.Init = function () {
this.GetFunctions(); // this === PersonViewModel.Init
this.Function_Id('#(Model.Function_Id)'); // calls PersonViewModel.Init.Function_Id(...)
};
You should store the refrence to the PersonViewModel instance.
var self = this;
self.Init = function () {
self.GetFunctions();
self.Function_Id('#(Model.Function_Id)'); // calls PersonViewModel.Function_Id(...)
};