1

I am trying to set focus on an input field via a custom directive on a form. This works fine when using the template property in the directive. But when I move the template into a separate html file via templateUrl, element.find() does not find my input fields any more:

The code goes as follows:

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <link rel="stylesheet" href="style.css" />
    <script data-require="angular.js@1.2.x" src="https://code.angularjs.org/1.2.16/angular.js" data-semver="1.2.16"></script>
    <script src="app.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <form get-input-by-id="input2">
      <my-input id="input1"></my-input>
      <my-input id="input2"></my-input>
    </form>
  </body>

</html>

The js:

var app = angular.module('plunker', []);

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
});

app.directive('getInputById', function() {
  return {
    link: function (scope, element, attrs) {
      //console.log(element);
      var toFocus = element.find('input');
      console.log(toFocus);
      toFocus[1].focus();
    }
  }
});

app.directive('myInput', function() {
  return {
    restrict: 'E',
    scope: {
      id: "@id",
    },
    // this is not working
    templateUrl: 'template.html',
    // this is working
    //template: '<div><input id="{{id}}"/></div>',
    link: function (scope, element, attrs) {
    }
  }
});

And the template:

<div>
  <input id="{{id}}"/>
</div>

I added both working and not working plunkers:

This plunker is working.

This plunker is not working.

vusan
  • 5,221
  • 4
  • 46
  • 81
zszep
  • 4,450
  • 4
  • 38
  • 58

1 Answers1

1

The thing is that the child directives are not rendered until their template is downloaded, so parent's link function does not find any input elements (read the other question in comments).

One option that works both ways is having the child directive (whenever is rendered) ask the parent directive, if it should focus or not.

app.directive('getInputById', function() {
  return {
    scope: {
      getInputById: '@'
    },
    controller: function($scope) {
      this.isFocused = function(id) {
        return $scope.getInputById === id;
      }
    }
  }
});

app.directive('myInput', function() {
  return {
    restrict: 'E',
    require: '?^getInputById',
    scope: {
      id: "@id",
    },
    templateUrl: 'template.html',
    link: function (scope, element, attrs, ctrl) {
      if (ctrl && ctrl.isFocused(scope.id)) {
        var input = element.find('input')
        input[0].focus();
      }
    }
  }
});

That way, the parent directive can be more generic and not entirely restricted to input elements. Each different "focusable" control asks the parent and implements its own focus.

Working plunker

Kos Prov
  • 4,207
  • 1
  • 18
  • 14
  • I implemented it as you suggested and it's working great. Still I'm wondering why the angular team implemented template differently from templateUrl. – zszep May 15 '14 at 12:06
  • 1
    I played around with this focus thing and ended up to [this](http://plnkr.co/edit/AEtWaQpXIxZO7afEfUtB?p=preview). I think it's a good example of collaborating directives. Of course, there are many other ways to do the same thing. – Kos Prov May 15 '14 at 12:47