0

I'm creating a smart input directive which will wrap a text input element and it requires access to the model on the input element in order to manipulate some classes on the directive.

Because the input element can be one of many types (text, email, password), I need to transclude the directive and be able to add different types of validation for each.

The problem I'm having (as with many others on the Internet), is scope inheritance.

Here's what my current code looks like
HTML

<smart-input ng-model="username">
  <span ng-show="isTyping">{{ placeholder }}</span>
  <input type="text" name="username" ng-model="username" ng-minlength="4" ng-maxlength="20" required />
</smart-input>

JS

angular.module('myApp').directive('smartInput', function ($compile) {
  return {
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: {
      model: '=ngModel'
    },
    template: '<div class="text-input" ng-class="{typing: isTyping}" ng-transclude>' +
              '</div>',
    link: function(scope, element, attrs) {
      scope.isTyping = false;

      scope.$watch('model', function(value) {
        console.log(value);
        scope.isTyping = value.length > 0;
      });
    }
  };
});

Basically, the value inside the $watch function is undefined so obviously I'm not doing this correctly.

So, how can I bind a model to the input field, while have the directive have a reference to the same object and be able to watch it's value?

John Slegers
  • 45,213
  • 22
  • 199
  • 169
Marko
  • 71,361
  • 28
  • 124
  • 158
  • Can you create a jsfiddle or plunkr with your code? – Michał Miszczyszyn Feb 03 '14 at 08:34
  • it works http://plnkr.co/edit/pYfiafbQR6JOZhDEQGQS?p=preview – Khanh TO Feb 03 '14 at 10:19
  • Interpreting the question's text, I guess that you need something like this: `ng-model="$$prevSibling.model"` http://plnkr.co/edit/DjQibwb5cmIVIFJzcEie?p=preview but without `$$prevSibling`, is that correct? – Khanh TO Feb 03 '14 at 10:22
  • @KhanhTO that's exactly right. I don't want to have to put $$prevSibling.model, instead make it automatic. This is because other developers will be using this directive and I'd like to make it as simple as possible to use. – Marko Feb 03 '14 at 19:57

1 Answers1

0

When you use isolate scope with transclusion, your scopes do not have parent/child relationship. It looks like this:

<controllerScope>
     <smartInputScope>
     <transcludedContentScope>

That's why in order to access smartInputScope's model property, we have to access $$prevSibling.model In your first example ng-model="username" works because this scope inherits from controllerScope, it's accessing a parent's scope property.

Check out my solution with custom transclusion: http://plnkr.co/edit/cV9urKJdcn4mKlpqPJTr?p=preview

app.directive('smartInput', function($compile) {
  return {
    restrict: 'E',
    transclude: true,
    replace: true,
    scope: {
      model: '=ngModel'
    },
    template: '<div class="text-input" ng-class="{typing: isTyping}">' +
      '</div>',
    compile: function(element, attr, linker) {
      return {
        pre: function(scope, element, attr) {
          linker(scope, function(clone) { //bind the scope your self
            element.append(clone); // add to DOM
          });
        },
        post: function postLink(scope, iElement, iAttrs) {
          scope.isTyping = false;

          scope.$watch('model', function(value) {
            console.log(value);
            scope.isTyping = value.length > 0;
          });
        }
      };
    }
  };
});

In the html, I don't need $$prevSibling anymore:

<input type="text" name="username" ng-model="model" ng-minlength="4" ng-maxlength="20" required />
Khanh TO
  • 48,509
  • 13
  • 99
  • 115