celsomtrindade
celsomtrindade

Reputation: 4671

AngularJS directive doesn't update scope value even with apply

I'm usin a directive to show a div on the screen only when the screen size is smaller than 600px. The problem is, the scope value isn't being updated, even using $apply() inside the directive.

This is the code:

function showBlock($window,$timeout) {
    return {
        restrict: 'A',
        scope: true,
        link: function(scope, element, attrs) {
            scope.isBlock = false;
            checkScreen();

            function checkScreen() {
                var wid = $window.innerWidth;
                if (wid <= 600) {
                    if(!scope.isBlock) {
                        $timeout(function() {
                            scope.isBlock = true;
                            scope.$apply();
                        }, 100);
                    };

                } else if (wid > 600) {
                    if(scope.isBlock) {
                        $timeout(function() {
                            scope.isBlock = false;
                            scope.$apply();
                        }, 100);
                    };
                };
            };
            angular.element($window).bind('resize', function(){
                checkScreen();
            }); 
        }
    };
}

html:

<div ng-if="isBlock" show-block>
    //..conent to show
</div>
<div ng-if="!isBlock" show-block>
    //..other conent to show
</div>

Note: If I don't use $timeout I'll get the error

$digest already in progress

I used console logs inside to check if it's updating the value, and inside the directive everything works fine. But the changes doesn't go to the view. The block doesn't show.

Upvotes: 0

Views: 1548

Answers (3)

Pankaj Parkar
Pankaj Parkar

Reputation: 136144

You should use do rule in such cases to get the advantage of Prototypal Inheritance of AngularJS.

Basically you need to create a object, that will will have various property. Like in your case you could have $scope.model = {} and then place isBlock property inside it. So that when you are inside your directive, you will get access to parent scope. The reason behind it is, you are having scope: true, which says that the which has been created in directive is prototypically inherited from parent scope. That means all the reference type objects are available in your child scope.

Markup

<div ng-if="model.isBlock" show-block>
    //..conent to show
</div>
<div ng-if="!model.isBlock" show-block>
    //..other conent to show
</div>

Controller

app.controller('myCtrl', function($scope){
   //your controller code here

   //here you can have object defined here so that it can have properties in it
   //and child scope will get access to it.
   $scope.model = {}; //this is must to use dot rule,
   //instead of toggle property here you could do it from directive too
   $scope.isBlock = false; //just for demonstration purpose

});

and then inside your directive you should use scope.model.isBlock instead of scope.isBlock

Update

As you are using controllerAs pattern inside your code, you need to use scope.ag.model.isBlock. which will provide you an access to get that scope variable value inside your directive.

Basically you can get the parent controller value(used controllerAs pattern) make available controller value inside the child one. You can find object with your controller alias inside the $scope. Like here you have created ag as controller alias, so you need to do scope.ag.model to get the model value inside directive link function.

NOTE

You don't need to use $apply with $timeout, which may throw an error $apply in progress, so $timeout will run digest for you, you don't need to worry about to run digest.

Demo Here

Upvotes: 1

Nolan
Nolan

Reputation: 916

In my experience linear code never works well with dynamic DOM properties such as window sizing. With code that is looking for screens size you need to put that in some sort of event / DOM observer e.g. in angular I'd use a $watch to observe the the dimensions. So to fix this you need to place you code in a $watch e.g below. I have not tested this code, just directional. You can watch $window.innerWidth or you can watch $element e.g. body depending on your objective. I say this as screens will be all over the place but if you control a DOM element, such as, body you have better control. also I've not use $timeout for brevity sake.

// watch window width
showBlock.$inject = ['$window'];
function bodyOverflow($window) {
    var isBlock = false;
    return {
        restrict: 'EA',
        link: function ($scope, element, attrs) {
            $scope.$watch($window.innerWidth, function (newWidth, oldWidth) {
                if (newWidth !== oldWidth) {
                    return isBlock = newWidth <= 600;
                }
            })
        }
    };
}

// OR watch element width
showBlock.$inject = [];
function bodyOverflow() {
    var isBlock = false;
    return {
        restrict: 'EA',
        link: function ($scope, element, attrs) {
            $scope.$watch($element, function (new, old) {
                if (newWidth) {
                    return isBlock = newWidth[0].offsetWidth <= 600;
                }
            })
        }
    };
}

Upvotes: 0

tanenbring
tanenbring

Reputation: 780

I suspect it has something to do with the fact that the show-block directive wouldn't be fired if ng-if="isBlock" is never true, so it would never register the resize event.

Upvotes: 0

Related Questions