Proposal for ngTarget, to support setting focus, blur and select.

2,941 views
Skip to first unread message

Broc Seib

unread,
Feb 5, 2013, 12:26:25 AM2/5/13
to ang...@googlegroups.com
Hi all,

I'd like to see some official support in Angular for setting focus on elements. This can address the same problem for blur() and select() at the same time. Here's the short version of the proposition -- it would look like:

  1: <input type="text" x-ng-model="form.color" x-ng-target="form.colorTarget">
  2: <button class="btn" x-ng-click="form.colorTarget.focus()">do focus</button>

I have some evidence (i.e. I've implemented it) that this directive will work inside ng-repeat too.

The other aspect to this that I would like to address is a timing issue. But it means assigning $watch callbacks a priority. The timing problem is when you "show" or "enable" and element followed immediately by a set focus. We need to be able to influence the order of these events.

After I started to look into this, I blogged in (too much) detail here: http://goo.gl/4rdZa

In this thread I'd like to hear discussion on two things:
 1) I would like to hear if changing $watch to have a priority is the right place to address the timing problem.
 2) I would like to hear how the community feels about having an "official" way of applying focus, blur, and select.

I feel it would be nice to have an "official" way of doing this common thing.

Broc

ganaraj p r

unread,
Feb 5, 2013, 8:12:24 AM2/5/13
to ang...@googlegroups.com
How about just having the ng-target or ng-set-focus... and this works like how ng-show and hide work? 

Can you assess what the problem with this approach is? I know you would have to have a bit more complex logic in the controller now.. But I guess we are fine with logic?


Broc

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to angular+u...@googlegroups.com.
To post to this group, send email to ang...@googlegroups.com.
Visit this group at http://groups.google.com/group/angular?hl=en-US.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Regards,
Ganaraj P R

Steve Mosley

unread,
Feb 5, 2013, 5:19:29 PM2/5/13
to ang...@googlegroups.com
+1 for focus support ... I don't understand the watch thing (pretty new to Angular), so have no opinion

Broc Seib

unread,
Feb 10, 2013, 10:34:06 PM2/10/13
to ang...@googlegroups.com
The proposal is to have ng-target work just like ng-show or ng-hide. You change a value in the $scope of your controller, and this causes the focus to happen.

I believe this directive should be built-in to angular rather than developers all roll their own. My basis for this argument is that focus, blur, and select are fundamental DOM operations that should behave the same for everyone and therefore we shouldn't all have to invent our own solution.

Jason Berk

unread,
Feb 12, 2013, 3:34:22 PM2/12/13
to ang...@googlegroups.com
+1

