Use $timeout to force execution, is a bad practice? - javascript

I am working with ionic 3. So I have this code on controller and it works
$scope.note = 'Lorem...';
$rootScope.$on('Active',function() {
$timeout(function() {
$scope.note = 'test';
},0);
});
But why this not work?
$scope.note = 'Lorem...';
$rootScope.$on('Active',function() {
$scope.note = 'test';
});
What is the best approach for this?

In this context it can be considered a bad practice. Spontaneous use of $timeout usually indicates that a developer doesn't know if the code runs inside or outside of digest cycle and tries to play it safe.
The explantion why this doesn't work stays outside of the scope of posted code, but the reason is that this code runs outside of a digest. This depends on where Active scope event is triggered, and this is what a developer should care about in the first place, since scope events don't necessarily happen inside of digest cycle.
If the event is known to happen outside of a digest, digest-dependent code should be wrapped with $apply:
$scope.$on('Active',function() {
$scope.$apply(function() {
$scope.note = 'test';
});
});
If the event is known to happen both inside and outside of a digest, code should be wrapped with $evalAsync:
$scope.$on('Active',function() {
$scope.$evalAsync(function() {
$scope.note = 'test';
});
});
$timeout(...) is supposed to be used only when it's behaviour is wanted, i.e. one tick delay or more and a digest.
As it was suggested by #georgeawg, it is also a bad practice to use $rootScope as global event bus in controllers (they have access to child scopes). This is basically an antipattern that may cause memory leaks. Considering that an event was $broadcasted, it will propagate to child scopes. As a rule of thumb, it should be $scope.$on(...), unless there are reasons why it should be done on $rootScope specifically.

This happens when the view does not get notified of a change in controller. It usually happens for ng-repeat arrays/objects or inside the forms that do not use a global object. The $timeout solution works or you can call $scope.$apply(). Please look at this document for more information:
https://www.sitepoint.com/understanding-angulars-apply-digest/

Related

Understanding specific $scope.$apply(fn) implementation

What does the following function wrapped by scope.$apply do? I can't seem to find the answer to this, but I see examples where it is used in directives.
scope.$apply(function() {
fn(scope, {
$event: evt
})
});
The closest explanation I could find implies that this might be used when the event that you want to respond to is not handled by Angular directives. Here is the explanation I am referring to.
If someone could provide the intended use of this pattern and what it means, it would be appreciated.
EDIT 1
I should have seen this earlier. Must need more sleep.
Since my example is not a complete working one. It makes a lot more sense after looking at the referenced explanation carefully.
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.
scope.$apply is used to manually trigger Angular's digest cycle for any async events that happen outside of Angular's execution context.
One such async event is element.on("click", function(e){...}) (or any other event related captured with .on), but could also be other async function outside of Angular context.
The second part is an invocation of the "$parsed" expression. It accepts a scope as a parameter and a map of "local" variables, such as {$event: evt}. The intent is every similar to what scope: "&" is doing - but without creating an isolate scope. For example, if the expression is:
<my-directive p="doSomething(foo)">
then, if doSomething(foo) is $parsed, the caller can supply the value of foo:
var parsedFn = $parse(attrs.p);
parsedFn(scope, {foo: 5})'
This will cause the invocation of doSomething(5)
Posting an answer to this since it makes sense to me (see my edit).
The fn(scope, {$event: evt}) call is invoking the parsed reference to a custom function via the directive parsed in the following line:
var fn = $parse(tAttrs.myContextmenu);
So the target function implementation is capturing a specific event via the directive and then suppressing it.
So I guess this is useful when you do not want to clutter directives with controller specific functions and maybe fire a different event in response to another event then let a controller handle it.

Does $scope.$on('$destroy', ...)'s event handler get destroyed?

