Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

angularjs

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

like image 461
Mdb Avatar asked Mar 21 '14 11:03

Mdb


4 Answers

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);
    };
});
like image 183
BKM Avatar answered Nov 08 '22 04:11

BKM


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

like image 14
BbopLifa Avatar answered Nov 08 '22 03:11

BbopLifa


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)

like image 8
Phil Avatar answered Nov 08 '22 04:11

Phil


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/

like image 4
Inc1982 Avatar answered Nov 08 '22 03:11

Inc1982