angularJS element.on callback and scope.$apply
Asked Answered
J

2

6

In this example, I have an input with an attached directive. The directive is meant to display messages next to the input. There's another input and a button to add messages. Once some messages are displayed, focusing on the input with the attached directive should clear the messages. http://jsfiddle.net/viro/WBqxf/

So I have a directive with an isolated model, and I'm trying to update the model when the element which has the directive goes into focus. It seems like I have to wrap event callbacks in scope.$apply if I want to update the model:

element.on('focus',function(){
    scope.$apply(function(){
        console.log("focus !");
        scope.tstMsg=[];
    })
});

I suppose I have to wrap it in $apply because I'm using jqlite event callbacks and I guess they run "outside" angularJS, but I didn't find it clearly stated in the docs.

Am I doing it right or is it a hack ?

Is there a better way to do it ?

Josephus answered 12/12, 2013 at 23:46 Comment(2)
use ng-focus and save yourself extra coding for extrenal event handlers docs.angularjs.org/api/ng.directive:ngFocusVerdun
But for every element that has the directive attached, I want the same behavior on focus, regardless of the controller in which the element is. With ngFocus I'd have for every element to make a function that resets the array of messages, that's tedious if it can be automated by the directive, isn't it ?Josephus
S
2

scope.$apply will cause a $digest to be run so any watchers on the scope will check for changes and fire where appropriate. Since as you say you're just binding to an event (on just does addEventListener/attachEvent as appropriate) doing the scope.$apply in this context is valid.

Spartan answered 12/12, 2013 at 23:51 Comment(4)
So that means callbacks bound from element.on are not "angularJS aware" ?. I wasn't sure about that.Josephus
Yeah as you say in the question since it's just using jqLite or jQuery (if you include that) it's not within the context of angular code. Services like $http and $timeout automatically have the scope.$apply callSpartan
So, just like there is $timeout instead of setTimeout; is there something that should be used instead of element.on ?Josephus
One other thing to note here, passing a function to scope.$apply() will capture any errors that occur within the function to be handled with Angular error handling, if this isn't desired you can run your code then call scope.$apply. Regarding an angular service or the like for binding to events I don't think anything exists, there is $on but that is meant to work with $broadcast and $emit, but I don't think it has to do with DOM events.Spartan
G
3

Whenever you are using a thrid party library and perform changes you need to let Angular know by calling $apply().

As @charlietfl mentioned ng-focus is even easier:

Controler

$scope.focus = function() {
    // Do something
}

HTML

<input ng-model="inp" tst-msg="message" ng-focus="focus()" />

See jsFiddle

Goodloe answered 13/12, 2013 at 1:28 Comment(4)
For some reason when I did it without the $apply, it wouldn't work. I have to check that. ng-focus is not the solution for me because I want the directive to be self-sufficient. I don't want to have to code a function that will reset the messages for every controller in which I have an input with the directive attached.Josephus
I updated the jsFiddle (removed $apply()) and added the ng-focus. Have a look. I would be confused if it only works for me :)Goodloe
I had a look. Indeed it works. But if you remove the ng-focus, it doesn't. Your exemple helped me understand something: ng-focus being handled by angularJS, it does a $digest cycle on focus, so in that case I don't need to do it in the event callback. If I want to get rid of the ng-focus, I need the $apply.Josephus
@DavidV. Okay that makes sense :) Interesting coincidence. Sorry for the confusion.Goodloe
S
2

scope.$apply will cause a $digest to be run so any watchers on the scope will check for changes and fire where appropriate. Since as you say you're just binding to an event (on just does addEventListener/attachEvent as appropriate) doing the scope.$apply in this context is valid.

Spartan answered 12/12, 2013 at 23:51 Comment(4)
So that means callbacks bound from element.on are not "angularJS aware" ?. I wasn't sure about that.Josephus
Yeah as you say in the question since it's just using jqLite or jQuery (if you include that) it's not within the context of angular code. Services like $http and $timeout automatically have the scope.$apply callSpartan
So, just like there is $timeout instead of setTimeout; is there something that should be used instead of element.on ?Josephus
One other thing to note here, passing a function to scope.$apply() will capture any errors that occur within the function to be handled with Angular error handling, if this isn't desired you can run your code then call scope.$apply. Regarding an angular service or the like for binding to events I don't think anything exists, there is $on but that is meant to work with $broadcast and $emit, but I don't think it has to do with DOM events.Spartan

© 2022 - 2024 — McMap. All rights reserved.