How to catch event in angular's controller?
I have document level events, so I need to catch event in angular controller, is it possible?
Update
I have standalone js file with handling of some actions from camera.
document.addEventListener('myCameraEvent', handleMyCameraEvent);
And I want to trigger this event in angular controller or directive. Could anyone explain, how is it possible to implement with angular?
Here's a generic custom events observer service:
.service('Camera', function($document) {
//map of current subscribers
var subscribers = {};
//notifies all subscribers of particular event type
function notify(event) {
var handlers = subscribers[event.type] || [];
for (var i = 0; i < handlers.length; i++) {
handlers(i)(event);
}
}
//adds new handler to subscribers list
//returns object with unsubscribe() method
this.subscribe(eventType, handler) {
var handlers = subscribers[eventType] || [];
handlers.push(handler);
return (function(type, index) {
return {
unsubscribe: function() {
subscribers[type].splice(index);
}
}
})(eventType, handlers.length - 1);
}
//register custom events
$document.on('myCameraEvent', function(event) {
notify(event);
});
$document.on('myOtherCameraEvent', function(event) {
notify(event);
});
});
In your controller, you'd use it like this:
.controller('MyCtrl', function($scope, Camera) {
subscriber = Camera.subscribe('myCustomEvent', customEventHandler);
function customEventHandler(event) {
//process event
//..
//if handler is not needed anymore, unsubscribe
//subscriber.unsubscribe();
}
})
Of course, this is just general idea. You'd might want to have specific methods on Camera service, for example Camera.onSnapshot(snapshothandler); Camera.onTurnOff(turnOffHandler); which would register handlers to specific events to abstract away the event names.
Related
I'm creating a service in angular to track events, and I want the controllers to be as dumb as possible about the event tracking. For this service each controller will need to supply its own copy of the event function. My current solution is below.
//tracking module
let tracked_events = ['Event1','Event2']
.map(function(e){
return {
eventName:e,
eventProperties: {}
}
});
let finishingTracking = (event) => {
console.log("prentend this does something fancy...",event);
}
let track = (eventName,fn) => {
var event = tracked_events.filter(e => e.eventName===eventName)[0];
fn(event.eventProperties);
//some other internal tracking function
finishTracking(event);
}
//end tracking module
//called from a controller
track("Event 1",(event) => {
event["User ID"] = 123;
event["Company ID"] = 12
});
//some other controller
track("Event 2",(event) => {
event["Manager ID"] = 345;
event["Time Sent"] = Date.now()
}
This works well because each controller will only have to provide its own way to modify the event object and it won't know anything else about the tracking module. From a design perspective, is this ok? I'm not sure about modifying the callback parameters (V8 optimizations/side effects), but I can't think of another way that doesn't cause more changes to each controller that needs to do its own event tracking. Any suggestions on this?
EDIT (prior to refactor)
var eventProperties = { table: "Machines, has_rows: /*rows...*/ }
some_service.list(data)
.then(function (response) {
tracking_service.track({
eventName: 'Event 1',
eventProperties: eventProperties
});
AFTER
some_service.trackEvent(some_service.events.EVENT1, function (event) {
event["Table"] = "Machines";
event["Has Rows"] = response.data.machines.length > 0;
});
An object of shape {eventName: "Name", eventProperties:{} } is currently being defined in each controller. My solution was to pass in the eventName from a constant defined in the service and the callback function modifies the eventProperties of the object. Each controller will have a different set of properties in eventProperties.
After the callback in version two runs, the service does the actual "tracking". My aim was to have a function that prepared the eventProperties object and added whatever properties it needed before being actually tracked.
self.trackEvent = function (eventName, fn) {
//var event = TRACKED_EVENTS.filter(e => e.eventName === eventName)[0];
var event = {
eventName: self.events[eventName],
eventProperties: { }
}
fn(event.eventProperties); //this is the callback that does the prep
self.track(event); //does the actually tracking
}
I'm writing a small library that essentially polls a site for data, and is then supposed to notify a consumer when it matches. In C# I'd use events, which are actually multicast delegates. I've written my own multicast delegate in javascript before, but I figure there has to be a better way.
A consumer should register a callback which should be called when data is available. Something like this:
window.MyLibrary.dataAvailable(function(data) {
// do something with data
});
In the background MyLibrary is polling for data. When it finally has something that matches, it should execute the registered function(s). Multiple functions should be able to be registered and probably unregistered too.
CustomEvent is very very close to what I want. The problem with CustomEvent is that the event has to be raised on an element - it can't be raised on an object. That is, this wouldn't work:
var event = new CustomEvent('dataAvailable', { data: 'dynamic data' });
window.MyLibrary.addEventListener('dataAvailable', function (e) {
// do something with e.data
}, false);
// From somewhere within MyLibrary
this.dispatchEvent(event, data);
How do you register handlers on objects in javascript? I need to support the usual browsers and IE11+. Ideally I wouldn't be pulling in a library to do any of this. jQuery will be available on the page and can be used if that would make things easier.
For reference, this is the Multicast Delegate implementation I've used in the past:
function MulticastDelegate(context) {
var obj = context || window,
handlers = [];
this.event = {
subscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.push(handler);
}
},
unsubscribe: function (handler) {
if (typeof (handler) === 'function') {
handlers.splice(handlers.indexOf(handler), 1);
}
}
};
this.execute = function () {
var args = Array.prototype.slice.call(arguments);
for (var i = 0; i < handlers.length; i++) {
handlers[i].apply(obj, args);
}
};
}
var myEvent = new MulticastDelegate();
myEvent.event.subscribe(function(data) { ... }); // handler 1
myEvent.event.subscribe(function(data) { ... }); // handler 2
myEvent.execute(some_data);
So far I have seen many solutions of the problem. The simplest one is, of course, to $emit an event in $rootScope as an event bus e.g. ( https://github.com/btilford/anti-patterns/blob/master/angular/Angular.md )
angular.module('myModule').directive('directiveA', function($rootScope) {
return {
link : function($scope, $element) {
$element.on('click', function(event) {
$rootScope.$emit('directiveA:clicked', event);
});
}
}
});
angular.module('myModule').directive('directiveB', function() {
return {
link : function($scope, $element) {
$rootScope.on('directiveA:clicked', function(event) {
console.log('received click event from directiveA');
});
}
}
});
and another one is to declare a service with a mediator or pubsub functionality / an enclosed scope e.g. ( Communicating between a Multiple Controllers and a directive. )
module.factory('MessageService',
function() {
var MessageService = {};
var listeners = {};
var count = 0;
MessageService.registerListener = function(listener) {
listeners[count] = listener;
count++;
return (function(currentCount) {
return function() {
delete listeners[currentCount];
}
})(count);
}
MessageService.broadcastMessage = function(message) {
var keys = Object.keys(listeners);
for (var i = 0; i < keys.length; i++) {
listeners[keys[i]](message);
}
}
return MessageService;
}
);
The question are:
is there point to use the second one in an angular application?
and what are pros and cons of each of those in comparison to each other?
Creating your own implementation of event emitter is counter-productive when writing an AngularJS application. Angular already provides all tools needed for event-based communication.
Using $emit on $rootScope works nicely for global inter-service communication and doesn't really have any drawbacks.
Using $broadcast on a natural scope (one that is bound to a part of your DOM) provides scoped communication between view components (directives, controllers).
Using $broadcast on $rootScope brings the two previous points together (it provides a completely global communication platform). This is the solution used basically by any AngularJS-based library out there.
and
If you're worried about performance in the previous option and you really want your separate event emitter, you can easily create one by creating an isolated scope ($rootScope.$new(true)) and using $broadcast on it. (You can then wrap it into a service and inject it anywhere you want.)
The last option creates a full-fledged event emitter integrated into Angular (the implementation provided in your question would at least need to wrap all listener calls in $apply() to integrate properly) that can be additionally used for data change observation, if that fits a particular use-case.
However, unless your application is really humongous, or you're really paranoid about event name collisions, the first three options should suffice just fine.
I won't go into detail about other means of communication between your components. Generally speaking, when the situation calls for data sharing using scope, direct interaction of controllers, or communication through DOM Node attributes, you should know it.
I would say that broadcasting is an Angular way how to achieve this.
However your mediator can work, if you pass internal funcion of directive, in example I have used method on scope, but it can be done also with controller method.
I have used exact same factory as you post.
angular.module("sharedService", [])
.factory('MessageService',
function() {
var MessageService = {};
var listeners = {};
var count = 0;
MessageService.registerListener = function(listener) {
listeners[count] = listener;
count++;
return (function(currentCount) {
return function() {
delete listeners[currentCount];
};
})(count);
};
MessageService.broadcastMessage = function(message) {
var keys = Object.keys(listeners);
for (var i = 0; i < keys.length; i++) {
listeners[keys[i]](message);
}
};
return MessageService;
}
)
.directive("directiveA", function(MessageService) {
return {
link:function(scope) {
scope.click = function() {
MessageService.broadcastMessage("broadcasted message");
};
},
template: '<button ng-click="click()">Click</button>'
};
})
.directive("directiveB", function(MessageService) {
return {
link:function(scope) {
scope.callback = function(message) {
console.log(message);
};
MessageService.registerListener(scope.callback);
}
};
});
Full example: http://jsbin.com/mobifuketi/1/edit?html,js,console,output
Just to be complete, I would like to add, that angular also provides more posibilities how can directives communicate.
Require atribute
If your directives are connected in hierarchy, then you can use require attribute which let you to access other directives controller. This is ussually best solution for many cases.
.directive("directiveA", function() {
return {
require: "^directiveB",
link: function(scope, element, attrs, directiveCtrl) {
scope.click = function() {
directiveCtrl.call();
};
},
template: '<button ng-click="click()">Click</button>'
};
})
.directive("directiveB", function() {
return {
controller :function() {
this.call = function() {
console.log("method has been called");
};
}
};
});
Full example: http://jsbin.com/turoxikute/1/edit?html,js,console,output
Using $watch
If the functionality deppends on data and not on action, you cen use $watch and react on the changes of given model or model stored in shared service , its not like listener, its basicly checking of change. I have named method changeState() and log "state changed" for everybody see it clear.
angular.module("sharedService", [])
.service("MediatorService", function() {
this.state = true;
this.changeState = function() {
this.state = !this.state;
};
})
.directive("directiveA", function(MediatorService) {
return {
link:function(scope) {
scope.click = function() {
MediatorService.changeState();
};
},
template: '<button ng-click="click()">Click</button>'
};
})
.directive("directiveB", function(MediatorService) {
return {
link:function(scope) {
scope.mediator = MediatorService;
scope.$watch("mediator.state", function(oldValue, newValue) {
if (oldValue == newValue) {
return;
}
console.log("state changed");
});
}
};
});
Full example: http://jsbin.com/darefijeto/1/edit?html,js,console,output
I like an event bus.
Angular does provide $emit on $rootScope but I don't think that should bound your decision to use it for event-based flows if they are complex or foreseeably complex. Angular has lots of features and while most are great, even the authors admit they're mostly meant to compliment good software engineering principles, not replace them.
I like this post on using postal.js: An angular.js event bus with postal.js. The two main benefits are channels and envelopes, which will make for more explicit, understandable and flexible event-based logic.
I find service based approaches to be error prone if state is not managed tightly, which is hard with async calls and injections, where you can't be certain how a service will be multi-purposed in the future.
I'm trying to encapsulate the events in a service in order to implement a mechanics to subscribe / unsubscribe the listeners when a controller's scope is destroyed. This because I have been using the rootScope.$on in the following way:
if(!$rootScope.$$listeners['event']) {
$rootScope.$on('event', function(ev, data){
// do some...
});
}
or
$scope.$on('$destroy', function(ev, data){
// unsubscribe the listener
});
So I just need one listener of this event, I need to delete the existing listener when the controller is no longer alive, because the function I registered earlier is still being triggered.
So I need to implement a $destroy event listener on my controller, to destroy the listener when the scope is destroyed, but I don't want to do that code each time I create an event.
That's why I want to create a service in where I'm going to encapsulate the events.
angular.module('core').factory('event', [
function() {
var service = {};
service.events = {};
service.on = function(scope, eventId, callback) {
scope.$on('$destroy', function(ev, other){
//unsubscribe
});
service.events[eventId] = callback;
// scope = null; I guess ?
};
service.emit = function(eventId, data){
if (service.events[eventId])
service.events[eventId](data);
else
return new Error('The event is not subscribed');
};
return service;
}
]);
This could be done using $rootScope instead of my own methods but encapsulating the $on and $emit of $rootScope, but at the end I'll have the same issue here.
So these are my questions:
Is a good practice to pass the scope ref value to a service?
What is the meaning of $$destroyed? when this is true means that angularJS has no internal references to the instance?
Should I do a scope = null in my service to let GC delete the object or does angularJS handle an explicit delete?
Is there a better way to do what I want?
What you are trying to accomplish is basically an event bus.
You have also described very well what is wrong with the current implementation.
A different way to approach the problem is to decorate the $rootScope with your bus (or any other event bus for that matter). Here is how:
app.config(function ($provide) {
$provide.decorator('$rootScope', ['$delegate', '$$bus', function ($delegate, $$bus) {
Object.defineProperty($delegate.constructor.prototype, '$bus', {
get: function () {
var self = this;
return {
subscribe: function () {
var sub = $$bus.subscribe.apply($$bus, arguments);
self.$on('$destroy',
function () {
console.log("unsubscribe!");
sub.unsubscribe();
});
},
publish: $$bus.publish
};
},
enumerable: false
});
return $delegate;
}]);
});
Considering the following $$bus implementation (kept basic for simplicity):
app.factory('$$bus', function () {
var api = {};
var events = {};
api.subscribe = function (event) {
if (!events.hasOwnProperty(event.name)) {
events[event.name] = [event];
} else {
events[event.name].push(event);
}
return {
unsubscribe: function () {
api.unsubscribe(event);
}
}
};
api.publish = function (eventName, data) {
if (events.hasOwnProperty(eventName)) {
console.log(eventName);
angular.forEach(events[eventName], function (subscriber) {
subscriber.callback.call(this, data);
});
}
};
api.unsubscribe = function (event) {
if (events.hasOwnProperty(event.name)) {
events[event.name].splice(events[event.name].indexOf(event), 1);
if (events[event.name].length == 0) {
delete events[event.name];
}
}
};
return api;
});
Now all you have to do is subscribe or publish events. The unsubscribe will take place automatically (when the $scope is destroyed):
$scope.$bus.subscribe({
name: 'test', callback: function (data) {
console.log(data);
}
});
And later on publish an event:
$scope.$bus.publish('test', {name: "publishing event!"});
An important point to make is that the events themselves are subscribed to each individual $scope and not on the $rootScope. That is how you "know" which $scope to release.
I think it answers your question. With that in mind, you can obviously make this mechanism much sophisticated (such as controller event listener released when a view routed, unsubscribe automatically only to certain events, etc.).
Good luck!
** This solution is taken form Here which uses a different bus framework (other then that it is the same).
Are there any available tweak to make Knockout support routed events?
In my perticular case I want to handle context-menu-events in the root-vm of my view and let any nested vm to set up a context-menu trigger like this:
event: { contextmenu: OnContextMenu }
If the OnContextMenu-handler is not defined on the current vm it should route the event to it's parent-vm and so on until a handler is found.
Currently I have to do like this (which is kind of error prone)
event: { contextmenu: $parents[3].OnContextMenu }
Or are there other ways of doing this allready?
I have previously used a pattern where I search up through a hierarchy of view-models via the $parentContext, until I find whatever it is I need. I quickly adapted it for your code, a bit rough:
OnContextMenuSearch = function(data, event) {
var context = ko.contextFor(event.target);
done = false;
while (!done) {
if (typeof context.$data.OnContextMenu == "function") {
// Found it! Invoke it here...
context.$data.OnContextMenu()
done = true;
}
// Check there is something to recurse up into, before assigning it!
// If not, we are at the $root.
if ('$parentContext' in context == false) done = true;
else context = context.$parentContext;
}
}
which would be bound with something like:
event: { contextmenu: OnContextMenuSearch }
Found a simple solution. I'm using the built-in DOM event bubbling and then on the root-element I catch the event and get the vm using ko.dataFor, like this:
self.OnContextMenu = function (vm, e) { // the root-vm
vm = ko.dataFor(e.originalEvent.target);
if (vm && vm.contextMenu) {
self.openContextMenu(vm.contextMenu);
}
};