angular JS - communicate between non-dependend services - angularjs

I am new in angular and encounter a catch-22:
Facts:
I have a service that logs my stuff (my-logger).
I have replaced the $ExceptionHandler (of angular), with my own implementation which forwards uncaught exceptions to my-logger service
I have another service, pusher-service, that needs to be notified whenever a fatal message is to be logged somewhere in my application using 'my-logger'.
Problem:
I can't have 'my-logger' be depend on 'pusher' since it will create circular dependency (as 'pusher' uses $http. The circle: $ExceptionHandler -> my-logger -> pusher -> $http -> $ExceptionHandler...)
My attempts:
In order to make these 2 services communicate with each other, I wanted to use $watch on the pusher-service: watches a property on $rootscope that will be updated in my-logger.
But, when trying to consume $rootScope in 'my-logger', in order to update the property on which the 'pusher' "watches", I fail on circular dependency as it turns out that $rootscope depends on $ExceptionHandler (the circle: $ExceptionHandler -> my-logger -> $rootScope -> $ExceptionHandler).
Tried to find an option to get, at runtime, the scope object that in its context 'my-logger' service works. can't find such an option.
Can't use broadcast as well, as it requires my-logger to get access to the scope ($rootScope) and that is impossible as seen above.
My Question:
Is there an angular way to have two services communicate through a 3rd party entity ?
Any idea how this can be solved ?

Use a 3rd service that acts as a notification/pubsub service:
.factory('NotificationService', [function() {
var event1ServiceHandlers = [];
return {
// publish
event1Happened: function(some_data) {
angular.forEach(event1ServiceHandlers, function(handler) {
handler(some_data);
});
},
// subscribe
onEvent1: function(handler) {
event1ServiceHandlers.push(handler);
}
};
}])
Above, I only show one event/message type. Each additional event/message would need its own array, publish method, and subscribe method.
.factory('Service1', ['NotificationService',
function(NotificationService) {
// event1 handler
var event1Happened = function(some_data) {
console.log('S1', some_data);
// do something here
}
// subscribe to event1
NotificationService.onEvent1(event1Happened);
return {
someMethod: function() {
...
// publish event1
NotificationService.event1Happened(my_data);
},
};
}])
Service2 would be coded similarly to Service1.
Notice how $rootScope, $broadcast, and scopes are not used with this approach, because they are not needed with inter-service communication.
With the above implementation, services (once created) stay subscribed for the life of the app. You could add methods to handle unsubscribing.
In my current project, I use the same NotificationService to also handle pubsub for controller scopes. (See Updating "time ago" values in Angularjs and Momentjs if interested).

