Question

In AngularJS, I have directive with basic transclusion. I know that usually is transcluded input or textarea when i using it, and if is there, I want to $watch its model for changes. But I don't have access to attribs of transcluded content, only access to attribs of root element which directive is called on. Transcluded scope as well (maybe scope.$$nextSibling can help but something tells me that it's way to hell :) ).

So is there any way to do that without adding another parameter (attribute) to element where is directive called?

directive template

<div ng-transclude>
    <someContent>...</someContent>
    <!-- HERE IS INPUT TRANSCLUDED -->
</div>

directive usage

<div my-directive="somedata">                           //this attribs are accessable
    <input ng-model="iWantToWatchThisInMyDirective" />  //but i want to access this also
</div>
Was it helpful?

Solution

Here is my solution:

I created second directive: Input (restricted for Element, so every input has one). In Input directive I broadcast every change to element's scope:

link: function (scope, element: JQuery, attrs: ng.IAttributes) {
  if(typeof attrs.ngModel !== "undefined") {
    scope.$watch(attrs.ngModel, function (newValue, oldValue) {
      scope.$broadcast('valueChanged'+scope.$id, newValue, oldValue);
    });
  }
}

scope.$id is used just for be sure that event name is unique for every input.

Now, in any other directive I can listen event of changing any input:

link: function (scope, element:JQuery, attrs:ng.IAttributes) {
  //My input is last child everytime..
  var children = element.find("input");
  var lastInput = angular.element(children[children.length - 1]); 
  var lastInputScope = lastInput.scope();

  var unregister = lastInputScope.$on('valueChanged' + lastInputScope.$id, 
                                      function (event, newValue, oldValue) {
    //do whatever you want...
  });

  scope.$on('$destroy', function () {
    unregister();
  });
}

OTHER TIPS

Here's another way you can do this using $scope.$watch. This code is written without jQuery.

angular.module('sample').directive('sampleDirective', function () {
    'use strict';

    // Returns a JQLite object for the first input element we transcluded, 
    // allowing us to pick information from it
    function findInput(clone) {
        for (var i = 0; i < clone.length; i++) {
            if (clone[i].nodeName.toLowerCase() == 'input') {
                return angular.element(clone[i]);
            }
        }
    }

    return {
        transclude: true,
        link: function (scope, element, attrs, ctrl, transclude) {

            transclude(function (clone) {
                var input = findInput(clone);
                if (input) {

                    // Watch for changes on our select element's model
                    scope.$watch(input.attr('ng-model'), function (val) {

                        // Do things
                        console.log('My updated model:', val);
                    });
                }
            });
        }
    };
});

This assumes your model is available on the same scope the directive is on. If your directive has an isolate scope, you could alter the watch statement to watch for the ng-model string on the $parent scope:

scope.$watch('$parent.' + input.attr('ng-model')

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top