0

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.

celsomtrindade
  • 4,501
  • 18
  • 61
  • 116
  • if you want to view your data different depending on your window size you should use css, like `@media screen and (max-width: 600px)` – Lusk116 Jan 21 '16 at 19:47
  • I know about this, but this is going to be used for other purpouses, so even with a basic example I'll expand it later. Also, ng-if doesn't render the html, which in this case, is pretty extensive – celsomtrindade Jan 21 '16 at 19:56

3 Answers3

1

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

Community
  • 1
  • 1
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
0

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.

tanenbring
  • 780
  • 4
  • 14
  • I noticed this after i made the question. Then i inserted other div with the opposite value (see edited question) but the problem still persists.. When using log, I can se the resize event at all times. – celsomtrindade Jan 21 '16 at 18:53
  • The scope: true means the isBlock before the directive is in a different scope than the one you are setting/changing in the directive. Try removing that. – tanenbring Jan 21 '16 at 19:02
0

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;
                }
            })
        }
    };
}
Nolan
  • 888
  • 1
  • 7
  • 18
  • though it syntactical mistake, watch `$element` why? – Pankaj Parkar Jan 21 '16 at 20:20
  • good catch, didn't catch that as I took it the code from some of my element body text overflow code where I have a controller where I inject $element – Nolan Jan 21 '16 at 20:45
  • Understood the concept, but 2 questions: -How to pass the value to the view? Still not able to do this. -I've read some articles saying using $watch isn't a proper best practice for ng, and can take a hit on the performance. The project I'm working on is kind of complex, so I'm trying to save everywhere it's possible. – celsomtrindade Jan 21 '16 at 21:28