Yes, use events and listeners.
In your 'my-logger' you can broadcast an event when new log is captured:
$rootScope.$broadcast('new_log', log); // where log is an object containing information about the error.
and than listen for that event in your 'pusher':
$rootScope.$on('new_log', function(event, log) {... //
This way you don't need to have any dependencies.

I have partially succeeded to solve the case:
I have created the dependency between 'my-logger' and 'pusher' using the $injector.
I used $injector in 'my-logger' and injected at "runtime" (means right when it is about to be used and not at the declaration of the service) the pusher service upon fatal message arrival.
This worked well only when I have also injected at "runtime" the $http to the 'pusher' right before the sending is to happen.
My question is why it works with injector in "runtime" and not with the dependencies declared at the head of the service ?
I have only one guess:
its a matter of timing:
When service is injected at "runtime", if its already exists (means was already initialized else where) then there is no need to fetch and get all its dependencies and thus the circle is never discovered and never halts the execution.
Am I correct ?

This is an easy way to publish/subscribe to multiple events between services and controllers
.factory('$eventQueue', [function() {
var listeners = [];
return {
// publish
send: function(event_name, event_data) {
angular.forEach(listeners, function(handler) {
if (handler['event_name'] === event_name) {
handler['callback'](event_data);
}
});
},
// subscribe
onEvent: function(event_name,handler) {
listeners.push({'event_name': event_name, 'callback': handler});
}
};
}])
consumers and producers
.service('myService', [ '$eventQueue', function($eventQueue) {
return {
produce: function(somedata) {
$eventQueue.send('any string you like',data);
}
}
}])
.controller('myController', [ '$eventQueue', function($eventQueue) {
$eventQueue.onEvent('any string you like',function(data) {
console.log('got data event with', data);
}])
.service('meToo', [ '$eventQueue', function($eventQueue) {
$eventQueue.onEvent('any string you like',function(data) {
console.log('I also got data event with', data);
}])

You can make your own generic event publisher service, and inject it into each service.
Here's an example (I have not tested it but you get the idea):
.provider('myPublisher', function myPublisher($windowProvider) {
var listeners = {},
$window = $windowProvider.$get(),
self = this;
function fire(eventNames) {
var args = Array.prototype.slice.call(arguments, 1);
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.on(): argument one must be a string.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
var eventListeners = listeners[eventName];
if(eventListeners && eventListeners.length) {
angular.forEach(eventListeners, function(listener) {
$window.setTimeout(function() {
listener.apply(listener, args);
}, 1);
});
}
});
return self;
}
function on(eventNames, handler) {
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.on(): argument one must be a string.');
}
if(!angular.isFunction(handler)) {
throw new Error('myPublisher.on(): argument two must be a function.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
if(listeners[eventName]) {
listeners[eventName].push(handler);
}
else {
listeners[eventName] = [handler];
}
});
return self;
}
function off(eventNames, handler) {
if(!angular.isString(eventNames)) {
throw new Error('myPublisher.off(): argument one must be a string.');
}
if(!angular.isFunction(handler)) {
throw new Error('myPublisher.off(): argument two must be a function.');
}
eventNames = eventNames.split(/ +/);
eventNames = eventNames.filter(function(v) {
return !!v;
});
angular.forEach(eventNames, function(eventName) {
if(listeners[eventName]) {
var index = listeners[eventName].indexOf(handler);
if(index > -1) {
listeners[eventName].splice(index, 1);
}
}
});
return self;
}
this.fire = fire;
this.on = on;
this.off = off;
this.$get = function() {
return self;
};
});

Related

Getting a value sent by SignalR with AngularJS

I'm trying to write a very simple message on the screen that when signalr send a value this message gets updated.
I have a very simple Hub:
public class Chat : Hub
{
public Task Send(string message)
{
return Clients.All.InvokeAsync("Send", message);
}
}
On the front-end I have the following html:
<div ng-app="myApp" ng-controller="myCtrl">
<p ng-bind="value"></p>
</div>
And the script is:
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.value = "My Test Value";
});
chatConnection.on('Send', (message) => {
app.scope.value = message;
});
What I am failing to understand is how do I access that value parameter so that I may update it.
EDIT:
The SignalR part works, the message comes through. The problem is that I am not sure how to update the value that is inside of that controller. app.scope.value = message;
Use observer pattern. Basically create a service which should do the following things:
Creates hub connection (handles start, reconnection, ....)
Support for example the following methods:
register for event (adds the caller to a list which contains
interested members.)
unregister for event (removes the caller from the list with the interesed members.)
In the case a new message from server inform all observers
Example:
(Pseudocode extracted from my solution (to big to show all)):
Server (here called hubWrapperService):
var yourHubProxy = $.connection.yourHub;
yourHubProxy.client.yourMethodWhichServerCanCall= function (params){
// Inform observers. Iterate to over all your observers and inform them
notifyObservers(params);
};
// Start connection.
$.connection.hub.start().done(function () {
...
});
}).fail(function (error) {...
});
function registerToEvent(callback) {
$log.debug("ServiceHub: getConnectionEvent called");
// Add caller to you observer list. (here serviceInitObservers)
}
function notifyServiceInitObservers() {
angular.forEach(serviceInitObservers, function (callback) {
callback.callback();
});
}
In your Controller (inject service and register for events):
hubWrapperServer.registerToEvent(function () {
serviceHub.getAllDevices().then(function (params) { // Do something in your controller
});
});
There is also a wrapper service available
https://github.com/JustMaier/angular-signalr-hub
poker.client.showAllCards = function (show) {
$scope.allCardsShowing = show;
$scope.$apply();
};
$scope.$apply(), refresh the Angularjs context, and this working for me.
I get this form article: Consensus: SignalR + AngularJS

How to keep the AngularJS template updated while getting socket updates?