The fact that Angular doesn't support "set focus" out of the box almost caused me to pick a different framework.  This is one of those little details that users _really_ notice and expect to just be there.  With all the other work angular eliminated by separating the model and the view, it feels like it "forgot" about some fundamental DOM operations.  AngularUI filled in the onFocus() and onBlur()...but the fact that setFocus() isn't available (and that the community doesn't seem to mind) really shocks me.

Jason
ng-if is your friend

George Kappel

unread,
Feb 12, 2013, 10:53:43 PM2/12/13
to ang...@googlegroups.com
Both ngFocus and ngBlur are on the 1.1 roadmap (last updated in July but it seems the schedule has slipped) 

Joshua Miller

unread,
Feb 13, 2013, 12:22:33 AM2/13/13
to angular
Hello!

I'm not sure why "official support" for setting focus is so important as it is officially supported in the DOM with `element.focus()`. But if it is, writing a directive to do it when only include a few lines of code. What is the "timing issue"? Can you provide a plunker?

Josh

Broc Seib

unread,
Feb 13, 2013, 2:45:52 AM2/13/13
to ang...@googlegroups.com
Here is a demonstration of the timing problem. http://jsfiddle.net/bseib/ANfJZ/ If you make an element visible, then set focus, the DOM element was not actually visible yet when the directive fires the .focus() call on the element. Thus the hacky $timeout delay to work around it.

With respect to wanting to see "official support", I can totally see angular wanting to "stay out of your way" and let me just have everything I can do with the CSS/HTML/javascript. I applaud that and want that. But the same threshold of "ease" that I get ng-show, ng-hide, and ng-disabled et al., I simply expect ng-focus to be there too. Telling the angular newbie that they need to go write their own directive for setting focus seems a bit churlish.

Also, because this one has an underlying UI timing issue, I'd rather see official angular code doing it right rather than see a proliferation of varied solutions online that might or might not handle the problem correctly.

Broc Seib

unread,
Feb 13, 2013, 2:50:15 AM2/13/13
to ang...@googlegroups.com
Awesome. I didn't know it was on the roadmap.

There should be something to handle select() as well. I had combed through the W3C DOM Level 2 spec looking for other places where our controller is poking targets in the DOM like this, and focus(), blur() and select() were the only ones I found.

Broc

Joshua Miller

unread,
Feb 13, 2013, 3:18:11 AM2/13/13
to angular
Thanks for answering. I agree that it can be difficult for newbies and I certainly wouldn't *object* to the inclusion of an ngFocus (or similar) directive.

However, I do disagree with your approach. Your directive is really complicated (and I don't think it needs to be), but more importantly, you are calling DOM-related functions for your controller, which is bad practice in AngularJS. Your directive should work like ngShow and ngHide; that is, they should be expression-based and completely decoupled - no method-calling or DOM references necessary.

I'm not sure how to solve for the order of execution issues ideally, but here is a drastically simpler but functional ngFocus directive: http://jsfiddle.net/ANfJZ/38/.

What do you think?

Josh

Peter Bacon Darwin

unread,
Feb 13, 2013, 4:48:37 AM2/13/13
to ang...@googlegroups.com
+1 joshua

Broc Seib

unread,
Feb 13, 2013, 2:59:08 PM2/13/13
to ang...@googlegroups.com
Actually I'm not calling the DOM functions from the controller, and it is decoupled and expression based. I just went one step further and created a function called "focus()" that simply sets my bound scope variable to be "1". There is a $watch on that scope variable that fires the real element .focus() call, and resets the variable to 0 so it can be fired again. (complex?) This occurs inside a directive where it should be too. Unit testing remains tractable, which I feel is the best "smell" test.

I can agree that seeing a focus() function "smells" like a direct DOM manipulation, although that business is actually safely behind a directive. We can make the case that we don't see a show() or hide() function in angular. So I could agree that we shouldn't see a focus() function either. Besides, if I really want a show() function, I can do just do it myself like this:

  $scope.thing.isShown = false;
  $scope.thing.show = function() {
      $scope.thing.isShown = true;
  }

Now I can accomplish the state change by using an assignment, or by calling a function. It's purely semantic. But I don't believe there is any harm in having a function with a cozy familiar name. I do agree it should not directly refer to the DOM from the controller.

Broc

Broc Seib

unread,
Feb 13, 2013, 3:04:03 PM2/13/13
to ang...@googlegroups.com
There are still some nuances I'd like to discuss with respect to the state of the UI and the state of scope variables in the controller.

Take ngShow for example. From the controller, you flip the state one way or another, and the DOM reflects that state. Like, $scope.isShown = true/false; the element is either given  style={ display: none; } or not. But it is a one way street. If I were to change the class of my element to not be visible, I don't expect my $scope.isShown variable to be updated. And I think that's ok. Decisions to show and hide stuff emanate from the controller. It fits.

Suppose we use the same strategy for ngFocus, i.e. the DOM has a state that is representative of a scope variable. Ok, so we $watch our scope variable, and when it changes to "true", we change our DOM. But we (the controller) aren't the only ones who can change focus state, unlike the ngShow example. Tabbing and clicks cause the UI the change state. We now have to track blur events so that we can reset the scope variable to "false". Otherwise an assignment of "true" to a variable that is already "true" will never trigger the $watch. Ok, this turns out to work pretty good for ngFocus and ngBlur. We have a sort of two-way binding on our variable, and the state of that variable tracks the current focus state of the DOM element. Note that we required this two-way binding so that a variable can be changed again and actually trigger the $watch. I only reiterate this because of select().

What about select()? If I were to call select() directly on a DOM input element, it selects the entire text. This is a very specific reaction by the DOM. I might register an 'select' event listener to know when bits of text are selected. But how do I represent the state of a "select" in my scope variable? When should it be true, when is it false? Regardless of the element's state (whether the element doesn't have focus, does have focus, has a substring selected, has no string selected, whole string selected) a call to select() should have the specific outcome of selecting all the text. This means that a $watch for select needs to be able to fire *any* time I change its value. That implies that I should reset the value to false immediately after $watch detects its change to true.

In the case of select, the state of the scope variable can't really be made to represent the state of the UI. Select is a transition. Actually, so are focus and blur. But we can overload the word "focus" to mean both the transition (please set the focus) and the state (the element is presently focused). We cannot overload the meaning with select. Which seems to make a function call more suitable rather than a state assignment. Which then begs the question for all of them, for consistency.

We also have an initialization problem. If we are to treat our focus and blur scope variables such that they are representative of what is present in the UI, then to my knowledge, there is not a way to inquire the current focus state of an element. So we can't assign an initial value to the scope variable that reflects whether or not the element is focused.

This is how I arrived at thinking of focus(), blur() and select() as "triggers" rather than "states". I am not arguing for "triggers" rather than "states" or vice versa, but rather airing the issues. Blame it all on select().

Broc

On Wednesday, February 13, 2013 3:18:11 AM UTC-5, Joshua Miller wrote:

Broc Seib

unread,
Feb 13, 2013, 3:15:34 PM2/13/13
to ang...@googlegroups.com
Looking at your fiddle Joshua...  Yes I agree with this type of approach to make the UI track the "states" represented in your controller as opposed to "transitions". I might add to your fiddle that we need to handle variables being auto-vivified, e.g.

  <input type="text" x-ng-model="form.color1" x-ng-focus="some.thing.deep.isFocusDemo1">

This means $scope.some.thing.deep.isFocusDemo1 doesn't have to exist and *will* exist if someone sets focus on that element from the UI side. This is why I had that setter/getter function in my fiddle. (Perhaps this was the complexity you were referring to?) This is how ngModel works.

Here's another possible version of ngFocus, ngBlur, and ngSelect directives that "keep state" with UI. (well, except for select... and initial states aren't right...)


