4

I'm having a problem trying to grasp why two way data binding is't working in my custom directive?

The codez:

Directive code:

.directive('invoiceFilter', function () {
    return {
        restrict: 'E',
        replace: true,
        templateUrl: '_invoice-filter.tpl.html',
        scope: {
            selectedItem: '=',
            selectedItemChange: '&'
        },
        link: function(scope) {

            scope.items = {
                all: 'Show all invoices',
                draft: 'Show drafts only',
                open: 'Show open invoices',
                paid: 'Show paid invoices'
            };

            scope.raiseSelectedItemChange = function (key) {


                alert('Selected item in directive: ' + key + " (which seems to work!)");

                scope.selectedItem = key;
                scope.selectedItemChange();

            };

        }
    };
});

Directive template

<div class="btn-group dropdown">
    <button class="btn dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown">
        {{ items[selectedItem || 'open' ] }}
        <span class="caret"></span>
    </button>
    <ul class="dropdown-menu" role="menu" aria-labelledby="dropdownMenu1">
        <li ng-repeat="(key, value) in items">
            <a href="javascript:void(0)" ng-click="raiseSelectedItemChange(key)">{{ value }}</a>
        </li>
    </ul>
</div>

As you can see I'm just adding some data to the scope (in the link function) and relying on the behavior of ng-repeat and ng-click to raise an event/callback when an item is selected. The alert correctly displays the selected item.

But when I start using the directive like this:

HTML

  <body ng-controller="MainController">
    <h1>Hello Plunker!</h1>
    <invoice-filter selected-item="filter" selected-item-change="filterChange()"></invoice-filter>
  </body>

Controller

    .controller('MainController', function($scope) {

        $scope.filter = "all";

        $scope.filterChange = function() {

          alert("Selected item in controller: " + $scope.filter + " (does not work, databinding problem???)");

        };

    })

The $scope.filter value never gets updated with the item I selected in the directive, even though I specified '=' on the directive scope which to my understanding should enable two way data binding, right?

What am I missing here?

Plunk playground

Here's a plunkr with the setup described above, so you can verify that it doesn't work :o/

Thanks to anyone who can help!

2 Answers 2

10

The two-way binding works, but happens on the next cycle. That's why when you reproduce it again, the value of the last cycle is shown. This is because AngularJS has no chance to run its data binding inbetween the line where you set the value and the line where you call the callback. JavaScript, in that sense, doesn't allow intervention, yet.

Changing scope.selectedItemChange(); to $timeout(scope.selectedItemChange); is an easy fix (don't forget to inject $timeout), by forcing the callback to be called on the next cycle.

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

2 Comments

Alright! :o) As you indicated, changing to scope.selectedItemChange(); to $timeout(scope.selectedItemChange); seems to do the trick! One additional question: is the link function the appropriate place for this code or should I use a controller inside the directive? Many thanks!
The link function is the right place! Use a shared directive controller if there are coupled directives, such as ngForm and ngModel are in the AngularJS base.
0

I had implemented something similar and resolved it slightly shorter version:

just add

scope.$watch();

after

scope.selectedItem = key;

And Angular will resolve by himself how and what to do.

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.