Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: Pause $digest & watchers on hidden DOM elements

We're building a single page application which has multiple pages loaded as tabs. Only the content of one tab is visible at any given time (much like a browser), so we want to temporarily pause $digest and watchers from executing on those DOM nodes of the hidden tabs, until the user switches to that tab.

Is there a way to achieve this, so that the model continues to be updated for the background tabs, but the view updates based on a condition.

The following code illustrates the problem:

<div ng-repeat="tab in tabs" ng-show="tab.id == current_tab.id">
    <!-- tab content with bindings -->
</div>

The goal is optimization.

I'm already aware of Scalyr directives, but I want a more specific solution without the extra features contained in Scalyr.

like image 440
Yogesh Mangaj Avatar asked Apr 22 '15 15:04

Yogesh Mangaj


2 Answers

After some trial and error I've figured out the following directive which pauses all the children's $$watchers if the expression on the attribute evaluates to true, on false it restores any backed up $$watchers

app.directive('pauseChildrenWatchersIf', function(){
    return {
        link: function (scope, element, attrs) {

            scope.$watch(attrs.pauseChildrenWatchersIf, function (newVal) {
                if (newVal === undefined) {
                    return;
                }
                if (newVal) {
                    toggleChildrenWatchers(element, true)
                } else {
                    toggleChildrenWatchers(element, false)
                }
            });
            function toggleChildrenWatchers(element, pause) {
                angular.forEach(element.children(), function (childElement) {
                    toggleAllWatchers(angular.element(childElement), pause);
                });
            }

            function toggleAllWatchers(element, pause) {
                var data = element.data();
                if (data.hasOwnProperty('$scope') && data.$scope.hasOwnProperty('$$watchers') && data.$scope.$$watchers) {
                    if (pause) {
                        data._bk_$$watchers = [];
                        $.each(data.$scope.$$watchers, function (i, watcher) {
                            data._bk_$$watchers.push($.extend(true, {}, watcher))
                        });

                        data.$scope.$$watchers = [];
                    } else {
                        if (data.hasOwnProperty('_bk_$$watchers')) {
                            $.each(data._bk_$$watchers, function (i, watcher) {
                                data.$scope.$$watchers.push($.extend(true, {}, watcher))
                            });
                        }
                    }

                }
                toggleChildrenWatchers(element, pause);
            }
        }
    }
});
like image 50
Yogesh Mangaj Avatar answered Oct 02 '22 12:10

Yogesh Mangaj


Ok, the reason I asked you to show some code was because of the reason @Rouby stated.

For performance purposes, you can use ng-if instead of ng-show. ng-if removes or restores the element from the DOM.

<div ng-repeat="tab in tabs" ng-if="tab.id == current_tab.id">
    <!-- tab content with bindings -->
</div>

ng-show is good to use when you want to style the hiding differently. For instance, you might want that a hidden element would only have its "body" hidden, with the header still appearing. It is possible with ng-show, you just have to define a CSS style for the class ng-hide.

If you want to keep the values of your $scope, you can bind those with a parent scope who would keep your variables intact.

like image 43
Deblaton Jean-Philippe Avatar answered Oct 02 '22 13:10

Deblaton Jean-Philippe