Trigger $digest from external code - javascript

I have an application that exposes an methods (from various angular services) to code that is loaded and eval'd at runtime. What is the best way to guarantee $digest triggers when these methods are called from outside angular, taking into account that they might also be called from within angular? Should I expose a separate interface wrapped in $scope.$apply instead of exposing the service methods directly?

Quick solution would be to call $evalAsync. From documentation:
" if this function is called outside of a $digest cycle, a new $digest cycle will be scheduled. " https://docs.angularjs.org/api/ng/type/$rootScope.Scope
My advice to expose something outside Angular is to expose element (by providing ID or selector) where you have scope or (better) controller attached and then use 'angular.element(someDomEkenent).scope().someFunction()'
In this case you would avoid possibility to capture unessesary objects that can lead to memory leaks that is very hard to debug

call $timeout() function with no time.
for example
function someEventOutsideAngualer(value) {
$timeout(function(){
$scope.bindedValue = value;
})
}
The idea is that when $timeout callback is executed, in our case it will be picked up with the next $digest. Please do not use $scope.$apply() and $digest triggers. They cause more problems than they solve, if not properly understood

Related

Is calling $scope.$digest within $scope.$on fundamentally incorrect?

I have inherited some AngularJS code, and I know very little about it (the code and Angular both). The code that I inherited contains several places where $scope.$digest is called within a $scope.$on method within a controller. For example:
$scope.$on("ScriptEditorEnabled", function (e, enabled) {
$scope.scriptEditorDisabled = !enabled;
$scope.$digest();
});
In my context, this is causing the popular $digest already in progress error (AngularJS : Prevent error $digest already in progress when calling $scope.$apply()). Isn't an $on method part of the $scope object, and thus, any changes to its data would be picked up automatically? If so, doesn't that mean that calling $digest within one (either directly or indirectly), just plain incorrect?
I've been simply removing these calls, seemingly without any loss in functionality, and the errors go away. Is there any dangers I should be aware of when doing this?
$scope.$on gets called in response to $scope.$broadcast or $scope.$emit. The $scope.$on should always assume that it runs inside a digest loop. Neither $apply nor $digest calls should be needed inside the $on handler.
Angular is pretty clear about it in the When to use $scope.$apply(). If you dispatch events from non-angular environments, you should wrap the $emit or $broadcast call in $apply instead of calling it in the event handler. Example:
$rootScope.$apply($rootScope.$broadcast('recievedMsg', msg));
It's dispatcher responsibility to enter the digest loop if it knows there isn't one started already.
Isn't an $on method part of the $scope object, and thus, any changes
to its data would be picked up automatically?
No. $scope.$on callback is triggered by $scope.$emit or $scope.$broadcast. If you inspect source code of both of them, you'll see that they don't by itself trigger $digest loop that checks for changes. This means that callback on $scope.$on can be executed from within $digest loop as well is outside of it. A good approach is to have a convention which defines whether the $scope.$emit or $scope.$broadcast$ should be executed inside $digest loop or the callback within $scope.$on should be executed inside $scope.$apply. Usually, it's the code that triggers event ensures the digest loop by using $scope.$apply().
A solution could be to use $scope.$evalAsync instead of $scope.$apply to avoid digest already in progress when calling $scope.$apply() error.

When should I call $digest instead of $apply?

I have seen both
$rootScope.$apply();
and
$rootScope.$digest();
being used inside a factory. However Angular docs say:
Usually, you don't call $digest() directly in controllers or in
directives. Instead, you should call $apply() (typically from within a
directive), which will force a $digest().
If you want to be notified whenever $digest() is called, you can
register a watchExpression function with $watch() with no listener.
In unit tests, you may need to call $digest() to simulate the scope
life cycle.
One of the advantages of not calling $digest directly is to avoid running an infinite loop.
Are there any cases in which one should use $digest over $apply (apart from testing, as mentioned in the quote)?
When your application becomes sufficiently large enough that digests become prohibitively expensive, you can call digest on scopes that you're sure don't cause side-effects on other parts of your application.
For instance you may have introduced a jQuery calendar plugin, that you would ordinarily call $apply on to observe its bindings to a scope. This calendar might be for a feedback form so it is, so to speak, independent to the rest of your application, which may be a large real-time data feed that has thousands of watchers.
A scope.apply on your calendar would go through your whole application and do dirty checks on all your thousands of watchers for your real-time feed when it is unnecessary. In this situation, you should call $scope.$digest() because you know only the calendar model has changed.
I think the documentation is unhelpful here. As far as i know:
$digest will process all watchers of the current scope and its children.
$apply() is more or less a wrapper around $rootScope.$digest and thus will run a full digest on all scopes.
Unlike the doc says you can use both methods to pull an update from outside Angular into the digest cycle. As already said, $apply may invoke a full digest cycle, meaning $rootScope.$digest(). Calling $digest instead will most likely prevent that from happening and is better for performance.
If you want to be absolutely certain that Angular recognizes the change, use $apply. If you know what you are doing and only need to update the current scope (or its children) use $digest.

