1

I am reviewing someone else's code and I see that they have $scope.$apply in their directive.

The scenario is that we got some event from the DOM and we want to change the scope.

From my experience, directives should call apply. It causes some weird side effects.

One of them is in the directive's test. All my tests have the same pattern

$compile( "<the html>" )(scope); 
scope.$digest(); --> will error if directive calls apply
  • Should directives call apply?
  • What is the recommended solution for apply changes on scope when you get an event from DOM which is not wrapped in angular?
guy mograbi
  • 27,391
  • 16
  • 83
  • 122

2 Answers2

2

I would say that calling $scope.$apply or $scope.$digest is usually (although not always) a bad idea.
For your example, registering DOM event can go through angular using ng-click, ng-keydown, etc, which will conceal the need to call $apply or $digest.
The reason it is even needed, is obviously because there is some code executed "outside" angular, meaning, outside of the angular ecosystem and so basically angular doesn't "know" an event (or any other data related thing) has happened.
So to sum up, there should be a (very) good reason to call $apply or $digest.
How else? Well, you could encapsulate these event capturing inside your own directive (although most if not all of them are covered on angular). These is exactly what angular itself does and will result $apply or $digest only when actually needed by the event itself.

/EDIT/
For instance, a simplified version of angular's ng-click can be translated into your own directive:

app.directive('myClick', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            var clickHandler = $parse(attr.myClick);
            element.on('click', function(event) {
                // Do some of your own logic if needed.
                scope.$apply(function() {
                    // Calling the event handler.
                    clickHandler(scope, {$event: event});
                });
            });
        }
    }
}]);  

By encapsulating this event handler, it can be reused (in this a form of a directive) and because being part of angular's world, any other logic using this directive, doesn't have to worry about $apply or $digest. It also means it can be used declaratively now (rather then operatively) which is what angular aspires anyway.

One thing to notice, this directive doesn't isolate its scope and doesn't introduce any other new variables on the scope (the event handler is being parsed on the linking function). This is important because it means there are is no overhead side effects on the parent scope (the scope that needs to "know" about this event - which is basically the main scope), since the directive's scope is inherited.

P.S You can also consider overriding directives or decorating other services on angular.

Community
  • 1
  • 1
Tomer
  • 4,382
  • 5
  • 38
  • 48
  • can you please elaborate on "encapsulate these events" ? – guy mograbi Nov 05 '14 at 15:24
  • so this is what I don't get - what will happen if I click a button, while I am already in a digest cycle? – guy mograbi Nov 07 '14 at 07:47
  • Another digest will take place. A simple way to think of that is angular tries to keep everything - that is the View and the View Model, synced. And so any event within the "angular's boundaries" causes the scope to perform a digest cycle (which might trigger its parent scope to digest). This digest will recursively call itself as long as something has became "dirty" from the previous digest (this include events as well as model modifications). It will stop once either no modification happened or it reach a certain depth of digesting. – Tomer Nov 07 '14 at 21:20
  • B.T.W, does this answer your question? – Tomer Nov 08 '14 at 06:18
  • "Another digest will take place" - but that causes a "digest already in progress" message. doesn't it? – guy mograbi Nov 08 '14 at 09:35
  • No it doesn't. Since the event is raised by a native DOM (through jQuery registration). – Tomer Nov 08 '14 at 09:54
  • sure.. still trying to figure out if it answers my question. – guy mograbi Nov 09 '14 at 18:26
0

Well... if your directive wrapps some native events or anything outside of the Angular scope, you dont have much more options than calling "$apply()". From my experience this will only cause an error if this function is called from both, WITHIN the angular scope and from outside (e.g. ng-click as well as window-click event or something). If this is a case, you can still use the $timeout-Service. It's not the nicest solution, but from what I`ve heard its even the suggested one from the angular team.

Narm
  • 10,677
  • 5
  • 41
  • 54
David Losert
  • 4,652
  • 1
  • 25
  • 31