AngularJS- why am I needing $scope.$apply within $scope.$on? - angularjs

I have some code that is not behaving as expected... I have an event listener within a AngularJS controller like this:
$scope.$on("newClipSelected", function(e) {
$scope.$apply(function() {
$scope.isReady = true;
});
});
The controller's markup has a ng-show='isReady'. When this event handler runs, the ng-show region shows as expected. However, this event handler does not work:
$scope.$on("newClipSelected", function(e) {
$scope.isReady = true;
});
With this event handler, if I use the debugger to expect the AngularJS scope I do see that $scope.isReady=true, however the ng-show element is not showing.
Its my understanding that $scope.$on will in fact call $watch/$apply as appropriate. So why am I needing to call $apply in this case?
The event is being triggered by a call to $rootScope.$broadcast() within a non-angularJS asynchronous completion event.

No, angular doesn't fire $apply on the events so if $broadcast is called outside angular's context, you are going to need to $apply by hand.
Check the source here

$on and $broadcast doesn't call $apply for you, you need to call it yourself. I'm not sure why that is, but my guess is that it's because a digest is a bit expensive, and it would be a cost to run a digest cycle for every event.

Related

invoking directive controller method from another directive's controller

I have a directive DirectiveA which has a method x defined inside its controller. I need to invoke this method from another directive's (DirectiveB) controller.
The problem is with the app I work on because when you need to go to a specific route, you basically invoke another directive which is then rendered by Angular. What I am looking for is to find a way to call x method after the page was rendered. That x is going to display an element on my page when it is called.
Can you help me with this?
You might want to use events, you can fire event up the scope by using $emit, or down by using $broadcast. You catch/subscribe the event using the $on listener.
So, on your directive's controller you'll listen to the event (You can also subscribe $rootScope but then you need to manually unbind the listener upon directive destroy)
$scope.$on('mySpecialEvent', function (event, data) {
... DO YOUR STUFF ...
});
And on DirectiveB you'll fire the event when you want to execute the action in DirectiveA's listener:
$rootScope.$broadcast('mySpecialEvent', 'HI');

AngularJS $scope.$on() and $timeout()

I am new to AngularJS. Please see the code below and tell me what it is doing.
$scope.$on('$viewContentLoaded', function(event) {});
How to use it in a controller to access the DOM?
$timeout(function() { });
I am looking for explanation and example of how to use $scope.$on() and $timeout() in real life and what it does.
$scope.$on registers a listener for the event passed as the first parameter and executes the function passed as the second on each instance of said event. $broadcast and $emit can be used to send out custom events of your own.
$timeout can be used in place of setTimeout but when called with no delay argument will simply wait for the next digest before executing its callback function.
As for DOM manipulation, this should not be carried out in a standard 'jQuery like fashion'. If manipulation of the DOM is required a custom directive can be defined to encapsulate this functionality and therefore allow the Angular framework to govern its syncopation.

Forcing a digest in Angular

At some point after a user action I would like to cause a digest to occur, so the UI reflects a change in the data-model backing it.
I have a service that performs some change in a callback (asynchronously).
I understand that $scope only makes sense in the context of a controller. Can I achieve the same effect by performing $apply() on the $rootScope?
I have seen code that checks for $$phase or similar related to avoiding digest errors, what checks should I perform in order to trigger a digest safely?
See this answer: Running $apply on $rootScope vs any other scope
You can call $rootScope.$apply() outside of a controller (i.e. in a service) in order to trigger a digest loop.
Alternatively, you could consider using $broadcast and $on to send a notification to other parts of your app when something needs refreshing. (See Understanding Angular’s $scope and $rootScope event system $emit, $broadcast and $on)
// in a service
$rootScope.$broadcast('myCustomEvent', {
someProp: 'foobar'
// other data
});
// in a controller or another service
$scope.$on('myCustomEvent', function (event, data) {
console.log(data);
// do something with this event
});

Why is $digest not getting invoked after I changed a model on $scope?

I have an asynchronous call which is making a change a value on $scope. When it completes, I don't see my view updated, but if I append a $scope.digest() I do see the view update. e.g.
// a doesn't update in view
$rootScope.$on('some_event', function() {
$scope.a = true;
});
// a does update in view
$rootScope.$on('some_event', function() {
$scope.a = true;
$scope.$digest();
});
According to http://www.sitepoint.com/understanding-angulars-apply-digest/ the $digest cycle repeats itself until the $scope has settled (a minimum of two times).
Why wouldn't I see this update?
Thanks!
Dispatching event either by $emit or $broadcast functions call don't start digest cycle itself. Thus, if you have an event dispatched from async code like window.setTimeout or some third-party library, you must use $scope.$apply in event handler or $scope.$digest in code which fire event.

When is it Necessary to Use Deregistration Functions Returned by $on and $watch methods?

Is it recommended to always hang on to the $on method, as in
var dereg = $scope.$on(...)
And later
$scope.$on("$destroy", function() { dereg(); });
Or is this only needed for certain situations? Same question for $watch
AFAIK, you should only use $destroy event when your app may have memory leak issues or zombie events.
$destroy is useful when you pass some of the directive's variables to another directive or controller, and after the element gets removed, yo want to remove it from the reference.
For instance, you may want to use $destory when your directive creates event handlers on global DOM elements and the element with the directive gets removed.
let's say this is a linking function inside a directive:
function myEventListener(){console.log('scroll!');}
$window.bind('mousewheel',myEventListener);
scope.$on('$destroy',function(){$window.unbind(myEventListener)});
If you don't unbind your event listener, then after the element with the directive gets removed, you will still have scroll! messages when you scroll.

Resources