ChrisV
ChrisV

Reputation: 241

Angular JS directive isolated scope two way binding not working

I know this is a pretty common problem but I searched a lot of posts and didn't find a solution to my problem.

The purpose of my directive is to minimize a sidebar responsively. In order to do this, I register media queries and add listeners to it for adding specific class but I have a problem with the two-way-binding on isMinimized that does not update parent scope.

Actually, it updates the parent scope at the initialization when _mqlMinimizeListener(mql***Devices); or _mqlUnminimizeListener(mql***Devices); are called for the first time (to detect initial size of the screen) but after value is no more updated.

Here is my directive :

angular.module('app.minimizableSideBar').directive('minimizableSideBar',
[
    '$window',
    function($window) {
        return {
            restrict: 'A',
            scope: {
                minimizeOn: '@',
                addClass: '@',
                isMinimized: '='
            },
            link: function(scope, element) {

                /**
                 * Initialize the directive and handle behavior regarding provided params
                 */
                var init = function() {

                    if (!_validParams()) {
                        return;
                    }

                    // If browser is recent
                    if ($window.matchMedia) {
                        if (scope.minimizeOn === 'object') {
                            _handleRangeBreakpoint();
                        } else {
                            scope.minimizeOn = parseInt(scope.minimizeOn);
                            _handleSimpleBreakpoint();
                        }
                    } else {
                        // If browser outdated, we fall back on innerWidth
                        scope.$watch(function() {
                            return $window.innerWidth;
                        }, function(value) {
                            if (value < scope.minimizeOn) {
                                element.addClass(scope.addClass);
                            } else {
                                element.removeClass(scope.addClass);
                            }
                        });
                    }

                    // We handle external minimization (i.e. the user want to minimize/un-minimize
                    // the sidebar by clicking on a button
                    scope.$watch('isMinimized', function(value) {
                        if (value) {
                            element.addClass(scope.addClass);
                        } else {
                            element.removeClass(scope.addClass);
                        }
                    });
                };

                /**
                 * Check params validity
                 *
                 * @returns {Boolean} true if params are valid, false otherwise
                 * @private
                 */
                var _validParams = function() {
                    if (scope.addClass &&
                        scope.minimizeOn) {

                        if (scope.minimizeOn === 'object' &&
                            (!scope.minimizeOn.lowerSize ||
                            !scope.minimizeOn.upperSize ||
                            Number.isNaN(scope.minimizeOn.lowerSize) ||
                            Number.isNaN(scope.minimizeOn.upperSize))) {
                            return false;
                        } else if (Number.isNaN(scope.minimizeOn)) {
                            return false;
                        }
                        return true;
                    }
                };

                /**
                 * Listener for minimizing action
                 *
                 * @param {MediaQueryList} mql a media query listener
                 */
                var _mqlMinimizeListener = function(mql) {
                    if (mql.matches) {
                        element.addClass(scope.addClass);
                        scope.isMinimized = true;
                    }
                };

                /**
                 * Listener for unminimizing action
                 *
                 * @param {MediaQueryList} mql a media query listener
                 */
                var _mqlUnminimizeListener = function(mql) {
                    if (mql.matches) {
                        element.removeClass(scope.addClass);
                        scope.isMinimized = false;
                    }
                };

                /**
                 * Handle Range breakpoint with lower size and higher size
                 *
                 * @private
                 */
                var _handleRangeBreakpoint = function() {
                    var lowerSize = parseInt(scope.minimizeOn.lowerSize);
                    var upperSize = parseInt(scope.minimizeOn.upperSize);

                    // Handle screen sizes
                    //-- In the range
                    var mqlRangeDevices = $window.matchMedia('screen and (min-width: ' + lowerSize + ' px) and (max-width: ' + upperSize + 'px)');
                    mqlRangeDevices.addListener(_mqlMinimizeListener);
                    _mqlMinimizeListener(mqlRangeDevices);

                    //-- Out of the range
                    var mqlInfDevices = $window.matchMedia('screen and (max-width: ' + lowerSize - 1 + 'px)');
                    mqlInfDevices.addListener(_mqlUnminimizeListener);
                    _mqlUnminimizeListener(mqlInfDevices);

                    var mqlSupDevices = $window.matchMedia('screen and (min-width: ' + (upperSize + 1) + 'px)');
                    mqlSupDevices.addListener(_mqlUnminimizeListener);
                    _mqlUnminimizeListener(mqlSupDevices);
                };

                /**
                 * Handle simple breakpoint (i.e. when mnimizeOn only contains string for one size)
                 *
                 * @private
                 */
                var _handleSimpleBreakpoint = function() {
                    var mqlInfDevices = $window.matchMedia('screen and (max-width: ' + scope.minimizeOn + 'px)');
                    mqlInfDevices.addListener(_mqlMinimizeListener);
                    _mqlMinimizeListener(mqlInfDevices);

                    var mqlSupDevices = $window.matchMedia('screen and (min-width: ' + (scope.minimizeOn + 1) + 'px)');
                    mqlSupDevices.addListener(_mqlUnminimizeListener);
                    _mqlUnminimizeListener(mqlSupDevices);
                };

                init();
            }
        };
    }
]
);

and here is the HTML markup :

<div class="sidebar navbar-collapse collapse sidebar-navbar-collapse"
     data-minimizable-side-bar
     data-minimize-on="992"
     data-add-class="sidebar-minimized"
     data-is-minimized="sidebar.isMinimized">
</div>

Thanks !

Upvotes: 1

Views: 342

Answers (1)

ChrisV
ChrisV

Reputation: 241

Ok I finally understood my mistake !

As I am updating scope from "native" Javascript listeners, I have to call scope.$apply() to get scope updated.

Upvotes: 1

Related Questions