Angularjs: setting focus on a previously-hidden input element inside a directive

angularjsangularjs-directiveangularjs-scope

Here is a simplified plunk to illustrate the problem

I got an angular directive that compiles some html code before visualizing it. In the html, there is an hiden input that becomes visible only if scope.isEditShown is true:

<input ng-show='isEditShown' type='text' value='{{content.name}}' class='title_edit'/>

The input appears when the scope.titleChange function is invoked. This function (bound to an ng-dblclick directive) just sets true to scope.isEditShown and tries to invoke jQuery's focus() method on the input element (previously stored in the scope in the linker function with scope.input = $("input:text", ae);:

scope.isEditShown = true;
scope.input.focus();

In short, I want to visualize a previously-hidden input when something is double-clicked, and give it immediate focus. The immediate focus is needed because the input is hidden when it loses focus (I want the user to immediatly be able to edit the input content. When the user clicks away, the input is hidden).

The problem is that it seems I can't give the focus to the input element programmatically.
After experimenting, I found that when scope.isEditShown = true; is executed, the input is not visible yet (= angular js doesn't show it in the DOM), and setting focus with scope.input.focus(); to a hidden input does nothing. When the input element finally gets shown, it's unfocused.

What is the angular way to do what I want? When can I be sure that my input is shown and when can I call focus() on it effectively? NOTE: if I use scope.$apply() before giving focus to the input, I will get $apply already in progress exception. If I do a safe apply, foucus won't be given. See the plunkr.

(ps: I really want to understand, so I'm not gonna use companion libreries that automagically do that for me)

Best Answer

When you make changes to some data-bound variable and you need to wait for the corresponding rendering to happen, you need to use the famous setTimeout 0 ( alternately in Angular the $timeout service ).

Here is a plunkr that shows how to do this.

http://plnkr.co/edit/N9P7XHzQqJbQsnqNHDLm?p=preview