paulroho
paulroho

Reputation: 1266

Change in scope does not appear in child scope

I have a directive pr-items rendering instances of child-directives pr-item using a template and ng-repeat:

app.directive('prItems', function(){
    console.log('prItems');
    return {
        restrict: 'E',
        scope: {
            items:'=',
        },
        template:'<pr-item ng-repeat="item in items"></pr-item>',
    ...
});

The child directive pr-item renders content using another template:

app.directive('prItem', function($compile){
    console.log('prItem');
    return {
        restrict: 'E',
        template: '<div ng-style="{\'border-style\':item.border_style, \'color\':item.color}" style="cursor:pointer;"><span ng-if="item.selected">* </span>{{item.name}} {{item.selected}}</div>',
   ...
});

This works fine so far.

On a click on the item, the parent directive gets notified using an event via $emit. When the parent element changes state of one of the items, this change does not show up in the respective item as demonstrated in this Plunk.

Now I am curious what I'm missing.

Upvotes: 0

Views: 53

Answers (1)

Joe Enzminger
Joe Enzminger

Reputation: 11190

Here is a corrected Plunk

I've tried to note my changes (as opposed to your version)

app.directive('prItems', function(){
    console.log('prItems');
    return {
        restrict: 'E',
        scope: {
            items:'=',
        },
        template:'<pr-item ng-repeat="item in items"></pr-item>',
        link: function (scope, elem, attr) {
            console.log('prItems.link, scope.$id = ' + scope.$id);
            scope.$on('onSelecting', function (event, item) {
                console.log('prItems: onSelecting ' + item.name);
                //clear the selected flag for the other items
                angular.forEach(scope.items, function (value) {
                    if (value.selected) {
                        //console.log(value);
                        value.selected = false;
                    }
                });
                //set it for the item that was sent in
                item.selected = true;

                //change, no $scope.$apply - not needed here
            });
        }
    };
});

app.directive('prItem', function($compile){
    console.log('prItem');
    return {
        restrict: 'E',
        //ng-style will change camel case to - case borderStyle works (instead of using 'border-style' with escaped quotes.
        template: '<div ng-style="{borderStyle:item.border_style, \'color\':item.color}" style="cursor:pointer;">CLICK ME: <span ng-if="item.selected">* </span>{{item.name}} {{item.selected}}</div>',
        controller: function ($scope, $element) {
            console.log('prItem.controller, $scope.$id = ' + $scope.$id);
            //I left this in as-is, but there is a better way to do this by just using the template ($watch shouldn't be required)
            $scope.$watch('item.selected', function (val) {
                console.log('prItem.selected ' + $scope.item.name + ' changed to ' + val);
                $scope.item.border_style = val ? 'solid' : 'none';
                //console.log('$scope.item.border_style = ' + $scope.item.border_style);
            });
        },
        link: function (scope, elem, attr) {
            console.log('prItem.link ' + scope.item.name + ', scope.$id = ' + scope.$id);

              elem.on('mousedown touchdown', function () {
                //console.log('touché!');
                //scope.$apply should "wrap" non-angular code.  In the old version , you were calling scope.$apply() inline
                scope.$apply(function(){
                  scope.$emit('onSelecting', scope.item);
                  //instead of changing scope.selected, change item.selected - this change was probably not necessary
                  item.selected = !item.selected;
                });
                //old $scope.$apply();
              });
        }
    };
});

EDIT: Without $watch

app.directive('prItem', function($compile){
    console.log('prItem');
    return {
        restrict: 'E',
        //ng-style will change camel case to - case borderStyle works (instead of using 'border-style' with escaped quotes.
        template: '<div ng-style="{borderStyle:item.selected ? \'solid\' : \'none\', \'color\':item.color}" style="cursor:pointer;">CLICK ME: <span ng-if="item.selected">* </span>{{item.name}} {{item.selected}}</div>',
        controller: function ($scope, $element) {
            console.log('prItem.controller, $scope.$id = ' + $scope.$id);
        },
        link: function (scope, elem, attr) {
            console.log('prItem.link ' + scope.item.name + ', scope.$id = ' + scope.$id);

              elem.on('mousedown touchdown', function () {
                //console.log('touché!');
                //scope.$apply should "wrap" non-angular code.  In the old version , you were calling scope.$apply() inline
                scope.$apply(function(){
                  scope.$emit('onSelecting', scope.item);
                  //instead of changing scope.selected, change item.selected - this change was probably not necessary
                  item.selected = !item.selected;
                });
                //old $scope.$apply();
              });
        }
    };
});

Upvotes: 1

Related Questions