2

I have this directive:

angular.module("app", [])
  .directive("myDirective", myDirective);

function myDirective() {
  return {
    template: '{{vm.numbers}}',
    scope: {
      numbers: '='
    },
    controller: MyController,
    controllerAs: 'vm',
    bindToController: true,
  };
}

function MyController($timeout) {
  var vm = this;
  vm.numbers.push(3);
  $timeout(function() {
     vm.numbers.push(4);
  });
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js"></script>

<div ng-app="app">
  <my-directive numbers="[1,2]"></my-directive>
</div>

Why 3 is not being pushed to the array?

2
  • You've created a function called "myController", but you are not applying it as a controller to your module, nor applying it to anything in your view to instantiate it. Commented Jul 30, 2015 at 0:14
  • @DavidPisoni it's a directive controller Commented Jul 30, 2015 at 0:50

2 Answers 2

3

There are obvious syntax difference between = and @. And one of the features of two-way = binding is that you should proceed with care when attribute value is something other than scope property name, because it wasn't meant for that.

What happens in your example is that '[1,2]' string was parsed to an array and is available as vm.number scope property when controller function is running. The changes that were made with vm.numbers.push(3) were applied to a copy of an anonymous array and they weren't observed anywhere. After controller function was finished, the first digest cycle launches, and vm.number is overwritten with [1, 2] array, again. After that $timeout function kicks in and makes another change with vm.numbers.push(4). The changes in vm.number are observed only since this moment.

That's what happens when anonymous array or object is being fed to bi-directional directive binding. You will also have problems with assigning vm.number to something else.

Since @ binding is good only for text, it isn't an option also. You can do this instead

function myDirective() {
  return {
    template: '{{vm.numbers}}',
    scope: {},
    controller: MyController,
    controllerAs: 'vm',
    bindToController: true,
  };
}

function MyController($timeout, $parse, $attrs) {
  var vm = this;
  vm.numbers = $parse($attrs.numbers)() || [];
  vm.numbers.push(3);
  $timeout(function() {
     vm.numbers.push(4);
  });
}
Sign up to request clarification or add additional context in comments.

Comments

1

When the controller executes, vm.numbers is updated per your logic. Then the link phase kicks in and the scope vars are set from your markup and the nameds in scope but you lose your interim changes from controller.

I added some console logs that show this sequence:

in controller=> [1, 2, 3]
in link, scope=> [1, 2, 3] attrs=> [1,2]
in timeout method=> [1, 2]

One approach is as suggested by @estus, to wire the parameters yourself. Another approach might be to rewrite your logic so that you can rely on angular to manage the passed.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.