1

I've read and tried probably every thread on angular $watch() DOM element height but can't work out how to do this. Any help is greatly appreciated!

I have an angular app that does a simple class name update by changing a model value. Example:

class="theme-{{themeName}}"

When the class updates the DIV changes height.

I want to receive a callback on the height change.

I've tried to use $watch() and $watch(..,,true) and using both angular.element() as well as jquery ( $('foo')... ) but the $digest cycle never even calls the $watch expression.

Update (code example):

'use strict';

angular.module('k2')
.directive('k2', ['$rootScope', '$templateCache', '$timeout', 'lodash' ,'k2i',
function ($rootScope, $templateCache, $timeout, lodash, k2i) {
  return {
    restrict: 'E',
    template: $templateCache.get('k2/templates/k2.tpl.html'),
    replace: true,
    scope: {
      ngShow: '=',
      ngHide: '=',
      settings: '='
    },
    link: function(scope, elem, attrs) {
      k2i.initK2(scope, scope.settings || {});

      scope.$watch(function() {
        return $('.k2 .k2-template [k2-name]').height();
      }, function(newValue, oldValue, scope) {
        respondToChange(newValue, oldValue, scope);
      }, true);

      scope.$watch(function() {
        var kb = document.querySelectorAll('.k2 .k2-template [k2-name]')[0];
        var ab = document.querySelectorAll('.k2 .k2-template [k2-name] .k2-acc-bar')[0];
        var value = {
          kb: 0,
          ab: 0
        }
        if (kb) {
          value.kb = kb.clientHeight;
        }
        if (ab) {
          value.ab = ab.clientHeight;
        }
        return value;
      }, function(newValue, oldValue, scope) {
        respondToChange(newValue, oldValue, scope);
      }, true);

      function respondToChange(newValue, oldValue, scope) {
        if (newValue === oldValue) return;
        if (!scope.k2Pending) return;

        var kbNode = document.querySelectorAll('.k2 .k2-template [k2-name="' + scope.k2Pending.name + '"]');
        var abNode = document.querySelectorAll('.k2 .k2-template [k2-name="' + scope.k2Pending.name + '"] .k2-acc-bar');

        // Ensure required keyboard elements are in the DOM and have height.
        if ((kbNode.length > 0 && !scope.k2Pending.requiresAccessoryBar ||
           kbNode.length > 0 && abNode.length > 0 && scope.k2Pending.requiresAccessoryBar) &&

          (kbNode[0].clientHeight > 0 && !scope.k2Pending.requiresAccessoryBar ||
           kbNode[0].clientHeight > 0 && abNode[0].clientHeight > 0 && scope.k2Pending.requiresAccessoryBar)) {

          $rootScope.$emit('K2KeyboardInDOM', scope.k2Pending.name, getHeight());
        }
      };

      function getHeight() {
        var height = {};
        var kbElem = angular.element(document.querySelectorAll('.k2')[0]);

        var wasHidden = kbElem.hasClass('ng-hide');
        kbElem.removeClass('ng-hide');
        height[k2i.modes.NONE] = 0;
        height[k2i.modes.ALL] = document.querySelectorAll('.k2 .k2-template [k2-name="' + scope.k2Name + '"]')[0].clientHeight;
        height[k2i.modes.ACCESSORY_BAR_ONLY] = document.querySelectorAll('.k2 .k2-template [k2-name="' + scope.k2Name + '"] .k2-acc-bar')[0].clientHeight;
        height[k2i.modes.KEYBOARD_KEYS_ONLY] = height[k2i.modes.ALL] - height[k2i.modes.ACCESSORY_BAR_ONLY];
        if (wasHidden) {
          kbElem.addClass('ng-hide');
        }
        return height;
       };
     }
   }
 }
]);
6
  • 1
    post your code, this way we could actually see where the issue is.. Commented Oct 11, 2016 at 20:49
  • 1
    FWIW, class name updates should be done using the ng-class directive Commented Oct 11, 2016 at 20:53
  • this helps? stackoverflow.com/questions/19048985/… Commented Oct 11, 2016 at 20:56
  • @big_water - thanks; does using ng-class vs. a model attribute affect how the digest cycle runs? I'll try this and let you know. Commented Oct 11, 2016 at 21:03
  • @DmitriAlgazin Thanks, I'm really trying to avoid extra attributes as I am creating a framework and requiring the user to add extra attributes is not particularly appealing to me. I will give it a try as as an option though. Really hoping I can just detect the DOM update. Commented Oct 11, 2016 at 21:06

1 Answer 1

0

You should simply watch the 'themeName' variable. Then do your calculations in $timeout. $timeout should be required to wait your manual DOM updates. For ex:

scope.$watch('k2Name', function(newValue, oldValue){
    $timeout(function(){
        //do what you want
    });
});
Sign up to request clarification or add additional context in comments.

4 Comments

I've used the practice of $timeout() quite a bit but am still a bit concerned about it (especially if I have to add a time value).. Is the DOM ALWAYS guaranteed to have been fully updated by the time the next digest starts so that the style changes have all been applied? (I need final height)
Angular doesn't track DOM updates that was made by another process. So maybe you should listen to DOM events. But I think that the timeout is acceptable.
thanks. I tried this with no $timeout() value (just wait till next cycle) and it's too soon, the DOM has not yet updated.
Next cycle starts when you change your app state or you wait another automatically change by angular. $timeout is fix for this case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.