Turn $scope functions into regular var functions. Has any impact in the performance?

I have around 6 functions in my code that are not interacting with anything in the DOM, and I need those only in one specific scope.
So, is there any issue if I turn this
$scope.verifyPlaceBetAvailable = function(param) {
//something happens here
}
into this
var verifyPlaceBetAvailable = function(param) {
//something happens here
}
?
I mean, that is going to have any impact in the performance of my app?
I think if you don't populate the $scope with function that will not have affect on it or on the DOM should be better....
First of all, you should put all helpers or methods into a service, such as factory, service or provider.
About performance, I think the issue is not what you're asking. I believe can impact more about how they're written and what kind of data they're processing.
For 6 simple methods it shouldn't affect to the performance.
You should add functions in $scope only if you need them in a template otherwise use local variables or injected services if it has some business logic and does not require $scope. And it is rather improves app performance than decreases it.
You don't have to put every method in controller in scope.
As long as the method is not used in view or not interacting with anything in the DOM than you can remove that function from scope. There is no major performance issue.
For utility methods you should use factory,service or provider in angular js.
Refer AngularJS: Service vs provider vs factory
You only need to put a function in the scope if you are calling it from a view. Any other type of function could be a simple var in the controller, however many of these functions should probably be in services or factories, and not directly implemented in the controller anyway.

AngularJS: Events broadcasted/emitted on rootScope occur multiple times

My Angular application is fairly big already and I am using the rootScope to communicate between directives and controllers.
Directive 1
scope.$root.$broadcast('some:event');
Controller 1
$rootScope.$on('some:event', function() { I get called multiple times :( });
$scope.$on('some:event', function() { I am not getting called at all :( });
For some reason, my listeners get called multiple times (2x to be exact). I have the feeling that somewhere, I built in a second rootScope or something. I am currently debugging my app, but it is like finding the needle in the haystack.
This thread tries to solve a similar problem: AngularJs broadcast repeating execution too many times. It suggests to use $scope only which does not work for me in my particular case. The broadcasted events never reach the listeners.
My question would be if someone has an idea why this could happen? Maybe I am doing a silly mistake I am not aware of. Catching an event twice I only sent once does sound wrong.
Thanks in advance
use
$rootScope.$emit('some:event') ;
because it goes upwards and rootscope ist the top level
use
var myListener = $rootScope.$on('some:event', function (event, data) { });
$scope.$on('$destroy', myListener);
dont forgot to unregister on destroy and u have a bus communication on the same level
Edit: To your problem: maybe some other listener prevent it from bubbling further like this:.
$scope.$on('some:event', function (event, data) {
event.stopPropagation();
});
And the reason why your listener receive mulltible times could be that u send multible times. test with sending a timestring with your event and see if it is the same
Here is a fiddle with my Eventbus factory i use http://jsfiddle.net/navqtaoj/2/
Sometimes the good old observerpattern works also fine:http://jsfiddle.net/b742qdpz/ if it is to much overhead to watch a value in a service
Tighten Your Module & Scope
Many times, when you're experiencing multiple dispatches of an event -- or more specifically, when you've multiple Handlers being invoked then your modules need to utilize locality more.
By locality I mean, try not to use globals as much as possible. This can (and usually does) occur, usually, when you're listening/firing events off of $rootScope instead of $scope -- which is more local.
What Angular Does (In a Nutshell)
Angular has a special way of event-subscription-binding which differes based upon scope.
If an event is subscribed to using a controller, which uses a perishable scope, then Angular automatically unsubscribes to each event when the controller closes.
However, when subscribing using $rootScope, Angular will not insist on detaching these subscription-bindings. So...
TL;DR
Every time you load a view -- effectively constructing a new controller -- you are undoubtedly wiring up another listener for each subscription.
Hope this helps!

How do I create a globally-available callback API that DOESN'T use $scope?

I want my main module to offer an api which allows any directive, controller, factory, etc. to register a function to be called when a user resizes a div.
Right now I'm successfully doing this by using jQuery's resizable callback to update x and y variables on scope then calling $apply(). The problem, as you can imagine, is that when a user is dragging a div, it calls the resize callback very rapidly. Rapidly calling $apply() isn't ideal for performance reasons.
I thought about using a service/factory, but there's an indeterminate number of resizable divs/directives and I'm not sure a singleton makes sense here.
Is there a way I can create an "informal" API that doesn't use $scope, doesn't trigger digest cycles, and doesn't use additional libraries? Perhaps there's a way using controllers and require: ^myCtrl? Do I need to store all the registered callback functions in an array?
What you're doing sounds pretty hacky, so it's probably not a big leap for you to publish angularjs events that get listened to on their scope. So to 'register' a function you listen on the scope for some custom event you create, execute whatever you want in the callback. Get your jquery to broadcast the event on $rootScope.
Have a read about angularjs events
Working with $scope.$emit and $scope.$on

Categories

Resources