简体   繁体   中英

AngularJS : Transcluded directive and access to model

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?

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 />

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM