1

In the following example a new field is added (by adding a blank row to $scope) when the last field loses focus if it is not empty. The problem is that the new field is not added to the DOM in time to receive focus.

Is there a way to detect when angular has finished appending new field to the DOM and then pass focus to it?

Please, no "timer" solutions; the time it takes to change DOM is unknown and I need this focus switch to happen as fast as possible. We can do better!

JSFiddle

HTML

<div ng-app='a' ng-controller='b'>
    <input type="text" ng-repeat="row in rows" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}">
</div>

JS

angular.module('a', []).controller('b', function ($scope) {
    $scope.rows = [{'word': ''}];

    $scope.$watch('rows', function (n, o) {
        var last = $scope.rows[$scope.rows.length - 1];

        last.word && $scope.rows.push({'word': ''});        
    }, true);
});
kornieff
  • 2,389
  • 19
  • 29

2 Answers2

2

Use $timeout without specifying a number of milliseconds. It will, by default, run after the DOM loads, as mentioned in the answer to this question.

angular.module('a', []).controller('b', function($scope, $timeout) {
  $scope.rows = [{
    'word': ''
  }];

  $scope.addRow = function() {
    $scope.rows.push({
      'word': ''
    });
    $timeout(function() {
      //DOM has finished rendering
      var inputs = document.querySelectorAll('input[type="text"]');
      inputs[inputs.length - 1].focus();
    });
  };
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app='a' ng-controller='b'>
  <div ng-repeat="row in rows">
    <input type="text" ng-model="row.word" ng-model-options="{'updateOn': 'blur'}"><br>
  </div>
  <input type="button" ng-click="addRow()" value="Add Row">
</div>
Community
  • 1
  • 1
Andrew Mairose
  • 10,615
  • 12
  • 60
  • 102
  • Did you try it? Did not work when I tried in jsfiddle. (Tested in FF and Cr) – kornieff Aug 18 '15 at 17:25
  • 1
    `autofocus` is for when the page loads, not just whenever. – dandavis Aug 18 '15 at 17:26
  • There was never anything getting added in the fiddle, so I just thought it needed to happen on page load. Updated with more appropriate answer. – Andrew Mairose Aug 18 '15 at 17:37
  • @AndrewMairose, thanks for explaining $timeout workaround. Definitely +1. Is there a less hacky way of doing it? Something like $renderComplete. – kornieff Aug 18 '15 at 17:48
  • What if in the View, the "row" is really inside a template - `
    `? This approach fails immediately. Not to mention that a controller should be DOM-agnostic
    – New Dev Aug 18 '15 at 17:55
2

This is a View-concern and so should be dealt with by using directives.

One way to do so, is to create a directive that grabs the focus when it's linked:

.directive("focus", function(){
  return {

    link: function(scope, element){
      element[0].focus();
    }

  }
});

and use it like so:

<input type="text" 
       ng-repeat="row in rows" 
       ng-model="row.word"
       focus>

Demo

kornieff
  • 2,389
  • 19
  • 29
New Dev
  • 48,427
  • 12
  • 87
  • 129
  • I like the directive idea. Somehow I can't get it to work with scope watch. I guess I will need to try ng-blur directive instead of scope watch. The downside to that is running a query selector on all the fields every time new one is needed (it needs to handle huge number of input fields). – kornieff Aug 18 '15 at 21:00
  • @kornieff, where / why do you need `$scope.$watch`? – New Dev Aug 18 '15 at 21:14
  • I need to create new input when the last one loses focus. I watch scope for the last row to fill and then I add one more. I guess I can try to use ng-blur instead. – kornieff Aug 18 '15 at 21:18
  • @kornieff, I'm not sure I understand. This directive doesn't (shouldn't) care *how* it is decided that a new row is created. Do it however you want (I did it with a button) - but the whole point is that there is separation of concerns – New Dev Aug 18 '15 at 21:26
  • Tried ng-blur. It works when I trigger it with clicking, but not when I press tab key. http://jsfiddle.net/kornieff/Lw9d4se9/2/ – kornieff Aug 18 '15 at 21:37
  • 1
    @kornieff, yeah, why would it.... but this is a completely separate question - that is, "how can I add invoke a function in the controller in response to a tab press on the input". So, it's best to ask a new question – New Dev Aug 18 '15 at 21:51
  • Good point. It is "why focus via click is not the same as focus via tab?" – kornieff Aug 18 '15 at 21:56
  • 1
    @kornieff, because it's not "focus via click" - it's "focus via calling a controller function that generates a row in the model, which because it is `ng-repeat`-ed, creates another input that then is focused". It may sound like a subtle difference, but it's an important one to internalize - that the controller modifies the model, and the directives deal with the View - a separation of concerns that the answer you accepted completely neglects – New Dev Aug 18 '15 at 22:01