$watch() is not catching return sseHandler.result.cpuResult.timestamp after the first iteration. I'm not sure why, because I verified the datestamps are changing. Also, after the first iteration....if I click on the view repeatedly, the scope variables and view update with the new information...so it's like $watch does work...but only if I click on the view manually to make it work.
'use strict';
angular.module('monitorApp')
.controller('homeCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('cpuCtrl', ['$scope', 'sseHandler', function($scope, sseHandler) {
$scope.sseHandler = sseHandler;
$scope.avaiable = "";
$scope.apiTimeStamp = sseHandler.result.cpuResult.timestamp;
$scope.infoReceived = "";
$scope.last15 = "";
$scope.last5 = "";
$scope.lastMinute = "";
var cpuUpdate = function (result) {
$scope.available = result.cpuResult.avaiable;
$scope.apiTimeStamp = result.cpuResult.timestamp;
$scope.infoReceived = new Date();
$scope.last15 = result.cpuResult.metrics['15m'].data
$scope.last5 = result.cpuResult.metrics['5m'].data
$scope.lastMinute = result.cpuResult.metrics['1m'].data
}
$scope.$watch(function () {
console.log("being caught");
return sseHandler.result.cpuResult.timestamp},
function(){
console.log("sss");
cpuUpdate(sseHandler.result);
});
}]);
angular.module('monitorApp')
.controller('filesystemsCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.controller('httpPortCtrl', function($scope, $location, $document) {
console.log("s");
});
angular.module('monitorApp')
.factory('sseHandler', function ($timeout) {
var source = new EventSource('/subscribe');
var sseHandler = {};
sseHandler.result = { "cpuResult" : { timestamp : '1'} };
source.addEventListener('message', function(e) {
result = JSON.parse(e.data);
event = Object.keys(result)[0];
switch(event) {
case "cpuResult":
sseHandler.result = result;
console.log(sseHandler.result.cpuResult.timestamp);
break;
}
});
return sseHandler;
});
The changes in sseHandler.result.cpuResult.timestamp happen otuside of the Angular context (in the asynchronously executed event-listener callback), so Angular does not know about the changes.
You need to manually trigger a $digest loop, by calling $rootScope.$apply():
.factory('sseHandler', function ($rootScope, $timeout) {
...
source.addEventListener('message', function (e) {
$rootScope.$apply(function () {
// Put all code in here, so Angular can also handle exceptions
// as if they happened inside the Angular context.
...
});
}
...
The reason your random clicking around the app made it work, is because you probably triggered some other action (e.g. changed a model, triggered and ngClick event etc) which in turn triggered a $digest cycle.
Your message event in the EventListener does not start a new digest cycle. In your sseHandler try:
$timeout(function () {sseHandler.result = result;});
Related
I want to hide my headmenu.
app.controller("kpiOverviewCtrl", function ($scope, $stateParams,) {
"use strict";
var setUpController = function () {
$scope.headmenu = $state.current.controller === "kpiCompareCtrl";
};
$rootScope.$on('$locationChangeSuccess', function () {
setUpController();
});
$rootScope.$on('$stateChangeSuccess', function () {
setUpController();
});
setUpController();
});
As you can see on the code it sets headmenu to true on a controller switch. It works fine. But now I want to set headmenu to true on a ng-click statment from a controller thats already been loaded.
app.controller("kpiDetailsCtrl", function ($scope, $state) {
"use strict";
$scope.loadDataForMonthView = function () {
$scope.errorNoDataForDate = false;
$scope.yearMode = false;
$scope.monthMode = true;
//Here I want to set $scope.headmenu = true;
//Other code.....
};
Any nice suggestions?
Use a broadcast. They're a great way for communication between controllers.
Create a regular function in your main controller, which you can call from within the controller itself.
app.controller('Main', function($scope) {
function setHeadMenu() {
// Set menu to true
}
$scope.$on('setHeadMenu', function() {
setHeadmenu(); // Fires on broadcast
})
});
Create an ng-click which fires a broadcast from the other controller
app.controller('Second', function($scope) {
$scope.click = function() {
$scope.$broadcast('setHeadMenu'); // Send a broadcast to the first controller
}
});
You can declare new method to $rootScope inside kpiOverviewCtrl:
app.controller("kpiOverviewCtrl", function ($scope, $stateParams, $rootScope) {
"use strict";
//your code...........
$rootScope.setUpController = setUpController;
});
And then call it from kpiDetailsCtrl controller:
app.controller("kpiDetailsCtrl", function ($scope, $state, $rootScope) {
"use strict";
$scope.loadDataForMonthView = function () {
$scope.errorNoDataForDate = false;
$scope.yearMode = false;
$scope.monthMode = true;
$rootScope.setUpController();
}
});
First dummy suggestion:
$scope.loadDataForMonthView = function () {
$scope.headmenu = true; //(or false)
}
But most likely you are using some asynchrounous call, so something like this would be better:
$scope.loadDataForMonthView = function () {
// First: you need some promise object
// the most simple is to use $http
var promise = $http({url: 'some.url', method: 'GET'});
promise.then(function() {
// the data have arrived to client
// you can hide or show menu according to your needs
$scope.headmenu = true; //(or false)
})
}
More on how $http works is in the docs https://docs.angularjs.org/api/ng/service/$http
I'm trying to write an AngularJS library for Pusher (http://pusher.com) and have run into some problems with my understanding of the digest loop and how it works. I am writing what is essentially an Angular wrapper on top of the Pusher javascript library.
The problem I'm facing is that when a Pusher event is triggered and my app is subscribed to it, it receives the message but doesn't update the scope where the subscription was setup.
I have the following code at the moment:
angular.module('pusher-angular', [])
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window',
function ($window) {
var pusher = new $window.Pusher(apiKey, initOptions);
return pusher;
}];
})
.factory('Pusher', ['$rootScope', '$q', 'PusherService', 'PusherEventsService',
function ($rootScope, $q, PusherService, PusherEventsService) {
var client = PusherService;
return {
subscribe: function (channelName) {
return client.subscribe(channelName);
}
}
}
]);
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
})
}]);
and in another file that sets the app up:
angular.module('myApp', [
'pusher-angular'
]).
config(['PusherServiceProvider',
function(PusherServiceProvider) {
PusherServiceProvider
.setToken('API KEY')
.setOptions({});
}
]);
I've removed some of the code to make it more concise.
In the ItemListController the $scope.items variable doesn't update when a message is received from Pusher.
My question is how can I make it such that when a message is received from Pusher that it then triggers a digest such that the scope updates and the changes are reflected in the DOM?
Edit: I know that I can just wrap the subscribe callback in a $scope.$apply(), but I don't want to have to do that for every callback. Is there a way that I can integrate it with the service?
On the controller level:
Angular doesn't know about the channel.bind event, so you have to kick off the cycle yourself.
All you have to do is call $scope.$digest() after the $scope.items gets updated.
.controller('ItemListController', ['$scope', 'Pusher', function($scope, Pusher) {
$scope.items = [];
var channel = Pusher.subscribe('items')
channel.bind('new', function(item) {
console.log(item);
$scope.items.push(item);
$scope.$digest(); // <-- this should be all you need
})
Pusher Decorator Alternative:
.provider('PusherService', function () {
var apiKey = '';
var initOptions = {};
this.setOptions = function (options) {
initOptions = options || initOptions;
return this;
};
this.setToken = function (token) {
apiKey = token || apiKey;
return this;
};
this.$get = ['$window','$rootScope',
function ($window, $rootScope) {
var pusher = new $window.Pusher(apiKey, initOptions),
oldTrigger = pusher.trigger; // <-- save off the old pusher.trigger
pusher.trigger = function decoratedTrigger() {
// here we redefine the pusher.trigger to:
// 1. run the old trigger and save off the result
var result = oldTrigger.apply(pusher, arguments);
// 2. kick off the $digest cycle
$rootScope.$digest();
// 3. return the result from the the original pusher.trigger
return result;
};
return pusher;
}];
I found that I can do something like this and it works:
bind: function (eventName, callback) {
client.bind(eventName, function () {
callback.call(this, arguments[0]);
$rootScope.$apply();
});
},
channelBind: function (channelName, eventName, callback) {
var channel = client.channel(channelName);
channel.bind(eventName, function() {
callback.call(this, arguments[0]);
$rootScope.$apply();
})
},
I'm not really happy with this though, and it feels as though there must be something bigger than I'm missing that would make this better.
I'm wondering whether there is a different approach to using $watch in order to achieve the following.
Setup:
ControllerA depends on ServiceA.
ControllerB depends on ServiceB.
Current browser view is showing both controllers.
Scenario:
ControllerA is initiating a function on ServiceA, which in turn changes the value of propery X in ServiceB which should be reflected in the UI of ControllerB.
http://jsfiddle.net/zexscvax/2/
html:
<div>
<div ng-controller="ControllerA"></div>
<div ng-controller="ControllerB">Progress: {{progress}}</div>
</div>
js:
var myApp = angular.module('myApp', []);
myApp.factory('serviceA', ['$q', '$interval', 'serviceB', function ($q, $interval, serviceB) {
var service = {};
service.start = function () {
var progress = 0;
var deferred = $q.defer();
deferred.promise.then(null,null, notifyServiceB);
function notifyServiceB() {
serviceB.update(progress);
}
$interval(function() {
if (progress == 0.99) {
deferred.resolve();
} else {
progress += 0.01;
deferred.notify(progress);
}
}, 50, 100);
};
return service;
}]);
myApp.factory('serviceB', ['$rootScope', function ($rootScope) {
var service = {};
service.update = function (progress) {
console.log('update', progress);
service.progress = progress;
//$rootScope.$apply(); // <+ ERROR: $digest already in progress
};
return service;
}]);
myApp.controller('ControllerA', ['$scope', 'serviceA',
function ($scope, serviceA) {
serviceA.start();
}]);
myApp.controller('ControllerB', ['$scope', 'serviceB',
function ($scope, serviceB) {
$scope.progress = serviceB.progress;
/* this works but I'm not sure whether this is performing
$scope.$watch(function () { return serviceB.progress; },
function (value) {
$scope.progress = serviceB.progress;
}
);
*/
}]);
Without the $watch in ControllerB for the property X in ServiceB, the UI would not get updated. I've also tried injecting $rootScope in ServiceB in order to run an apply() but that wouldn't work.
I'm not entirely sure whether there's a better way to setup this scenario or whether $watch is fine. I'm a bit worried about performance issues as the value of property X changes almost every 50 ms (it's basically a visual timer counting down).
Thanks for your input.
If you don't use $watch, you can use $rootScope to broadcast, and on controller B, you can $on this event and handle the view update.
I have an angular service and a controller interacting. The service usings the $interval to poll the server. I know this returns a promise, however it uses $http to make an call to the server, which ALSO returns a promise and the chaining of the promises is not happening the way I would expect.
SERVICE
(function () {
'use strict';
var serviceId = "notificationService";
angular.module('app').factory(serviceId, ['helpersService', '$interval', '$http', function (helpersService, $interval, $http) {
var defaultOptions = {
url: undefined,
interval: 1000
};
var myIntervalPromise = undefined;
var displayedNotifications = [];
function onNotificationSuccess(response) {
//alert("in success");
displayedNotifications.push(response.data);
return response.data;
}
function onNotificationFailed(response) {
alert("in Failure");
throw response.data || 'An error occurred while attempting to process request';
}
function initializeNotificationService(configOptions) {
var passedOptions = $.extend({}, defaultOptions, configOptions);
if (passedOptions.url) {
myIntervalPromise = $interval(
function() {
//console.log(passedOptions.url);
//return helpersService.getAjaxPromise(passedOptions);
//promise.then(onNotificationSuccess, onNotificationFailed);
$http({
method: 'POST',
url: passedOptions.url
}).then(onNotificationSuccess, onNotificationFailed);
}, passedOptions.interval);
//alert("in initializeNotificationService");
return myIntervalPromise;
}
//return myIntervalPromise;
}
//$scope.$on('$destroy', function() {
// if (angular.isDefined(myIntervalPromise)) {
// $interval.cancel(myIntervalPromise);
// myIntervalPromise = undefined;
// }
//});
return {
// methods
initializeNotificationService: initializeNotificationService,
//properties
displayedNotifications : displayedNotifications
};
}]);
})();
CONTROLLER
(function () {
'use strict';
var controllerId = 'MessageCtrl';
//TODO: INVESTIGATE HAVING TO PASS $INTERVAL TO HERE TO DESTROY INTERVAL PROMISE.
//TODO: HAS TO BE A WAY TO MOVE THAT INTO THE SERVICE
angular.module('app').controller(controllerId, ['notificationService', '$scope', '$interval', function (notificationService, $scope, $interval) {
var vm = this;
// tied to UI element
vm.notifications = [];
vm.initialize = function () {
// initialize tyhe notification service here
var intervalPromise = notificationService.initializeNotificationService({ url: 'api/userProfile/getNotifications', interval: 5000 });
intervalPromise.then(
function (response) {
// NEVER GETS CALLED
var s = "";
//vm.notifications.push(response);
// alert("successful call");
},
function (response) {
var s = "";
// THIS GETS CALLED WHEN THE PROMISE IS DESTROYED
// response = canceled
//alert("failure to call");
},
function(iteration) {
console.log(notificationService.displayedNotifications);
// This gets called on every iteration of the $interval in the service
vm.notifications = notificationService.displayedNotifications;
}
);
// TODO: SEE COMMENT AT TOP OF CONTROLLER
$scope.$on('$destroy', function () {
if (angular.isDefined(intervalPromise)) {
$interval.cancel(intervalPromise);
intervalPromise = undefined;
}
});
};
vm.alertClicked = function (alert) {
alert.status = 'old';
};
// call to init the notification service here so when the controller is loaded the service is initialized
vm.initialize();
}]);
})();
The way this ends up flowing, and I'll do my best to show flow here
1) SERVICE - $interval makes the call with the $http BOTH OF THESE SEEM TO RETURN THEIR OWN PROMISES ACCORDING TO THE DOCS
2) CONTROLLER - intervalPromise's NOTIFY callack is called
3) SERVICE - onNotificationSuccess callback of $http is called
WHAT DOESN'T HAPPEN THAT I WOULD EXPECT
4) CONTROLLER - intervalPromise success callback is never called
Should the return response.data in the onNotificationSuccess handler within the service trigger the then chain in the Controller? It's aware that the promise is returned or seemingly cause the notify callback in the controller is called each time $interval executes, so I'm confused as to where the chain is broken.
IDEAL
$interval calls with $http, the promise from $http is passed up to the controller
then with each iteration new messages are added to the service on a successful call by $interval, then in the controller onsuccess I can check the property of the service and update the UI. Where am I losing the method chain?
I would recommend breaking the usage of $interval outside of service and use it directly in your controller.
The service being provided is the ability to get data from the server and the interval is the means in which to get the data, which is more indicative of the user interface's requirements as to how often the data is retrieved.
What you appear to be doing is to wrap the functionality of the $interval service which is causing a complication for you.
Note: after creating a quick plnkr the report progress event of $interval returns the iteration number (times called) and no other parameters.
Ended up with everything in the controller...
(function () {
'use strict';
var controllerId = 'NotificationCtrl';
angular.module('app').controller(controllerId, ['helpersService', '$scope', '$interval', function (helpersService, $scope, $interval) {
var vm = this;
var intervalPromise = undefined;
// tied to UI element
vm.notifications = [];
function onNotificationSuccess(response) {
//alert("in success");
vm.notifications.push.apply(vm.notifications, response.data);
return response.data;
}
function onNotificationFailed(response) {
//alert("in Failure");
throw response.data || 'An error occurred while attempting to process request';
}
vm.initialize = function () {
intervalPromise = $interval(
function () {
var promise = helpersService.getAjaxPromise({ url: 'api/userProfile/getNotifications' });
promise.then(onNotificationSuccess, onNotificationFailed);
}, 5000);
$scope.$on('$destroy', function () {
if (angular.isDefined(intervalPromise)) {
$interval.cancel(intervalPromise);
intervalPromise = undefined;
}
});
};
vm.alertClicked = function (alert) {
//alert.status = 'old';
};
// call to init the notification service here so when the controller is loaded the service is initialized
vm.initialize();
}]);
})();
i cant get my head around what is the recommended way to communicate from a service to a custom directive. The custom directive is an interactive svg graphic, which on user interaction calls a method of an injected service to retrieve new data. This should happen in an asynchronous manner. I read here and there that events are in general not the recommend way to communicate in angularjs. Should I use a callback function? Or?
Thanks buddies
martin
You inject the service into the directive, and then the directive calls methods on the service passing in argument values as parameters.
To let a directive know that a service method has completed asynchronously, have the service method return a promise object.
http://jsfiddle.net/gGhtD/5/
var myApp = angular.module('myApp', []);
//myApp.directive('myDirective', function() {});
myApp.factory('myService', function ($q, $timeout) {
return {
doSomething: function (msg) {
var d = $q.defer();
$timeout(function () {
d.resolve("resolved: " + msg);
}, 1500);
return d.promise;
}
}
});
function MyCtrl($scope, myService) {
$scope.callService = function () {
$scope.sent = new Date();
$scope.msg = "";
$scope.timestamp = "";
myService.doSomething("some value")
.then(function (data) {
$scope.timestamp = new Date();
$scope.msg = data;
});
}
}