8

I have a directive with isolate scope which takes a scope variable by reference

angular.module('myApp')
    .directive('myDirective', function() {
        return {
            scope: {
                items: '='
            },
            templateUrl: 'template.html',
            replace: true,
            controller: 'myDirectiveCtrl',
            controllerAs: 'ctrl'
        };
    })
    .controller('myDirectiveCtrl', function($scope) {
        this.items = $scope.items;
    });

This is passed in like so:

    <div my-directive items='items'></div>

In the external controller data is asynchronously loaded and the scope items passed into the directive updated:

angular.module('myApp', [])
  .controller('myCtrl', function($scope) {

    $scope.setItems = function() {
      $scope.items = [
        'Here',
        'There',
        'Everywhere'
      ];
    };
  });

When the data is loaded, the scope outside my directive updates, but inside it doesn't

My html:

      <div my-directive items='items'></div> <!-- this doesn't update --> 

      Outside directive
      <ul ng-repeat='i in items'>            <!-- this does update -->
        <li>{{i}}</lu>
      </ul>

      <button ng-click="setItems()">Set items</button>

How can I get my scope inside my directive to update? Do I

Plunker here

3 Answers 3

5

When Angular first runs your directive's controller function, your $scope.items === undefined, so when you do this.items = $scope.items, your this.items === undefined too.

That's it. After that there is nothing that changes this.items.

This is unlike $scope.items. $scope.items is two-way bound to the outer scope, so whenever Angular detects a change externally, it sets the isolated scope variable.

The easiest way (and most suitable, in my opinion) is to use the $scope property directly in the directive:

<div>
    Inside directive
    <ul ng-repeat="i in items">
      <li>{{ i }}</li>
    </ul>
</div>

If you want to use your controller as ViewModel instead of scope (I don't know why you would), you could do:

$scope.$watchCollection("items", function(newVal, oldVal) {
   ctrl.items = newVal;
});

EDIT:

In Angular 1.3 you can also do bindToController: true in the directive's definition, so that the controller property "items" will get the two-way binding that $scope.items gets. Then, you don't even need to do this.items = $scope.items;:

Your forked plunker to illustrate.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for this. The use of the controller was influenced by this blog post: teropa.info/blog/2014/10/24/…
I didn't say that you shouldn't use a controller. I meant that the scope-exposed variable need not be assigned to the controller's variable.
@SteveLorimer, I edited the answer with another approach with bindToController to make your example work
Wasn't aware of bindToController - that is awesome - thanks!
I guess this would work if we are not changing anything in the data given to directive. However, what if I am updating it in the directive controller? How would I then catch the update event? Here's a plunker of what I am talking about.
3

If it is isolated scope you cannot change what is inside the directive after you create a separate variable inside the directive controller.

Here is the updated plunker which removes the controller for the directive.

'use strict';

angular.module('myApp')
    .directive('myDirective', function() {
        return {
            scope: {
                items: '='
            },
            templateUrl: 'template.html',
            replace: true
        };
    });

Comments

1

Try putting your items in an object. See this example at Plunker

index.html

<div my-directive items='allItems'></div>

  Outside directive
  <ul ng-repeat='i in allItems.items'>
    <li>{{i}}</lu>
  </ul>

  <button ng-click="setItems()">Set items</button>
</div>

directive.js:

'use strict';

angular.module('myApp')
.directive('myDirective', function() {
  return {
    scope: {
      items: '='
    },
    templateUrl: 'template.html',
    replace: true,
    controller: 'myDirectiveCtrl',
    controllerAs: 'ctrl'
  };
})
.controller('myDirectiveCtrl', function($scope) {
    this.items = $scope.items;
});

template.html:

<div>
  Inside directive
  <ul ng-repeat="i in ctrl.items.items">
    <li>{{ i }}</li>
  </ul
</div>

script.js:

angular.module('myApp', [])
.controller('myCtrl', function($scope) {
  $scope.allItems={};
  $scope.setItems = function() {
    $scope.allItems.items = [
      'Here',
      'There',
      'Everywhere'
    ];
  };
});

There is a better explanation here:

Angular - ngModel not updating when called inside ngInclude

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.