26

I have a directive with isolated scope with a value with two way binding to the parent scope. I am calling a method that changes the value in the parent scope, but the change is not applied in my directive.(two way binding is not triggered). This question is very similar:

AngularJS: Parent scope not updated in directive (with isolated scope) two way binding

but I am not changing the value from the directive, but changing it only in the parent scope. I read the solution and in point five it is said:

The watch() created by the isolated scope checks whether it's value for the bi-directional binding is in sync with the parent's value. If it isn't  the parent's value is copied to the isolated scope.

Which means that when my parent value is changed to 2, a watch is triggered. It checks whether parent value and directive value are the same - and if not it copies to directive value. Ok but my directive value is still 1 ... What am I missing ?

html :

<div data-ng-app="testApp">
    <div data-ng-controller="testCtrl">
        <strong>{{myValue}}</strong>
        <span data-test-directive data-parent-item="myValue" 
            data-parent-update="update()"></span>
    </div>
</div>

js:

var testApp = angular.module('testApp', []);

testApp.directive('testDirective', function ($timeout) {
    return {
        scope: {
            key: '=parentItem',
            parentUpdate: '&'
        },
        replace: true,
        template:
            '<button data-ng-click="lock()">Lock</button>' +
            '</div>',
        controller: function ($scope, $element, $attrs) {
            $scope.lock = function () {
                console.log('directive :', $scope.key);

                 $scope.parentUpdate();
                 //$timeout($scope.parentUpdate); // would work.

                 // expecting the value to be 2, but it is 1
                 console.log('directive :', $scope.key);
            };
        }
    };
});

testApp.controller('testCtrl', function ($scope) {
    $scope.myValue = '1';
    $scope.update = function () {
        // Expecting local variable k, or $scope.pkey to have been
        // updated by calls in the directive's scope.
        console.log('CTRL:', $scope.myValue);
        $scope.myValue = "2";
        console.log('CTRL:', $scope.myValue);
    };
});

Fiddle

Prashant Pokhriyal
  • 3,727
  • 4
  • 28
  • 40
Mdb
  • 8,338
  • 22
  • 63
  • 98
  • better explanation of same problem discussed here: http://stackoverflow.com/a/27533683/2724125 – Amit G Mar 24 '17 at 19:46

5 Answers5

23

Use $scope.$apply() after changing the $scope.myValue in your controller like:

testApp.controller('testCtrl', function ($scope) {
    $scope.myValue = '1';
    $scope.update = function () {
        // Expecting local variable k, or $scope.pkey to have been
        // updated by calls in the directive's scope.
        console.log('CTRL:', $scope.myValue);
        $scope.myValue = "2";
        $scope.$apply();
        console.log('CTRL:', $scope.myValue);
    };
});
BKM
  • 6,949
  • 7
  • 30
  • 45
  • 5
    ok, I know there are ways to make it work, but I the questions is why this is neccessary. That is why this is called two way binding ? – Mdb Mar 21 '14 at 11:58
  • 4
    Two way data binding is something which is inside the scope of AngularJS. Here you are making change in your directive whcih is something out of the scope of Angular JS native code structure. So in order to reflect the changes you need to use $scope.$apply() – BKM Mar 21 '14 at 12:00
  • 19
    it is giving `$apply already in progress` error for me. tried the same in the jsfiddle link given above. – Mobin Skariya Mar 21 '14 at 13:55
14

The answer Use $scope.$apply() is completely incorrect.

The only way that I have seen to update the scope in your directive is like this:

angular.module('app')
.directive('navbar', function () {
    return {
        templateUrl: '../../views/navbar.html',
        replace: 'true',
        restrict: 'E',
        scope: {
            email: '='
        },
        link: function (scope, elem, attrs) {
            scope.$on('userLoggedIn', function (event, args) {
                scope.email = args.email;
            });
            scope.$on('userLoggedOut', function (event) {
                scope.email = false;
                console.log(newValue);

            });

        }
    }
});

and emitting your events in the controller like this:

$rootScope.$broadcast('userLoggedIn', user);

This feels like such a hack I hope the angular gurus can see this post and provide a better answer, but as it is the accepted answer does not even work and just gives the error $digest already in progress

BbopLifa
  • 131
  • 2
  • 5
  • if i add directive in 10 place, then what happens.. you know??? 10 time callback will get call. even give a one emit or broadcast – subash May 07 '15 at 19:52
  • In some cases from my experience if object you are sending is large you won't be able to use this as it gives an error. – HarshaXsoad Jan 30 '17 at 05:38
8

Using $apply() like the accepted answer can cause all sorts of bugs and potential performance hits as well. Settings up broadcasts and whatnot is a lot of work for this. I found the simple workaround just to use the standard timeout to trigger the event in the next cycle (which will be immediately because of the timeout). Surround the parentUpdate() call like so:

$timeout(function() {
    $scope.parentUpdate();
});

Works perfectly for me. (note: 0ms is the default timeout time when not specified)

Phil
  • 351
  • 3
  • 7
  • 5
    The reason this works is because at the end of Angular's $timeout function it calls $apply(). It's almost equivalent to `setTimout(function() { /* yourCallback() */ $rootScope.apply(); }, 0)` only it does some extra stuff I don't really understand. – rich97 Nov 07 '14 at 16:42
  • Doing `if (!$scope.$$phase) { $scope.$apply() }` also avoids `$apply already in progress error`, but anyway, that doesn't explain well why the issue is happening. Need clearer answers. – Alejandro García Iglesias Dec 12 '16 at 18:32
  • 1
    Can anyone explain why this actually has to be done in the next cycle or not in the current? – Timothy Dalton Jan 04 '17 at 14:03
4

One thing most people forget is that you can't just declare an isolated scope with the object notation and expect parent scope properties to be bound. These bindings only work if attributes have been declared through which the binding 'magic' works. See for more information:

https://umur.io/angularjs-directives-using-isolated-scope-with-attributes/

Inc1982
  • 1,955
  • 1
  • 18
  • 32
0

Instead of using $scope.$apply(), try using $scope.$applyAsync();

Eric S
  • 1,336
  • 15
  • 20
dev verma
  • 671
  • 1
  • 9
  • 18