Ran across a very "down-the-rabbit-hole" Angular question today I couldn't find the answer to. From the $scope docs, you can register an event handler on "$destroy", which is called right before a scope's destruction. That way, you can deregister event handlers like so:
var deregister = $scope.$on('myCustomEvent', function () {
// do some crazy stuff
});
$scope.$on('$destroy', function () {
deregister();
});
However, the $scope.$on('$destroy', ...) must create its own handler. Is that automatically destroyed, or do you have to do something like the following to destroy it?
var deregister = $scope.$on('myCustomEvent', function () {
// do some crazy stuff
});
var deregisterDestroy = $scope.$on('$destroy', function () {
deregister();
deregisterDestroy();
});
The answer is actually "maybe" depending on what you mean by it being automatically destroyed. If we look at the source for the $destroy method for scopes, we can see that while a $destroy event is broadcasted downward throughout child scopes, the actual $destroy method is never invoked on any scope but the initial one. That means that the actual cleanup and nulling out of properties never occurs on child scopes.
The reason that this doesn't leak memory is because once $destroy has been invoked on a scope, it becomes detached from the parent scope and is therefore eligible for garbage collection since it should no longer have any path to the GC Roots. This same logic applies to all child scopes since they also should have no paths to the GC Roots.
Your example is safe though; I do that myself in the same manner to clean up my own handlers when necessary and do not run into any kind of infinite loops.

How to unbind $on in Angular?

