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

Steve Lorimer
  • 27,059
  • 17
  • 118
  • 213

3 Answers3

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.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • Thanks for this. The use of the controller was influenced by this blog post: http://teropa.info/blog/2014/10/24/how-ive-improved-my-angular-apps-by-banning-ng-controller.html – Steve Lorimer Nov 16 '14 at 02:17
  • 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. – New Dev Nov 16 '14 at 09:57
  • 1
    @SteveLorimer, I edited the answer with another approach with `bindToController` to make your example work – New Dev Nov 16 '14 at 18:09
  • 1
    Wasn't aware of `bindToController` - that is awesome - thanks! – Steve Lorimer Nov 16 '14 at 21:19
  • 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](http://plnkr.co/edit/sVFXniFUwBBiBvdrAnwF) of what I am talking about. – 0_0 Aug 08 '16 at 15:35
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
        };
    });
Alan Souza
  • 7,475
  • 10
  • 46
  • 68
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

Community
  • 1
  • 1
Speedy059
  • 629
  • 10
  • 24