0
votes

Directive in AngularJS: I find out that the elements inside an element with the directive do not inherit its "scope".

For example:

app
.controller('xxx', function($scope) {})
.directive('yyy', function() {
  return {
    scope: {},
    link: function(scope,elem,attrs) {}
  };
});

When we use it in the HTML:

<body ng-controller="xxx">
  <div id='withD' yyy>
    <div id='inside'>Inside the element with a directive</div>
  </div>
</body>

"body" will have a scope whose $id may be 003; then "#withD" will have an isolate scope $id=004; the "#inside" will have the scope $id=003, which means the "#inside" inherits "body"'s scope.

If I use "transinclude" for the directive "yyy"; then "body" scope.$id=003, "#withD" scope.$id=004, "#inside" scope.$id=005; moreover, 003 has two children 004 and 005. However, I wanna make the element with the directive has an isolate scope and its child elements inherit the scope.

I read over "ui.bootstrap.tabs" source code but I do not like the style, for it is strange and also not make the parent element share its scope with child elements'; it looks like this:

app
.directive('xitem', function() {
  scope: {},
  controller: function($scope) {
    $scope.subitem = [];
    return {
      add: function(xsubitem) {$scope.subitem.push(xsubitem);}
    }
  },
  link: function(scope,elem,attrs) {}
})
.directive('xsubitem', function() {
  require: '^xitem',
  link: function(scope,elem,attrs,ctrl) {ctrl.add(elem);}
});

My expectation is that:

<div ng-controller="xxx">
  <div yyy>
    <button ng-click="sayHi()">Hi</button>
  <div>
</div>

when you click the "Hi" button, the alert dialog will pop up with the message "Hello World" not "Error: Scope".

app
.controller('xxx', function($scope) {
  $scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
  return {
    scope: {},
    link: function(scope,elem,attrs) {
      scope.sayHi = function(){alert('Hello World');};
    }
  };
});

Moreover, I tried this:

app
.controller('xxx', function($scope) {
  $scope.sayHi = function(){alert('Error: Scope');};
})
.directive('yyy', function() {
  return {
    scope: {},
    controller: function($scope, $compile) {$scope._compile = $compile;}
    link: function(scope,elem,attrs) {
      elem.children().forEach(function(one) {
        scope._compile(one)(scope);
      });
      scope.sayHi = function(){alert('Hello World');};
    }
  };
});

Then it will pop up two alert dialogs with the message "Error: Scope" and "Hello World" respectively.

2

2 Answers

0
votes

Now I found the solution - load template dynamically and use $compile to specify scope:

.controller('main', function($scope) {
  $scope.sayHi = function() {alert('scope error');};}
)
.directive('scopeInherit', ['$http', '$compile', function($http, $compile) {
  return {
    scope: {},
    link: function(scope, elem, attrs) {
      scope.sayHi = function() {alert('hello world');};
      scope.contents = angular.element('<div>');
      $http.get(elem.attr('contentsURL'))
           .success(function (contents) {
             scope.contents.html(contents);
             $compile(scope.contents)(scope);
           });
    },
  };
}]);

Then we write HTML:

<div ng-controller="main">
  <div scope-inherit contents="test.html"></div>
</div>

where there is a test.html:

<button ng-click="sayHi()">speak</button>

Then click on the "speak" button, it will pop up the alert dialog with "hello world"

0
votes

To do what you want, you need to use a template (either as a string or a templateUrl). If angularjs would work how you expect it in this case then a lot of the angular directives wouldn't work right (such as ng-show, ng-click, etc).

So to work how you want it, change your html to this:

<script type="text/ng-template" id="zzz.html">
  <button ng-click="sayHi()">Hi 2</button>
</script>

<div ng-controller="xxx">

  <button ng-click="sayHi()">Hi 1</button>

  <div yyy></div>
</div>

And update your directive definition to use a templateUrl (or you can provide the string as a template property)

app
  .controller('xxx', function($scope) {
    $scope.sayHi = function() {
      console.error('Error: Scope in xxx', new Date());
    };
  })
  .directive('yyy', function() {
    return {
      scope: {},
      templateUrl: 'zzz.html',

      link: function(scope, elem, attrs) {
        scope.sayHi = function() {
          console.log('Hello World in zzz', new Date());
        };
      }
    };
  });

Here's a plunker with this code: http://plnkr.co/edit/nDathkanbULyHHzuI2Rf?p=preview

Update to use multiple templates

Your latest comment was a question about what if you wanted to use different templates on the same page. In that case we can use ng-include.

html:

  <div yyy contents="template1.html"></div>
  <div yyy contents="template2.html"></div>
  <div yyy contents="template3.html"></div>

js:

app
  .controller('xxx', ...)
  .directive('yyy', function() {
    return {
      scope: {
        theTemplateUrl: '@contents'
      },
      template: '<ng-include src="theTemplateUrl"></ng-include>',

      link: function(scope, elem, attrs) {
        scope.sayHi = function() {
          console.log('Hello World in yyy', new Date());
        };
      }
    };
  });

The benefit of using ng-include is that this is already built into angularjs and is well tested. Plus it supports loading template either inline in a script tag or from an actual url or even pre-loaded into the angular module cache.

And again, here is a plunker with a working sample: http://plnkr.co/edit/uaC4Vcs3IgirChSOrfSL?p=preview