I get updates from the Backend via socket connections. I want to have an automatically updating Frontend with AngularJS while using a data object for the data I got from the Backend.
What do I have?
Template:
Status: {{unit.getStatus()}}
Controller 1:
function ($scope, unitFactory) {
// register to unit factory to get the updates and do
// $scope.unit = data.getUnit();
}
Controller 2:
function ($scope, unitFactory) {
// register to unit factory to get the updates and do
// $scope.unit = data.getUnit();
// $scope.foo = data.getFoo();
}
Service:
function(requestFactory) {
var unit = {},
foo = {};
Sockets.socket('unit', function (response) {
unit = new Unit(response['data']);
foo = new Foo(response['foo']);
// This is the data object which has to be send to the controllers
var Data = {
getUnit: function () {
return unit;
},
getFoo: function() {
return foo;
}
// some more functions...
}
});
}
Sockets:
channel.on('data', function (event) {
try {
event = JSON.parse(event);
// successCallback is what is given as second parameter in the `Service`.
$rootScope.$apply(successCallback(event));
} catch (e) {
console.log('Error: ' + e);
}
});
How should it work together?
Socket update comes in and gets handled by the Sockets object
Sockets call the function which is registered in the Service
The callback function in Service process the data
MISSING The processed data wrapped in an object has to be delivered to the controllers
MISSING The controllers can do whatever they want to do with the data whenever there is a new update.
The template gets auto updated.
Can anyone help me with the MISSING parts? I tried a lot of different approaches but I ran to dead ends every time.
Have you tried returning a promise for the data, and then $state.reload() ?
Got it solved using the 'data model pattern':
Template 1 (used by Controller 1):
Status: {{unit.data.getStatus()}}
Template 2 (used by Controller 2):
Status: {{foo.data.getBar()}}
Controller 1:
function ($scope, unitFactory) {
$scope.unit = unitFactory.getUnit();
}
Controller 2:
function ($scope, unitFactory) {
$scope.unit = unitFactory.getUnit();
$scope.foo = unitFactory.getFoo();
}
Service:
function(requestFactory) {
var unit = { data:{} },
foo = { data:{} };
Sockets.socket('unit', function (response) {
unit.data = new Unit(response['data']);
foo.data = new Foo(response['foo']);
});
return
getUnit: function ()
return unit;
},
getFoo: function() {
return foo;
}
// some more functions...
}
}
Sockets:
channel.on('data', function (event) {
try {
event = JSON.parse(event);
// successCallback is what is given as second parameter in the `Service`.
$rootScope.$apply(successCallback(event));
} catch (e) {
console.log('Error: ' + e);
}
});
Since the data is stored in an object the data in the templates is updated (since it is a reference). I have to get used to these extra attributes data and it is not looking nice but it does its job.

How to synch controller creation and emitting events?

I am interested if any of you run into this issue and how you solved it. I have a page that has it's controller and in that page, each tab has it's own controller. Depending on how I access the page, via navigation or direct url link, I have issue that tab controllers are not yet ready, by the time outer controller raises event. My solution was to raise "ControllerCreated" event, passing instance of the controller as the event arg. from each of the tab controllers. Outer controller listens for these and invokes on them "load" operation. Is there other built in way to synch this without having to raise custom event?
Event Recorder Service
One approach is to store outer events in a service and have child controllers use the service to "catch-up" and subscribe to the events.
app.service("eventRecorder", function() {
var lastEvent;
var subscriberIdNum = 0;
var subscriberList = {};
this.record = function evRecorder (eventValue) {
lastEvent = eventValue;
};
this.subscribe = function evSubscribe(callbackFn) {
//callback for subscriber to "catch-up"
if (lastEvent) { callbackFn(lastEvent) };
//save subscriber
subscriberIdNum++
var subscriberIdString = 'id'+subscriberIdNum;
subscriberList[subscriberIdString] = callbackFn;
//return unSubscribe function
return function unSubscribe() {
delete subscriberList[subscriberIdString];
};
};
this.notify = function evNotify( eventValue ) {
this.record(eventValue);
angular.forEach(subscriberList, function (cb) {
cb(eventValue);
});
};
});
Client Controller Example
app.controller("client", function($scope, eventRecorder) {
var vm = $scope;
var unSubscribe = eventRecorder.subscribe(function(value) {
//callback executes immediately to "catch-up"
vm.eventValue = value;
});
$scope.$on('$destroy', unSubscribe);
});
Parent Controller Usage
app.controller("parent", function($scope, eventRecorder) {
var vm = $scope;
vm.onEvent(value) {
eventRecorder.notify(value);
});
});
In order to prevent memory leaks, controllers should unsubscibe on destruction of the scope.
This example shows an event recorder service that provides only one feed. It can be generalized to handle more that one feed.
UPDATE: Automatic Unsubscribe
"In order to prevent memory leaks, controllers should unsubscibe on destruction of the scope." I know what you mean, we had similar notification service, but we pulled it out, because somebody would always forget to unsubscribe.
One could require scope as a subscribe function argument and implement automatic unsubsciption.
this.subscribe = function evSubscribe(scope, callbackFn) {
//callback for subscriber to "catch-up"
if (lastEvent) { callbackFn(lastEvent) };
//save subscriber
subscriberIdNum++
var subscriberIdString = 'id'+subscriberIdNum;
subscriberList[subscriberIdString] = callbackFn;
var unSubscribe = function evUnsubscribe() {
delete subscriberList[subscriberIdString];
};
scope.$on("$destroy", unSubscribe);
return unSubscribe;
};

Fire event from a service without "polluting" the $rootScope

