AngularJS - bind to directive resize
Asked Answered
S

6

22

How can i be notified when a directive is resized? i have tried

element[0].onresize = function() {
            console.log(element[0].offsetWidth + " " + element[0].offsetHeight);
        }

but its not calling the function

(function() {
'use strict';

// Define the directive on the module.
// Inject the dependencies. 
// Point to the directive definition function.
angular.module('app').directive('nvLayout', ['$window', '$compile', layoutDirective]);

function layoutDirective($window, $compile) {
    // Usage:
    // 
    // Creates:
    // 
    var directive = {
        link: link,
        restrict: 'EA',
        scope: {
            layoutEntries: "=",
            selected: "&onSelected"
        },
        template: "<div></div>",
        controller: controller
    };
    return directive;

    function link(scope, element, attrs) {
        var elementCol = [];

        var onSelectedHandler = scope.selected();

        element.on("resize", function () {
            console.log("resized.");
        });

        $(window).on("resize",scope.sizeNotifier);

        scope.$on("$destroy", function () {
            $(window).off("resize", $scope.sizeNotifier);
        });

        scope.sizeNotifier = function() {
            alert("windows is being resized...");
        };

        scope.onselected = function(id) {
            onSelectedHandler(id);
        };



        scope.$watch(function () {
            return scope.layoutEntries.length;
        },
        function (value) {
            //layout was changed
            activateLayout(scope.layoutEntries);
        });

        function activateLayout(layoutEntries) {


            for (var i = 0; i < layoutEntries.length; i++) {

                if (elementCol[layoutEntries[i].id]) {
                    continue;
                }
                var div = "<nv-single-layout-entry id=slot" + layoutEntries[i].id + " on-selected='onselected' style=\"position:absolute;";
                div = div + "top:" + layoutEntries[i].position.top + "%;";
                div = div + "left:" + layoutEntries[i].position.left + "%;";
                div = div + "height:" + layoutEntries[i].size.height + "%;";
                div = div + "width:" + layoutEntries[i].size.width + "%;";
                div = div + "\"></nv-single-layout-entry>";

                var el = $compile(div)(scope);
                element.append(el);
                elementCol[layoutEntries[i].id] = 1;
            }


        };
    }

    function controller($scope, $element) {

    }
}

      })();
Supersensual answered 16/1, 2014 at 19:5 Comment(3)
I have tried understand the purpose of code but it is hard to me. I have eliminated the JQuery dependency of your code and you can check here: jsfiddle.net/U3pVM/2669Respectable
i am just trying to be notified when the element of the directive is resized. i can listen on the whole page but i want to know when elemnt cause the directive to moveSupersensual
@li-raz: my answer accomplishes exactly that :-)Icky
I
15

Use scope.$watch with a custom watch function:

scope.$watch(
  function () {
    return [element[0].offsetWidth, element[0].offsetHeight].join('x');
  },
  function (value) {
    console.log('directive got resized:', value.split('x'));
  }
)
Icky answered 12/4, 2014 at 14:26 Comment(5)
I've tried this, but it normally shows me old values. I have a sidebar that can be hide or shown using a button, when I click, the digest cycle happens and the watch function tells me the size of the element didnt change and then the element is resized, which is picked up in the next cycle, which causes a glitch. Any ideas?Georgena
@HernanRajchert (and any other future readers), that is because clientHeight can be calculated as CSS height + CSS padding - height of horizontal scrollbar (if present). What you want is .offsetHeight. Watching element[0].offsetHeight (and width) should work.Jiles
@JordanCarroll For me, using offsetHeight instead of clientHeight didn't solve the problem.Polyanthus
You are right, I adjusted the answer accordingly. Thanks for pointing this out.Icky
I have the same problem with digest cycle, the resize event is triggered only on the next call. My question is here #39372274 . unfortunately watching to offsetHeight/Width didn't seem to fix that issue.Bastien
G
14

You would typically want to watch the element's offsetWidth and offsetHeight properties. With more recent versions of AngularJS, you can use $scope.$watchGroup in your link function:

app.directive('myDirective', [function() {

    function link($scope, element) {
        var container = element[0];

        $scope.$watchGroup([
            function() { return container.offsetWidth; },
            function() { return container.offsetHeight; }
        ],  function(values) {
              // Handle resize event ...
        });
    }

    // Return directive definition ...

}]);

However, you may find that updates are quite slow when watching the element properties directly in this manner.

