Search code examples
angularjsangularjs-directiveangular-ngmodelangularjs-ng-transclude

ngModel needs $parent when within Transcluded html


I have a directive for a input field that uses transclusion to take the elements that are enclosed in the directives element which includes an ng-model attribute. After reading countless SO questions and Angular documentation to find out how to get the ng-model in the transcluded html to sync with the ng-model in my directive I finally stumbled stumbled upon a trick to get it to work. That is to use $parent where the ng-model is within the input field. This is all fine and dandy, however, it seems clunky/hackish.

Plunker shown here: http://plnkr.co/edit/gEje6Z2uuTs9DFPeCZfv

I tried to make this a little more elegant by messing around with the transclusion function within my link function like so:

```

      var transcludedContent, transclusionScope;

      transcludeFn(scope, function(clone, scope) {
        //headerCtrl.$element.append(clone);
        transcludedContent = clone;
        transclusionScope = scope;

        console.log('scope form: ', scope);
        console.log('transclude form: ', clone);


      });

```

Also, shown in this Plunker: http://plnkr.co/edit/11k9LiA5hyi4xydWBo3H?p=preview

One would think that the transclusion function would allow you to overwrite the transclusion scope with the scope of your directive then the ng-model attributes would be associated and bound to the directives scope, however, this is not the case.

Although, the $parent.<ng-model> does work, it seems very hackish, and can lead to bugs like if my directive was not used with a parent scope that doesnt have an account object defined.


Solution

  • There are a couple ways of doing that.

    1) Exposing the account variable using =

    http://plnkr.co/edit/DxsipWRj0AJe6Yi3bhse

    JS:

    app.directive('formControl', [function(){
        return {
          restrict: 'EA',
          template: '<div ng-transclude></div>{{account.name}}',
          scope: {
            account: '='
          },
          transclude: true,
          link: function(scope, element, attrs){
            scope.account={};
    
            console.log('SCOPE: ', scope)
          }
        };
    }]);
    

    HTML:

    <form-control account='account'>
      <label for="name">Enter Name:</label>
      <input name="name" ng-model="account.name" \>
    </form-control>
    

    2) Using transclude function:

    This is similar to what ngIf and ngRepeat do. ngRepeat actually decorates every scope with $index and similar values, the same way you want to decorate your scope with account.

    http://plnkr.co/edit/cZjWqIgO23nzc0kMZA57

    JS:

    app.directive('formControl', ['$animate', function($animate){
        return {
          restrict: 'EA',
          transclude: 'element',
          link: function(scope, element, attrs, ctrl, transclude){
            //this creates a new scope that inherits from the parent scope
            //that new scope will be what you'll be working with inside your
            //transcluded html
            transclude(function (clone, scope) {
              scope.account = {name:'foobar'};
              $animate.enter(clone, null, element);
    
              console.log('SCOPE: ', scope)
            });
          }
        };
    }]);
    

    HTML:

    <form-control>
      <label for="name">Enter Name:</label>
      <input name="name" ng-model="account.name" \><br>
      {{account.name}}
    </form-control>