AngularJS: лучший способ наблюдать за изменением высоты

У меня старая проблема с навигацией по высоте: A position: fixes навигация сверху и содержимое с margin-top: $naviHeight ниже. Навигационная система может изменять высоту, когда данные загружаются асинхронно, и поэтому поле содержимого должно меняться вместе с ней.

Я хочу, чтобы это было самодостаточным. Таким образом, нет кода, в котором данные загружаются, но только в задействованных html-элементах/директивах.

В настоящее время я делаю это в AngularJS 1.2.0 с таким таймером:

/*
* Get notified when height changes and change margin-top
 */
.directive( 'emHeightTarget', function(){
    return {
        link: function( scope, elem, attrs ){

            scope.$on( 'heightchange', function( ev, newHeight ){

                elem.attr( 'style', 'margin-top: ' + (58+newHeight) + 'px' );
            } );
        }
    }
})

/*
* Checks this element periodically for height changes
 */
.directive( 'emHeightSource', ['$timeout', function( $timeout ) {

    return {
        link: function( scope, elem, attrs ){

            function __check(){

                var h = elem.height();

                if( h != scope.__height ){

                    scope.__height = h;
                    scope.$emit( 'heightchange', h );
                }
                $timeout( __check, 1000 );
            }
            __check();
        }
    }

} ] )

У этого есть очевидный недостаток использования таймера (который я чувствую как-то некрасиво) и определенную задержку после изменения размера навигации, пока содержимое не будет перемещено. p >

Есть ли лучший способ сделать это?

Ответы

Ответ 1

Это работает, регистрируя наблюдателя в emHeightSource, который вызывается каждый $digest. Он обновляет свойство __height, которое в свою очередь просматривается в emHeightTarget:

/*
 * Get notified when height changes and change margin-top
 */
.directive( 'emHeightTarget', function() {
    return {
        link: function( scope, elem, attrs ) {

            scope.$watch( '__height', function( newHeight, oldHeight ) {
                elem.attr( 'style', 'margin-top: ' + (58 + newHeight) + 'px' );
            } );
        }
    }
} )

/*
 * Checks every $digest for height changes
 */
.directive( 'emHeightSource', function() {

    return {
        link: function( scope, elem, attrs ) {

            scope.$watch( function() {
                scope.__height = elem.height();
            } );
        }
    }

} )

Ответ 2

Вы можете контролировать изменение высоты элемента без использования Div, просто пишите инструкцию $watch:

// Observe the element height.
scope.$watch
    (
        function () {
            return linkElement.height();
        },
        function (newValue, oldValue) {
            if (newValue != oldValue) {
                // Do something ...
                console.log(newValue);
            }
        }
    );

Ответ 3

Возможно, вам стоит посмотреть изменения размеров $window ', например:

.directive( 'emHeightSource', [ '$window', function(  $window ) {

    return {
        link: function( scope, elem, attrs ){

           var win = angular.element($window);
           win.bind("resize",function(e){

              console.log(" Window resized! ");
              // Your relevant code here...

           })
        }
    }    
} ] )

Ответ 4

Я использовал комбинацию $watch и resize event. Я нашел без ограничений. $Apply(); в событии изменения размера изменения высоты элемента не всегда подхватываются $watch.

   link:function (scope, elem) {
        var win = angular.element($window);
        scope.$watch(function () {
                return elem[0].offsetHeight;
        },
          function (newValue, oldValue) {
              if (newValue !== oldValue)
              {
                  // do some thing
              }
          });

        win.bind('resize', function () {
            scope.$apply();
        });
    };

Ответ 5

Этот подход позволяет избежать (потенциально) запуска reflow каждого цикла дайджеста. Он проверяет только elem.height()/after/цикл дайджеста, и только вызывает новый дайджест, если высота изменилась.

var DEBOUNCE_INTERVAL = 50; //play with this to get a balance of performance/responsiveness
var timer
scope.$watch(function() { timer = timer || $timeout(
    function() {
       timer = null;
       var h = elem.height();
       if (scope.height !== h) {
           scope.$apply(function() { scope.height = h })
       }
    },
    DEBOUNCE_INTERVAL,
    false
)

Ответ 6

Я написал еще один вариант, основанный на таймере, который может быть привязан к области видимости, потому что, как правильно заявил Джейми Паэт, прямой доступ DOM внутри цикла дайджеста - не очень хорошая идея.

.directive("sizeWatcher", ['$timeout', function ($timeout) {
    return {
        scope: {
            sizeWatcherHeight: '=',
            sizeWatcherWidth: '=',
        },
        link: function( scope, elem, attrs ){
            function checkSize(){
                scope.sizeWatcherHeight = elem.prop('offsetHeight');
                scope.sizeWatcherWidth = elem.prop('clientWidth');
                $timeout( checkSize, 1000 );
            }
            checkSize();
        }
    };
}

Теперь вы можете привязать его к любому элементу:

<img size-watcher size-watcher-height="myheight">
<div style="height: {{ myheight }}px">

Таким образом, div всегда сохраняет (с латентностью секунды) ту же высоту, что и изображение.

Ответ 7

Этот подход отслеживает высоту и ширину элементов и присваивает их переменной в области, указанной в атрибуте ваших элементов

 <div el-size="size"></div>


.directive('elSize', ['$parse', function($parse) {
  return function(scope, elem, attrs) {
    var fn = $parse(attrs.elSize);

    scope.$watch(function() {
      return { width: elem.width(), height: elem.height() };
    }, function(size) {
      fn.assign(scope, size);
    }, true);

  }
}])