I have a code that use $scope.$on one time on init and then in a function, so the code is executed multiple times. How can I unbind if first before I bind it again. I've try $scope.$off but there's not such function, https://docs.angularjs.org/api say nothing about $on. I'm using angular 1.0.6.
If you don't un-register the event, you will get a memory leak, as the function you pass to $on will not get cleaned up (as a reference to it still exists). More importantly, any variables that function references in its scope will also be leaked. This will cause your function to get called multiple times if your controller gets created/destroyed multiple times in an application.
Fortunately, AngularJS provides a couple of useful methods to avoid memory leaks and unwanted behavior:
The $on method returns a function which can be called to un-register the event listener.
Whenever a scope gets cleaned up in Angular (i.e. a controller gets destroyed) a $destroy event is fired on that scope. You can register to $scope's $destroy event and call your cleanUpFunc from that.
See the documentation
Sample Code:
angular.module("TestApp")
.controller("TestCtrl",function($scope,$rootScope){
var cleanUpFunc = $scope.$on('testListener', function() {
//write your listener here
});
//code for cleanup
$scope.$on('$destroy', function() {
cleanUpFunc();
};
})
$scope.$on returns a function which you can call to unregister.

Why should I make $scope.$apply()?

I have a service that make
$rootScope.$broadcast('myEvent', somedata)
from time to time. In controller I do
$scope.$on('myEvent', function (evt, somedata) { $scope.data = somedata })
The question is if I omit
if (!$scope.$$phase) { $scope.$apply(); }
in controller's event listener, then view won't change. Why is that? Is there any better way to do it?.
It is because $apply is a lazy worker and will do the job only there is enough stuff to refresh. You can't control when, unless you explicitly call $scope.$apply.
Yes, there is a better way to do this : call safeApply. Because calling explicitly many $apply can cause conflicts. There is no official safeApply implementation, so can choose you poison :
here : https://coderwall.com/p/ngisma
or there : AngularJS : Prevent error $digest already in progress when calling $scope.$apply()

Can dynamically loaded JavaScript be unloaded?

I am writing a web application that has a static outer "shell" and a dynamic content section. The dynamic content section has many updates as users navigate the system. When a new content block is loaded, it may also optionally load another JavaScript file. In the name of good housekeeping, I remove script blocks from the DOM that apply to old content blocks, since that JavaScript is no longer needed.
The problem comes next, when I realized that although I have removed the <script> element from the DOM, the JavaScript that was previously evaluated is still available for execution. That makes sense of course, but I'm worried that it may cause a memory leak if the users navigate to a lot of different sections.
The question then, is should I be worried about this situation? If so, is there a way to force the browser to cleanup stale JavaScript?
<theory>You could go with a more object-oriented approach, and build the model in a way that each block of javascript blocks come in as their own objects, with their own methods. Upon unloading it, you simply set that object to null.</theory>
(This is fairly off-the-cuff.)
Memory use is indeed an issue you need to be concerned with in the current browser state of the art, although unless we're talking about quite a lot of code, I don't know that code size is the issue (it's usually DOM size, and leftover event handlers).
You could use a pattern for your loadable modules that would make it much easier to unload them en mass -- or at least, to let the browser know it can unload them.
Consider:
window.MyModule = (function() {
alert('This happens the moment the module is loaded.');
function MyModule() {
function foo() {
bar();
}
function bar() {
}
}
return MyModule;
})();
That defines a closure that contains the functions foo and bar, which can call each other in the normal way. Note that code outside functions runs immediately.
Provided you don't pass out any references to what's inside the closure to anything outside it, then window.MyModule will be the only reference to that closure and its execution context. To unload it:
try {
delete window.MyModule;
}
catch (e) {
// Work around IE bug that doesn't allow `delete` on `window` properties
window.MyModule = undefined;
}
That tells the JavaScript environment you're not using that property anymore, and makes anything it references available for garbage collection. When and whether that collection happens is obviously implementation-dependent.
Note that it will be important if you hook event handlers within the module to unhook them before unloading. You could do that by returning a reference to a destructor function instead of the main closure:
window.MyModule = (function() {
alert('This happens the moment the module is loaded.');
function foo() {
bar();
}
function bar() {
}
function destructor() {
// Unhook event handlers here
}
return destructor;
})();
Unhooking is then:
if (window.MyModule) {
try {
window.MyModule();
}
catch (e) {
}
try {
delete window.MyModule;
}
catch (e) {
// Work around IE bug that doesn't allow `delete` on `window` properties
window.MyModule = undefined;
}
}
If you save the evaluated code in namespaces, such as:
var MYAPP = {
myFunc: function(a) { ... }
}
"Freeing" the whole thing should be as simple as setting MYPP to some random value, ala
MYAPP = 1
This does depend on there being no other means of referencing the variable, which isn't trivial
How about loading the JS files into an iframe? Then (in theory, never tested it myself) you can remove the iframe from the DOM and remove the "memory" it's using.
I think... or I hope...
If you are worried about memory leaks then you will want to make certain that there is no event handlers in the code you want removed referring to the still existing dom tree.
It may be that you need to keep a list of all event handlers your code added, and before unloading, go through and remove the event handlers.
I have never done it that way, I always worry about when I remove nodes that there is still a reference.
Here is a good article on javascript memory leaks:
http://javascript.crockford.com/memory/leak.html
JavaScript interpreters have garbage collectors. In other words, if you don't reference anything, it won't be keeping them around.
One of the reasons why it is good to use JSON with a callback function (JSONP).
example, if you HTTP response for each JS is:
callback({status: '1', resp: [resp here..]});
And if callback() does not create a reference to the JSON object passed in as an argument, it will be garbage collected after the function completes.
If you really need to make a reference, then you probably need that data around for some reason - otherwise you would/should NOT have referenced it in the first place.
The methods mentioned to namespace objects just creates a reference that will be persisted until the reference count comes to 0. In other words, you have to track every reference and delete it later, which can be hard when you have closures and references from DOM lying around. Just one reference will keep the object in memory, and some simple operations may create references without you realizing it.
Nice discussion. Clears up a lot of things. I have another worry, though.
If I bind window.MyModule.bar() to an event, what happens if the event accidentally gets triggered after window.MyModule is deleted? For me, the whole point of namespacing and separating js into dynamically loaded modules is to avoid triggering event handlers cross-module by mistake.
For example, if I do (excuse my jQuery):
$('.some-class').click(window.MyModule.bar);
What happens if I delete window.MyModule, load another module, and click on an element which accidentally has a class called some-class?

Categories