Broc

On Wednesday, February 13, 2013 3:18:11 AM UTC-5, Joshua Miller wrote:

Joshua Miller

unread,
Feb 13, 2013, 5:20:39 PM2/13/13
to angular
Whoa, there! That's quite a reply...and three of them, no less! I have other things to do today, too... :-)

You said: "Actually I'm not calling the DOM functions from the controller, and it is decoupled and expression based."

Not really. No, it is not a DOM function, but it is created through a DOM-centric process (a directive) and is intended to be a direct proxy of calling "focus()" on the element. That's a small distinction and we could debate it. Here's the big one: your directive has to know things about the controller and vice versa. That's what I object to. Your directive shouldn't care how your functionality is implemented in your model and your controller shouldn't know *anything* about how a directive works - or even that there is one! For your code to work, your controller needs to know that you're employing a specific directive. Without realizing it, you just integrated your view and controller together. That's bad juju. This is a big philosophical point, so let me know if I'm not explaining myself well - I'm quite tired. :-)

I have to admit that I am a skosh confused by your discussion of "select()". That's a jQuery shortcut for triggering and handling the "select" DOM event. We should keep the focus on what it does, and not on how jQuery does it, lest we beg the question with implementation details, which is what I think is happening.

I see "select" functionality as two super-simple directives: "ngSelect" and "ngSelected". The former is a directive that triggers a select when the expression evaluates to true and would work like my ngFocus directive. The latter holds the value of the currently-selected text so that the controller can react to it. These are not fully implemented, but should give you an idea of what I mean: http://plnkr.co/edit/HWMgPlVEpvPmNhVLZNI2?p=preview. Perhaps you can modify this to expose your issue.

