Angularjs chat with strange polling - angularjs

I created a simple chat with the frontend in angularjs. It has on the left size a simple index, of all the conversations that user has, with a show(chat_id) action, that opens up on the right side the chat itself, using polling to fetch new messages.
The main piece of the controller that does the functionality described:
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
var poll = function(){
conversation.get(id).then( function( conversation ) {
$scope.chat = conversation.data.messages;
$scope.form = true;
$timeout(function() {poll()}, 5000)
});
}
poll();
}
My problem, is whenever I click on two different conversations quickly, say show(1) and show(2) I get a weird behavior where it switches from conversation 1 to 2, back and forth, with the polling action.
Here is the get requests angular is making.
and for contextualization, here's the chat simple UI

As I said in the comment, every time show() is called, you start a new recursive loop that polls the conversation with the given id every 5 seconds.
So, if you click 4 conversations (1, 2, 3 and 4), you will end up with a refresh of conversation 1 every 5 seconds, another refresh of conversation 2 every 5 seconds, etc.
That's not what you want. Once you show a conversation, you only want to refresh that conversation, not the other ones.
So you could use that following code that cancels the previous timeout every time show() is called, and that verifies that the displayed conversation ID is the right one when getting a response:
var timer;
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
if (timer) {
$timeout.cancel(timer);
}
var poll = function(){
conversation.get(id).then( function( conversation ) {
if ($scope.currentConversation == id) {
$scope.chat = conversation.data.messages;
$scope.form = true;
timer = $timeout(function() {poll()}, 5000);
}
});
}
poll();
}

You probably want to do a check inside your inner function to make sure that $scope.currentConversation === id. That'll prevent it from losing context. My guess is that it's a race condition that you're clicking before the first
conversation.get(id) comes back.
$scope.show = function(id) {
$scope.chat = [];
$scope.currentConversation = id;
var poll = function(){
conversation.get(id).then( function( conversation ) {
if ($scope.currentConversation !== id) return; // Prevent the race condition
$scope.chat = conversation.data.messages;
$scope.form = true;
$timeout(function() {poll()}, 5000)
});
}
poll();
}

Related

Implementing notification alerts in angularjs