I'm building an app in angularjs, where I have a central notification queue. Any controller can push into the queue and digest the messages.
I have built a service like:
angular.module('app').factory('notificationSvc', ['translateSvc', notification]);
function notification(translate) {
var notificationQ = [];
var service = {
add: add,
getAll: getAll
};
return service;
function add(message, type) {
notificationQ.push({
message: message,
type: type
});
}
function getAll() {
return notificationQ;
}
}
(One of the problems with this is that the notificationQ can be modified unsafely by calling svc.getAll()[3].message = "I have changed a message"; or something similar. I originally wanted a "push only" service with immutable messages, but this problem is outside of the scope of this question.)
If I digest this queue in a controller like:
$scope.notifications = svc.getAll();
$scope.current= 0; // currently visible in the panel
And use it like:
<div ng-repeat="notification in notifications" ng-show="$index == current">
<p>{{notification.message}}</p>
</div>
I can bind to it, see it changing and all is well. I can cycle through past notifications by changing the variable current.
The question:
When the queue gets a new element I want the $scope.index variable to change to notifications.length - 1. How do I do that?
I have seen examples using $rootScope.$broadcast('notificationsChanged'); and $scope.$on('notificationsChanged', function() { $scope.index = $scope.notifications.length - 1; });, but I did not really like the pattern.
I have a controller that knows about the service, has a direct reference to it, and yet we use $rootScope to communicate? Everything else sees the $rootScope, and all the events from different services will clutter up there.
Can't I just put the event on the service instead? Something like this.$broadcast('notificationsChanged') in the service and svc.$on('notificationsChanged', function() { ... }); in the controller.
Or would it be cleaner to watch the data directly? If yes, how? I don't like this as I was not planning on exposing the full array directly (I was planning on get(index) methods) it just sort of happened along the lines where I had no idea what I was doing and was happy that at least something works.
You could just manage events yourself. For example (untested):
function EventManager() {
var subscribers = [];
var service = {
subscribe: subscribe;
unsubscribe: unsubscribe;
publish: publish
}
return service;
function subscribe(f) {
subscribers.push(f);
return function() { unsubscribe(f); };
}
function unsubscribe(f) {
var index = subscribers.indexOf(f);
if (index > -1)
subscribers.splice(index, 1);
}
function publish(e) {
for (var i = 0; i < subscribers.length; i++) {
subscribers[i](e);
}
}
}
function notification(translate) {
var notificationQ = [];
var addEvent = new EventManager();
var service = {
add: add,
getAll: getAll,
onAdded: addEvent.subscribe;
};
return service;
function add(message, type) {
var notification = {
message: message,
type: type
};
notificationQ.push(notification);
addEvent.publish(notification);
}
function getAll() {
return notificationQ;
}
}
Then, from your controller:
...
var unsubscribe = notificationSvc.onAdded(function(n) { /* update */ });
Caveat: using this method the service will maintain a reference to the subscriber function that is passed to it using subscribe, so you have to manage the subscription using $scope.$on('$destroy', unsubscribe)
The notification approach would definitely work. Depending on your implementation it would be the right solution.
Another approach would be to watch the notifications array in your controller, like this:
$scope.$watchCollection('notifications', function(newValue, oldValue) {
$scope.index = newValue.length - 1;
});
This should work, because your controller receives a direct reference to the notifications array and therefore can watch it directly for changes.
As runTarm pointed out in the comments, you could also directly $watch the length of the array. If you're only interested in length changes this would be a more memory saving approach (since you don't need to watch the whole collection):
$scope.$watch('notifications.length', function (newLength) {
$scope.index = newLength - 1;
});

angularjs send message from service

I have a service which creates a configuration object for an external component.
One of the config properties is an optional function that gets called when some event (non angular) gets triggered.
e.g.
{
eventHandler:function(e) { ... }
}
Inside this eventhandler I want to send a message to the current controller.
I tried getting instance of $rootService but it doesn't know about $broadCast.
update : the code (simplified version, to keep code short)
app.service('componentService',['$rootScope',
function($rootScope) {
this.getConfig = function() {
return {
transition:true,
... // other config parameters
clickHandler:function(e) { // event called by external library, e = event args
$rootScope.$broadCast("myEvent",e.value);
};
};
return {
getConfig : this.getConfig
}
}]);
http://plnkr.co/edit/BK4Vjk?p=preview
Check out the example I made above. It should work.
There's a few syntax errors in your snippet of code. I wasn't sure if it was because you were just quickly typing it or if they're really there.
Basically:
app.controller('MainCtrl', function($scope, componentService) {
var config = componentService.getConfig();
$('#nonAngular').bind('click', config.clickHandler);
$scope.$on('myEvent', function(e, value) {
console.log('This is the angular event ', e);
console.log('This is the value ', value)
});
});
app.service('componentService',['$rootScope',
function($rootScope) {
this.getConfig = function() {
return {
transition:true,
clickHandler:function(e) { // event called by external library, e = event args
$rootScope.$broadcast("myEvent", "Some value you're passing to the event broadcast");
}
}
}
}]);

Resources