I'm trying to optimise my Angular app and browsing several websites I found this practice too:
var cleanup = $scope.$on('someEvent', function() {
$scope.refresh();
});
$scope.$on('$destroy', function() {
cleanup();
});
Due to the fact I have several $scope.$on in my controllers I was wondering if it is correct to use it like this:
var first = $scope.$on('firstEvent', function() {
$scope.something1();
});
var second = $scope.$on('secondEvent', function() {
$scope.something2();
});
var third = $scope.$on('thirdEvent', function() {
$scope.something3();
});
$scope.$on('$destroy', function() {
first();
second();
third();
});
Is this good to implement and correct?
As cited article says,
Angular should clean this up if you forget to, but the advice is always do it yourself.
Putting safeguards around the expected framework behaviour is overcautious and can hardly be called a good practice.
While jQuery/jqLite (element.on) and third-party event listeners may lead to memory leaks and should be cleaned up on scope $destroy or element $destroy event.
Related
I've been trying to debug my Backbone multi-page app for most of the day now to get rid of 'zombies', but unfortunately to no avail. Before today, I didn't even realize I have a zombie problem. What am I doing wrong?
This is my RegionManager:
var regionManager = (function() {
var currView = null;
var rm = {};
var closeView = function(view) {
if (view && view.close) {
view.close();
}
};
var openView = function(view) {
view.render();
if (view.onShow) {
view.onShow();
}
};
rm.show = function(view) {
closeView(currView);
currView = view;
openView(currView);
};
return rm;
})();
This is my View cleaning up function:
Backbone.View.prototype.close = function() {
if (this.onClose) {
this.onClose();
}
if (this.views) {
_.invoke(this.views, 'close');
}
// Unbind any view's events.
this.off();
// Unbind any model and collection events that the view is bound to.
if (this.model) {
this.model.off(null, null, this);
}
if (this.collection) {
this.collection.off(null, null, this);
}
// Clean up the HTML.
this.$el.empty();
};
I tried appending the View els directly to the body and using this.remove(); in the View clean-up function (instead of using a common el: $('#content') to which I am appending elements, then cleaning up by this.$el.empty()), but that didn't work either.
It might have something to do with my "global Events":
Backbone.Events.on('letterMouseDown', this.letterMouseDown, this);
But I take care of them with the onClose function:
onClose: function() {
Backbone.Events.off('letterMouseDown');
}
One problem I see is that your close function never removes the event delegator from the view's el. A view's events are handled by using the delegator form of jQuery's on to attach a single event handler to the view's el. Your close does:
this.$el.empty();
but that only removes the content and any event handlers attached to that content, it does nothing at all to the handlers attached directly to this.el. Consider this minimal example:
var V = Backbone.View.extend({
events: {
'click': 'clicked'
},
clicked: function() {
console.log('still here');
}
});
var v = new V({ el: '#el' });
v.close();
After that, clicking on #el will throw a 'still here' in the console even though you think that the view has been fully cleaned up. Demo: http://jsfiddle.net/ambiguous/aqdq7pwm/
Adding an undelegateEvents call to your close should take care of this problem.
General advice:
Don't use the old-school on and off functions for events, use listenTo and stopListening instead. listenTo keeps track of the events on the listener so it is easier to remove them all later.
Simplify your close to just this:
Backbone.View.prototype.close = function() {
if(this.onClose)
this.onClose();
if(this.views)
_.invoke(this.views, 'close');
this.remove();
};
Don't bind views to existing els. Let the view create (and own) its own el and let the caller place that el into a container with the usual:
var v = new View();
container.append(v.render().el);
pattern. If you must attach to an existing el then the view should override remove with a slightly modified version of the standard implementation:
remove: function() {
this.$el.empty(); // Instead of removing the element.
this.undelegateEvents(); // Manually detach the event delegator.
this.stopListening();
return this;
}
I'm pretty sure I found the root for my problem.
mu is too short was right, with the close() method I wasn't removing the events bound directly to my el (which I tried to do by this.off() - this.$el.off()/this.undelegateEvents() is the correct way). But for me, it only fixed the problem that events got called multiple times unnecessarily.
The reason I was plagued by 'zombie views' or unintended behavior was that I wasn't freeing up the memory in the View..
this.remove() only gets rid of the el and it's elements/events, but not the View's internal variables. To elaborate - in my View I have an array declared like so this.array: [] and I didn't have it freed in the onClose function.
All I had to do was empty it in the onClose function or initially declare the array as this.array: null so on recurrent View renderings it would at least free the previous array (it still should be freed on the onClose method though, because the array/object is still going to sit in the memory until browsing away from the page).
It was excruciating to debug, because it's a crossword game (at least my code is hard to read there) and sometimes the words didn't match up, but I didn't know where the problem was coming from.
Lessons learned.
How can I test that an element fired an event with mocha? I've got an ugly solution working, but it's not very readable, takes a long time to time out when it fails, and doesn't give good failure messaging.
describe('test-element', function() {
var el;
beforeEach(function() {
el = document.createElement('test-element');
});
it('fires a save event', function(done) {
el.addEventListener('save', function() {
done();
});
el.save();
});
In a perfect world, I think something like this would be cooler.
it('fires a save event', function() {
el.save();
expect(el).to.have.firedEvent('save');
});
});
Am I going about this the right way? Is there a better approach or a custom matcher library I should be using?
How about spying on the fire function...?
Not sure what stubbing/spying library you're using but lets say Sinon.JS. So something like...
var spy = sinon.spy(el, 'fire');
el.save();
expect(spy.calledWith('save')).to.be.true;
tl;dr: The initial question was "How to trigger a callback every digest cycle?" but the underlying question is much more interesting and since this answers both, I went ahead and modified the title. =)
Context: I'm trying to control when angular has finished compiling the HTML (for SEO prerendering reasons), after resolving all of its dependencies, ngincludes, API calls, etc.
The "smartest" way I have found so far is via checking whether digest cycles have stabilized.So I figured that if I run a callback each time a digest cycle is triggered and hold on to the current time, if no other cycle is triggered within an arbitrary lapse (2000ms), we can consider that the compilation has stabilized and the page is ready to be archived for SEO crawlers.
Progress so far: I figured watching $rootScope.$$phase would do but, while lots of interactions should trigger that watcher, I'm finding it only triggers once, at the very first load.
Here's my code:
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch('$$phase', function (newPhase) {
if (newPhase) {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function () {
alert('Page stabilized!');
}, 2000);
}
});
Solution: Added Mr_Mig's solution (kudos) plus some improvements.
app.run(function ($rootScope) {
var lastTimeout;
var off = $rootScope.$watch(function () {
if (lastTimeout) {
clearTimeout(lastTimeout);
}
lastTimeout = setTimeout(function() {
off(); // comment if you want to track every digest stabilization
// custom logic
}, 2000);
});
});
I actually do not know if my advice will answer your question, but you could simply pass a listener to the $watch function which will be called on each iteration:
$rootScope.$watch(function(oldVal, newVal){
// add some logic here which will be called on each digest cycle
});
Have a look here: http://docs.angularjs.org/api/ng/type/$rootScope.Scope#$watch
I'm looking for a simple event aggregator that works with require.js. I have two modules, one containing a view model and another with a "listener" of some sort:
// view model
define(['lib/knockout-2.2.1', 'events/aggregator'], function(ko, events){
var squareViewModel = function(contents) {
this.click = function(){
events.publish('squareClicked', this);
};
};
return squareViewModel;
});
// some listener of some kind
define(['events/aggregator'], function(events){
events.subscribe('squareClicked', function(e){
alert("hurrah for events");
});
});
Is there anything out there that does this? Is this kind of architecture even a good idea? This is my first foray into client-side architecture.
This is similar to what you posted, but I've had good luck with extending Backbone events (you don't actually have to use anything else about Backbone for this to work), something like this:
define(['underscore', 'backbone'], function( _, Backbone ) {
var _events = _.extend({}, Backbone.Events);
return _events;
});
And then all your viewmodels (or any code really) can use it:
define(['knockout', 'myeventbus'], function(ko, eventBus){
return function viewModel() {
eventBus.on('someeventname', function(newValue) {
// do something
});
this.someKOevent = function() {
eventBus.trigger('someotherevent', 'some data');
};
};
});
The original idea came from this article by Derick Bailey. One of my other favorites on client-side events is this one by Jim Cowart
Again, it amounts to nearly the same thing as what you posted. However, the one thing I don't like about the approach of tying it to jQuery document DOM node is that you might have other types of events flying through there as well, bubbled up from child nodes, etc. Whereas by extended backbone events you can have your own dedicated event bus (or even more than one if you e.g. wanted to have separate data events vs. UI events).
Note regarding RequireJS in this example: Backbone and Underscore are not AMD-compatible, so you'll need to load them using a shim config.
I ended up using jQuery to make my own:
define([], function(){
return {
publish: function (type, params){
$(document.body).trigger(type, params);
},
subscribe: function(type, data, callback){
$(document.body).bind(type, data, callback);
},
};
});
It works for what I want it for, but it has not been extensively tested.
As explunit points out in his answer, this will capture any events on document.body. I also discovered a scoping issue when accessing this inside of a passed callback function.
I googled on how to unit test but examples are so simple. the examples always show functions that return something or do ajax that returns something - but never have i seen examples that do callbacks, nested callbacks and functions that are "one-way", that they just store something and never return anything.
say i have a code like this, how should i go about testing it?
(function(){
var cache = {};
function dependencyLoader(dependencies,callback2){
//loads a script to the page, and notes it in the cache
if(allLoaded){
callback2()
}
}
function moduleLoader(dependencies, callback1){
dependencyLoader(dependencies,function(){
//do some setup
callback1()
});
}
window.framework = {
moduleLoader : moduleLoader
}
}());
framework.moduleLoader(['foo','bar','baz'],function(){
//call when all is loaded
})
This illustrates a problem with keeping things private in an anonymous function in javascript. It's a bit difficult to validate that things are working internally.
If this was done test first then the cache, dependencyLoader and moduleLoader should be publicly available on the framework object. Or else it would be difficult to validate that the cache was handled properly.
To get things going I'd recommend you take a gander on BDD, that conveniently gives you an approach to help you start by letting you spell out the behaviour with a given-when-then convention. I like to use Jasmine, which is a javascript BDD framework (that integrates with jstestdriver), for this kind of thing and the unit tests I'd make for the sample you have above would be:
describe('given the moduleloader is clear', function() {
beforeEach(function() {
// clear cache
// remove script tag
});
describe('when one dependency is loaded', function() {
beforeEach(function() {
// load a dependency
});
it('then should be in cache', function() {
// check the cache
});
it('then should be in a script tag', function() {
// check the script tag
});
describe('when the same dependency is loaded', function() {
beforeEach(function () {
// attempt to load the same dependency again
});
it('then should only occur once in cache', function() {
// validate it only occurs once in the cache
});
it('then should only occur once in script tag', function() {
// validate it only occurs once in the script tag
});
});
});
// I let the exercise of writing tests for loading multiple modules to the OP
});
Hope these tests are self explanatory. I tend to rewrite the tests so that they nest nicely, and usually the actual calls are done in the beforeEach functions while the validation are done in the it functions.