I was wondering how an error alert would be implemented using angularjs.
Required functionality:
An alertQueue consists of all the alerts to be displayed to the user. These alerts are deleted from the queue after a span of 3 seconds. The user himself can close the alert by clicking the close button.
This AlertService must be the core service. Alerts are rendered in the view as <alert-list></alert-list>i.e using a component alertList.
Should be able to update alerts from other controllers like: AlertService.alert("my alert").
so far what I have done?
angular.
module('core').
factory('AlertService', [function() {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
};
var deleteAlert = function(alert){
alertQueue.splice(alertQueue.indexOf(alert), 1);
};
return{
warning: function(msg){
addAlert(msg, "warning");
},
success: function(msg){
addAlert(msg, "success");
},
removeAlert: function(alert){
deleteAlert(alert);
},
getAlerts: function(){
return alertQueue;
}
}
}]);
angular.
module('alertApp').
component('alertList', {
templateUrl: '/static/js/app/aurora-alert/aurora-alert.template.html',
controller: ['$routeParams','$scope', 'Aurora',
function AlertController($routeParams, $scope, AlertService) {
var self = this;
self.alertQueue = AlertService.alertQueue;
self.alert = function(){
var message = arguments[0];
AlertService.warning(message);
};
self.removeAlert = function(alert) {
AlertService.removeAlert(alert);
};
}
]
});
I know that I'm doing something wrong in the above code and in its logic. I said above that I require the <alert-list></alert-list> component. So the alertService is injected as a dependency into alertController. But how am I going to raise the alert from other controllers? I know we can use $scope.$broadcast but that doesn't feel right.
Please explain how to achieve this? No third party libraries are to be used.
I think you are going about it only slightly incorrectly. Your alert-list should be responsible only for displaying and removing alerts, not for creating them. Leave the creation of alerts to your controllers
So for example, if you run into an error with an ApiSerivce:
DemoCtrl(AlertService, ApiService) {
ApiService.submitForm({some:data}).then(function() {
//something successfull happened
}).catch(function(error) {
AlertService.warning("Something bad happened calling the API serivce");
});
}
Then you can change your AlertService to broadcast an event when a new alert is created that the alert-list can listen to:
factory('AlertService', ["$rootScope", function($rootScope) {
var alertQueue = [];
var addAlert = function(message, type){
message = {message: message, type: type};
alertQueue.push(message)
$rootScope.$broadcast("new-alert"); //notify the list that there are new alerts
};
This is how you would listen to it in your alert-list:
$scope.$on("new-alert", function() {
self.alertQueue = AlertService.alertQueue;
});
This way, as soon as an alert is created, the alert-list is instantly updated with the latest queue of alerts.
You would probably want to do the same thing for alert deletion.

angularjs: $interval call never happen after it's cancelled

I've a requirement where as soon as i load a page it makes makes api call and
shows data on the page. and after every 15 sec it keeps loading the data an shows and i've a popup that has a click button function(ConfigModalButtonClick ). When i open the popup i wanted to stop the interval that makes the api call so for that i added $interval.cancel when popup is open but now when i click the button in popup the api call never happen. how can i make sure api call happens it looks like i need to call interval function again somewhere but not sure where??
the interval should run normally after 15 sec when i close the popup.
$scope.timeDelay = 15000;
$scope.loadData = function() {
$http.post(url,data
).then(
function(response) {
$scope.output = response.data;
},
function(response) {
$scope.retrieveErrorMssg = "Failed to retrieve required data. Try again.";
});
};
var interval = $interval(function() {
$scope.loadData();
}, $scope.timeDelay);
$scope.showConfigurationModal = function() {
$interval.cancel(interval);
$scope.state.settingModalVisible = true;
};
$scope.closeConfigurationModal = function() {
$scope.state.settingModalVisible = false;
// Need to explicitly trigger the dismiss function of the modal
$scope.settingModalDismiss();
};
$scope.ConfigModalButtonClick = function () {
$scope.state.settingModalVisible = false;
//some data manipulation
$scope.loadData();
};

Atmosphere and Angular JS how to

I'm an atmosphere & Angular newbie and I'm really struggling to find an answer to this! Maybe I'm asking the wrong question.
I am setting up notifications using Atmosphere. I can open the websocket and watch the updates happen if I post the API URL directly into my browser.
In Angular I have an ng-repeat loop, which I would like to run as each new update adds a new object to the websocket.
<li ng-repeat="notification in notifications track by $index">
I am using angular watch to check for updates, but it doesn't pick up the new objects being added to the array. Here is my code:
// notification alerts
$scope.notifications = [];
notificationsService.notificationAlerts().then(function success(response) {
var jsonStringArray = response.data.split('|');
$scope.notifications = $.map(jsonStringArray, function(n, i){
if (n !== ""){
return JSON.parse(n);
}
});
console.log('Connect', response);
});
$scope.$watch('notifications', function(newVal, oldVal){
console.log('Watch', $scope.notifications);
}, true);
Hopefully I've made myself clear, let me know if I need to elaborate, or if I'm asking the wrong question. Thanks!
OK, I managed to solve this, for anyone stumbling across it later. Here is the final JS:
// add number of notifications to ".notifications-number"
function updateNumberOfNotifications(){
var numberOfNotifications = $("ul.notifications-list li").not(".nocount").length;
if (numberOfNotifications < 1) {
$(".notifications-number, .notifications-list").addClass("hidden");
} else {
$(".notifications-number").html(numberOfNotifications);
$(".notifications-number, .notifications-list").removeClass("hidden");
}
}
// notification alert variables
$scope.notifications = [];
var socket = atmosphere;
var subSocket;
// subscribe
function subscribe() {
var request = {
url : "/service/notifier",
transport: 'long-polling'
};
request.onMessage = function (response) {
//console.log('response', response);
var jsonStringArray = response.responseBody.split('|');
// console.log('json string array', jsonStringArray);
$.each(jsonStringArray, function(index, elem){
if (elem != ""){
$scope.notifications.push(JSON.parse(elem));
console.log("object", JSON.parse(elem));
}
});
//$scope.notifications.push($scope.newNotification);
$scope.$apply();
updateNumberOfNotifications();
// console.log('$scope.notifications', $scope.notifications);
};
subSocket = socket.subscribe(request);
}
function unsubscribe(){
socket.unsubscribe();
}
// subscribe on load and update notifications
updateNumberOfNotifications();
subscribe();

ng-include template variable not changing on $scope.$watch (no button trigger)?

I have a controller 'AController' that has a template variable, $scope.tpl.partialtemplate1 = 'initialcontactlist.html'.
'AController' basically is in charge of an entire page, 'mainpage.html', where we have
<div ng-include="tpl.partialtemplate1"></div>
On 'mainpage.html', there is a form to add contacts. This form is not part of 'partialtemplate1''s views.
Upon submitting the form, I want the HTML view for 'partialtemplate1' to be reset to what it was on initial page load, because it will then reload the latest list of contacts.
I have tried things like incrementing a variable after each new contact is successfully added, and then having that variable watched and the partialtemplate variable changed.
for example, in 'AController':
$scope.tpl = {};
$scope.contactcount = 0;
$scope.contactsignupdata = new Contact();
$scope.tpl.partialtemplate1 = 'initialcontactlist.html';
$scope.successmessage = null;
$scope.addcontact = function() {
$scope.contactsignupdata.$save();
$scope.successmessage = 'Saved!';
$scope.contactsignupdata = new Contact();
$scope.contactcount = $scope.contactcount + 1;
};
$scope.$watch('contactcount', function(newValue, oldValue) {
$scope.$apply(function() {
$scope.tpl.partialtemplate1 = null;
$scope.tpl.partialtemplate1 = 'initialcontactlist.html';
});
/*$scope.partialtemplate1 = 'projecttasklists.html';*/
});
Why isn't the partialtemplate variable getting changed? Yes, the contact gets successfully saved each time - I took care of that with the Rails factory...
Your code sets partialtemplate1 to null, then straight back to 'initialcontactlist.html'. As far as Angular is concerned, nothing is changed. True bindings are not supported meaning that just because you changed partialtemplate1, doesn't mean it immediately happens or triggers any special events. For this specific scenario, you would have to set partialtemplate1 to null, set a timer, then trigger the change back to 'initialcontactlist.html'
I do not recommend this by the way
$scope.$watch('contactcount', function(newValue, oldValue) {
$scope.$apply(function() {
$scope.tpl.partialtemplate1 = null;
$timeout(function() {
$scope.tpl.partialtemplate1 = 'initialcontactlist.html';
}, 1000);
});
});
I highly recommend
Creating an API for Contacts that you can query. That way when a Contact is created, updated, or removed you can handle it yourself in a couple ways:
You can requery the data source each time something changes
If the API returns data related to the change, you don't need to requery
You should look into creating Angular Services and/or Factories to handle this. In fact it is quite easy to implement if it is a true REST API using $resource. If it is not a RESTful resource, you can use $http for custom queries
I solved this problem with $emit.
In the HTML file, upon pressing the submit button for the "Add a contact" form, two events are triggered (separated by the apostrophe button).
ng-click="addcontact(contactsignupdata.name);$emit('MyEvent')"
</form>
{{successmessage}}
In the controller file:
$scope.successmessage = null;
$scope.tpl = {};
$scope.tpl.partialtemplate1 = 'initialcontactlist.html';
$scope.addcontact = function(value) {
$scope.contactsignupdata.$save();
$scope.successmessage = 'Saved ' + $scope.contactsignupdata.name;
$scope.contactsignupdata = new Contact();
};
$scope.$on('MyEvent', function() {
$scope.tpl.partialtemplate1 = null;
$scope.resettofullcontactslist($scope.tpl.partialtemplate1);
});
$scope.resettofullcontactslist = function(value) {
$scope.tpl.partialtemplate1 = 'initialcontactlist.html';
};

Poll server with a timer in angularjs

I am in a spot where I need to poll my server for data every so often. I have looked around at how people are handling this in angularjs and I am pretty confused.
Some examples are of just simple counters that increment up/down. Other are using the $timeout service. I need the ability to turn this on/off with a button click. I.E. click to start poll, poll every 30 seconds, click button to stop polling.
I am not claiming to be great at javascript nor angular so please go easy. I did write my own service that uses setInterval and clearInterval:
angular.module('myModule', [])
.factory('TimerService',
function () {
var timers = {};
var startTimer = function(name, interval, callback) {
// Stop the timer if its already running, no-op if not running
stopTimer(name);
timers[name] = setInterval(function() {
callback();
}, interval);
// Fire right away, interval will fire again in specified interval
callback();
}
var stopTimer = function(name) {
var timer = timers[name];
if (timer) {
clearInterval(timer);
delete timers[name];
}
}
return {
start: startTimer,
stop: stopTimer
};
});
Then in my controller I do this:
var timerARunning = false;
$scope.onClickA = function() {
var timerName = 'timerA';
timerARunning = !timerARunning;
if (timerARunning) {
TimerService.start(timerName, 5000, function() {
alert("Timer A just fired");
});
} else {
TimerService.stop(timerName);
}
}

Resources