I think we have to be careful to think about things in the context of separation of concerns. We should never have to couple our components - especially across layers.

Josh

Broc Seib

unread,
Feb 13, 2013, 9:15:39 PM2/13/13
to ang...@googlegroups.com
I can agree that a controller should just be assigning values and (sort-of) not care what directives might or might not be doing with those values. (Yeah we sort-of know assigning "true" to isShown is going to make a panel appear because we used an ng-show directive in our view, so we do make some assumptions about what a directive expects while we code...) I still contend that I am calling element.focus() from inside the directive, not in the controller, no different than your code example does. So I still don't see where I'm calling DOM functions from the controller, which we all agree is not a good idea.

As for select, I'm not concerned about jQuery at all and I presume it not being present. I speak only of the raw DOM select() call, e.g. if an input field contains "hello", and you call select on that element, then all five letters become highlighted and focus is set on that element. How to trigger this? How to represent the meaning of what is selected in our scope?

I agree that the three (focus, blur, select) should probably be separate directives.  I think my last fiddle is representative of this. I think splitting the select into two might be the only sane option to have both a representation of what's in the UI, and a means to trigger the select. It just doesn't work to squeeze into one like focus and blur can do.

Broc

Joshua Miller

unread,
Feb 14, 2013, 12:11:47 AM2/14/13
to angular
I think you're taking the "DOM in controller" part too literally. I already agreed that you are calling the DOM method from the directive. But you're only technically doing it from the directive; you didn't actually separate the concerns. You know, your controller knows, and your directive knows exactly what the consequence of that function is and where it came form. Your controller can only call it because it knows a specific directive acted on a specific element.  Arguing what semantically qualifies as a DOM function in that case is totally beside the point - your controller is still coupled with the DOM actions.

Here's an important note: in my contrived example, I used a variable called "isFocused" to indicate if a particular element is focused. But it's contrived! In the real world, that variable could be anything - even an expression or function! - and is probably related some *other* part of the wiring, and that wouldn't change *anything* about how either the controller *or* the directive operate. They don't know about each other and they don't care. They are truly decoupled. Again: in my example, the coder's intent is made clear, but that is a byproduct of it being contrived - it's not a requirement of the components. It's not the same as yours: mine optionally signals intent; yours demands integration.

But back to the select. :-) I'm sorry - I still don't understand what you're driving at here... Did you look at my Plunker? It has two working directives that solve for all cases while maintaining a clear separation of concerns. I'm not sure what the issue is. :-(

Josh

Broc Seib

unread,
Feb 14, 2013, 10:35:10 AM2/14/13
to ang...@googlegroups.com
On the "DOM in controller" topic...   Is line 16 an example of "bad form" in this fiddle from before http://jsfiddle.net/bseib/ANfJZ/ ? e.g.
   $scope.form.color1Target.focus();

The gist of the select topic is that it requires more than focus and blur. I felt your suggestion of ngSelect and ngSelected would address this. I'll take a look at the plunker.

My intent of this thread was to try to see if we can get official support for these in angular, if people cared about the topic, and also point out a timing issue. I think a revised proposal could be made having ngFocus, ngBlur, ngSelect, and ngSelected. I am also curious how much the angular team has looked into the details of this, as it could render this thread moot.

Broc

Broc Seib

unread,
Feb 14, 2013, 10:47:42 AM2/14/13
to ang...@googlegroups.com
Your plunker for ngSelect and ngSelected is a good solution for dealing with select.

Broc

Eric Miller

unread,
May 13, 2014, 2:09:55 PM5/13/14
to ang...@googlegroups.com
There's a lot of good discussion here, but I can't find a good recommendation.

How to I focus an input or textarea that angular brings in?

If I do it in the linking (postlink) function, the $digest cycle is already in progress.
Doing it in a timeout works but smells bad.

$safeApply has been dis-recommended for checking the $$phase. (https://coderwall.com/p/ngisma)

The problem is common enough, what's a good solution?
Reply all
Reply to author
Forward
0 new messages