5

Is there a trick to get the element associated with the scope outside the directive that owns it?

I act on the premise that it has to be done in least favorable conditions (from the console or Greasemonkey script). E.g. to get the element that has the scope

angular.element(document.querySelector('.ng-scope')).scope().$$childTail

without DOM traversing.

I guess it is possible to traverse all ng-scope and ng-isolate-scope DOM elements and map their scopes, yet I'm looking for more elegant solution (the map also needs to be kept up to date, and I'm trying to stay away from DOMSubtreeModified, also this won't work with debugInfoEnabled disabled).

Manube
  • 5,110
  • 3
  • 35
  • 59
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    You don't need scope at all if you know the id of the element – Dave Alperovich May 29 '15 at 00:02
  • let me rephrase the question, are you using Batarang? – Dave Alperovich May 29 '15 at 15:20
  • Sure, but "least favorable conditions" actually mean its absence. I wonder if Batarang traverses DOM and watches for its updates to map it to the scopes. – Estus Flask May 29 '15 at 18:02
  • What confuses me is circumstances when you would have scope Id. The use case helps to form an answer. – Dave Alperovich May 29 '15 at 18:04
  • Greasemonkey scripts (I start to think that hijacking Angular with custom build that provides additional metadata for scopes is the way). And debugging in arbitrary debugger (logging element's scope and investigating its children). – Estus Flask May 29 '15 at 18:13
  • Interesting... very far off the beaten path. And outside anything I can discuss with authority. But I can whip a function to return an element for a scope Id. Is that what you're looking for? – Dave Alperovich May 29 '15 at 18:16
  • If you need to pass data to and from several isolated scopes, try to consider $broadcast and $on. – Joao Polo Jun 02 '15 at 01:10
  • I suppose you can´t edit the directive ;) – Raúl Martín Jun 02 '15 at 18:46
  • 1
    That's right, I presume I can't edit them but have to hijack them at runtime. @Dave Alperovich, thanks, I've already written the function on my own (the answer below nailed it too) but wasn't happy with results because of DOM changes. – Estus Flask Jun 03 '15 at 13:47

3 Answers3

7

Scopes (src) don't keep a reference to the element they're associated with. After all, scopes can exist without being associated with a specific element.

$compile (src) is the one responsible for associating elements with scopes.

Part of the compilation process augments the element, letting you go from element >> scope (e.g. angular.element("#something").scope()). The same doesn't seem to happen with scopes.

So to go the other way, scope >> element, you have to map scope ids: Get DOM element by scope $id. That feature in Angular JS Batarang that lets you pick an element from the page and inspect the scope associated with it? This is how it's done. Batarang uses angular-hint. angular-hint iterates through all elements on the page with an ng-scope class and returns the one with a matching scope id (src: function findElt).

function findElt (scopeId) {
  var elts = document.querySelectorAll('.ng-scope');
  var elt, scope;

  for (var i = 0; i < elts.length; i++) {
    elt = angular.element(elts[i]);
    scope = elt.scope();
    if (scope.$id === scopeId) {
      return elt;
    }
  }
}
Community
  • 1
  • 1
user2943490
  • 6,900
  • 2
  • 22
  • 38
  • I've previously had performance problems with DOMSubtreeModified with similar approach, hope that mutation observers, caching and throttling can help. Thank you for improving the answer with angular-hint reference, I've overlooked it. – Estus Flask Jun 03 '15 at 14:11
1

There are a few things you can do to get the element of a directive.

Element on event

If you need to pass the element on an event, you can create a callback which can pass the element back. If you do not need a reference to it all the time, this is the preferred method.

In you return object in the directive, Add something like

    scope:{
      elementclicked: "&"
    }

In the template of your directive, you can add

    <....... ng-click="ElementClicked(event)"........>

In your Directive Controller, you can now handle the click and Pass the results

$scope.ElementClicked = function ($event) {
    if ($scope.elementclicked != undefined) {
        elementclicked({ event: $event });
    }
}

Now you pass your callback like any other to the directive.

    <yourDirective   elementclicked="MyFunction(event)" ....>

Element when linked

If you need a reference at the time of creation, you can do that as well. If you Pass in a data structure such as settings you could set it in the linking event. When you do your linking in the directive, just set the element.

scope:{
        settings:"="
},
link:function(scope,element){
    scope.$watch('settings',function(){
        if(scope.settings!=undefined){
            scope.settings.element=element;
        }
}
}

This will watch for when the settings are bound and set the element property. The big disadvantage here is that you are appending a property to a passed object but if it is for a directive inside a directive or it is just your project, it should be fine.

Another way to do it would be to use the first method and create an elementlinked(element) callback and fire it after you link the scope.

Jim Fallin
  • 236
  • 1
  • 6
0

What I have understood, you want to access scope's value outside the directive. I have made a small smaple, check it:

HTML Part:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>

  <div id="outer" ng-app="plunker" ng-controller="MainCtrl">
    You are {{msg}}
</div>
<div onclick="change()">click me</div> 

Scipt part:

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

    app.controller('MainCtrl', function($scope, $rootScope) {
     $scope.msg = "great";
        $rootScope.safeApply = function( fn ) {
            var phase = this.$root.$$phase;
            if(phase == '$apply' || phase == '$digest') {
                if(fn) {
                    fn();
                }
            } else {
                this.$apply(fn);
            }
        };
    });

//Custom java script

function change() {
    var scope = angular.element($("#outer")).scope();
    scope.safeApply(function(){
        scope.msg = 'Superhero';
    })
}

For Above code working Plunker Link is here: Plunker

Nitin Singh
  • 160
  • 1
  • 8