To make your directive more responsive, you could moderate the refresh rate by using $interval. Here's an example of a reusable service for watching element sizes at a configurable millisecond rate:

app.factory('sizeWatcher', ['$interval', function($interval) {
    return function (element, rate) {
        var self = this;
        (self.update = function() { self.dimensions = [element.offsetWidth, element.offsetHeight]; })();
        self.monitor = $interval(self.update, rate);
        self.group = [function() { return self.dimensions[0]; }, function() { return self.dimensions[1]; }];
        self.cancel = function() { $interval.cancel(self.monitor); };
    };
}]);

A directive using such a service would look something like this:

app.directive('myDirective', ['sizeWatcher', function(sizeWatcher) {

    function link($scope, element) {
        var container = element[0],
            watcher = new sizeWatcher(container, 200);

        $scope.$watchGroup(watcher.group, function(values) {
            // Handle resize event ...
        });

        $scope.$on('$destroy', watcher.cancel);
    }

    // Return directive definition ...

}]);

Note the call to watcher.cancel() in the $scope.$destroy event handler; this ensures that the $interval instance is destroyed when no longer required.

A JSFiddle example can be found here.

Gasiform answered 1/5, 2015 at 12:50 Comment(1)
Whoever wants to use this method consider replacing $interval(self.update, rate); with $interval(self.update, rate, 0, false);, which explicitly says not to invoke $apply in the end. Otherwise digest for the scope will be called on each interval callback function call. You can check this by $scope.$watch(function() { console.log("invalidated"); });Collinsia
S
8

Here a sample code of what you need to do:

APP.directive('nvLayout', function ($window) {
  return {
    template: "<div></div>",
    restrict: 'EA',
    link: function postLink(scope, element, attrs) {

      scope.onResizeFunction = function() {
        scope.windowHeight = $window.innerHeight;
        scope.windowWidth = $window.innerWidth;

        console.log(scope.windowHeight+"-"+scope.windowWidth)
      };

      // Call to the function when the page is first loaded
      scope.onResizeFunction();

      angular.element($window).bind('resize', function() {
        scope.onResizeFunction();
        scope.$apply();
      });
    }
  };
});
Sarcoma answered 4/4, 2014 at 7:29 Comment(2)
This will not work if only the directive get's resized but not the window itself.Icky
bind is working for me ... though I don't think you need to call applyWooley
T
2

The only way you would be able to detect size/position changes on an element using $watch is if you constantly updated your scope using something like $interval or $timeout. While possible, it can become an expensive operation, and really slow your app down.

One way you could detect a change on an element is by calling requestAnimationFrame.

var previousPosition = element[0].getBoundingClientRect();

onFrame();

function onFrame() {
  var currentPosition = element[0].getBoundingClientRect();

  if (!angular.equals(previousPosition, currentPosition)) {
    resiszeNotifier();
  }

  previousPosition = currentPosition;
  requestAnimationFrame(onFrame);
}

function resiszeNotifier() {
  // Notify...
}

Here's a Plunk demonstrating this. As long as you're moving the box around, it will stay red.

http://plnkr.co/edit/qiMJaeipE9DgFsYd0sfr?p=preview

Theogony answered 22/1, 2015 at 6:51 Comment(0)
A
0

A slight variation on Eliel's answer worked for me. In the directive.js:

      $scope.onResizeFunction = function() {
      };

      // Call to the function when the page is first loaded
      $scope.onResizeFunction();

      angular.element($(window)).bind('resize', function() {
        $scope.onResizeFunction();
        $scope.$apply();
      });

I call

    $(window).resize();

from within my app.js. The directive's d3 chart now resizes to fill the container.

Amarillo answered 15/9, 2015 at 23:38 Comment(0)
T
0

Here is my take on this directive (using Webpack as bundler):

module.exports = (ngModule) ->

  ngModule.directive 'onResize', ['Callback', (Callback) ->
    restrict: 'A'
    scope:
      onResize: '@'
      onResizeDebounce: '@'
    link: (scope, element) ->
      container = element[0]
      eventName = scope.onResize || 'onResize'
      delay = scope.onResizeDebounce || 1000
      scope.$watchGroup [
        -> container.offsetWidth ,
        -> container.offsetHeight
      ], _.debounce (values) ->
        Callback.event(eventName, values)
      , delay
  ]
Tace answered 29/9, 2015 at 11:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.