5

I am new to Angular and trying to understand advanced directive API - I want to re-create directive template in compile function using directive element attributes. But when I don't have a template set (or template is empty string) instead accesing isolated directive scope I access parent (controller) scope. Also - this works on Angular 1.1 but not 1.2

here is HTML:

<div class="container" ng-app="app" ng-controller="AppController">
      <sandbox title="Attribute Title"></sandbox>
</div>

JavaScript:

var app = angular.module('app', [], function () {});

app.controller('AppController', function ($scope) {
    $scope.title = "AppController title";
});

app.directive('sandbox', function ($log, $compile) {
    return {
        restrict: 'E',
        scope: {
            title: "@"
        },
        controller: function ($scope, $element, $attrs) {
            $scope.title = "Directive Controller title";
        },
        template: '<h1>Template</h1>', // change it to: '' and Run, than change Angular to 1.2.x
        compile: function (tElement, tAttrs) {
            tElement.append('<h2> Title = {{ title }}</h2>');
        }
    }

});

When you Run it you get:

Template

Title = Attribute Title

But when you change template to empty string you get with Angular 1.2:

Title = AppController Title

And with Angular 1.1.1:

Title = Attribute Title

My Questions:

Why there is a difference in accesing scope when template is set AND when it's not set?

Why there is a difference between Angular 1.1 and 1.2 (bug? - directive without 'template'and with isoleted scope acceses controller scope not directive scope ) ?

How can I build template that accesses isolated scope NOT parent scope in Angular 1.2 in compile function ?

Why directive controller function does not change 'title' using $scope.title = "..." - but when debugging 'scope' param in 'link' function 'title' value is 'Directive Controler Title' but it internally (WHERE to look for it) binds isoleted scope 'Attribute Value' ?

Here is JSFiddle to play with: http://jsfiddle.net/yoorek/zQ66L/4/

Yoorek
  • 1,003
  • 1
  • 12
  • 30

1 Answers1

7

You've hit on a big change that happened in 1.2 (and a quirk in using "@").

1) When your template is `` Angular sees no template to apply the isolate scope to. The reason this is an issue in 1.2 is in the answer to your second question.

2) This is a result of this 1.2 breaking change- make isolate scope truly isolate:

Isolate scope is now available only to the isolate directive that requested it and its template.

So, without a template you're appending to an element that is outside the isolate scope.
From https://github.com/angular/angular.js/issues/4889:

As we can't distinguish markup that was originally in the html file and markup that was added in the compile function the latter also doesn't get the isolate scope.

and

...additional markup in the compile function should be replaced by using the template property.
The idea is that the template property can also be a function. If it is one, it will get the compile element and the compile attributes as parameters.

3) As you see above, Angular (post 1.2) is really trying to push you to use the template instead of the compile function here. Your best bet is likely to use a template function in the manner you were using compile. Alternatively you could use a link function with a $compile- but that may be adding unneeded complexity.

By appending inside the compile function you're effectively just adding to the template so it's kind of getting around this.

4) This has to do with the way @ works. From Angulars guide to scopes:

use attrs.$observe('attr_name', function(value) { ... } in the linking function to get the interpolated value of isolate scope properties that use the '@' notation. E.g., if we have this in the linking function -- attrs.$observe('interpolated', function(value) { ... } -- value would be set to 11. (scope.interpolatedProp is undefined in the linking function. In contrast, scope.twowayBindingProp is defined in the linking function, since it uses the '=' notation.)

And you might also read this SO post on the difference between @ and =

Community
  • 1
  • 1
KayakDave
  • 24,636
  • 3
  • 65
  • 68
  • Great answer, but #4 has not been true for a while. I think ever since 1.1.x "@" will be defined in the scope in the link function. See this sample: http://plnkr.co/edit/IVfc7P1a9Kj4RQTrp7Dx?p=preview – Daniel Tabuenca Jan 05 '14 at 19:20
  • @dtabuenc Check out this updated plnkr: http://plnkr.co/edit/fH452EsTPjEJNJV0L6Xa So I believe it's still risky to use the scope version (depending on inheritance)- but I'd be happy to be wrong. – KayakDave Jan 05 '14 at 20:06
  • The $scope use in the link function is fine, it is the usage in the controller that is incorrect. "@" bindings are one-way, and as such should never be written to. Any value written to it will be over-written at the next digest cycle. – Daniel Tabuenca Jan 05 '14 at 21:15
  • @dtabuenc Note that Angular uses writing to an "@" bound variable from a controller [in their transclude example](https://github.com/angular/angular.js/blob/3410f65e790a81d457b4f4601a1e760a6f8ede5e/src/ng/directive/ngTransclude.js). So I'm not sure I'd call it incorrect- but risky for sure, and thus their recommendation of `$observe` – KayakDave Jan 05 '14 at 22:12
  • @KayakDave In the ng-transclude they are writing to $scope.title in a controller OUTSIDE of the directive which is perfectly legitimate since it is writing to the non-isolate scope. That is natural and works fine. The problem is writing to $scope.title INSIDE a directive with isolate scope. "@" bound variables should never be written to either from a directive's controller or link functions. – Daniel Tabuenca Jan 05 '14 at 22:19