Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can i set the margin of footer based on height of other elements, in Angular and no jQuery?

As i say in the question, what i'm trying to achieve is the equivalent of code like these in Angular only:

jQuery way

$(document).ready(function(){
  var windowHeight = $(window).height();
  var headerHeight = $('header').height();
  var footerHeight = $('footer').height();
  var contentHeight = $('.content').height();
  var paginationHeight = $('.pagination').height();

  var footerMarginTop = (windowHeight - headerHeight - footerHeight) - (contentHeight + paginationHeight);

  $('footer').on('content.loaded', function() {
    if(footerMarginTop > 0) {
      $(this).css('margin-top', footerMarginTop + 'px');
    }
  }
});

On document ready i have to set the margin top of the footer based on the calculation of the height of other elements, after the content of the page is loaded. Thinking in jQuery is easy to do but in Angular only i didn't find other way except translating it directly like this, with a directory

Angular way

angular.module('myApp', []).directive('FooterMargin', ['$window', '$document', function($window, $document){
  return {
    restrict: 'A',
    link: function(scope, element, attrs) {

      var windowHeight = $window.innerHeight;
      var headerHeight = $document[0].getElementById('header').offsetHeight;
      var footerHeight = $document[0].getElementById('footer').offsetHeight;
      var paginationHeight = $document[0].getElementById('pagination').offsetHeight;
      var contentHeight = $document[0].getElementById('content').offsetHeight;

      var footerMarginTop = (windowHeight - headerHeight - footerHeight) - (contentHeight + paginationHeight);

      scope.$on('content.loaded', function(){                          
          if(footerMarginTop > 0) {
            attrs.$set('style', 'margin-top: ' + footerMarginTop + 'px');
          }
        }
      });
    }
  };
}]);

I've added IDs to my elements to retrieve them in Angular, but even if it's working it seems to me that it is not a good way to do this, and it is also hard to unit testing it.

I can't use jQuery in this project and i've used Angular for other directive for events that are easier to get with it (like modals and dropdowns) in a satisfactory way

Can you help me understand how to approach this in a better Angular way?

like image 355
urbando Avatar asked Sep 29 '22 05:09

urbando


1 Answers

A more Angular way could be to use a service and another directive, that defines the elements you want to select and sets their heights to the service.

Your original directive will then retrieve the precalculated height from the new service.

In my example I used a factory heightFactory as a service. I also had to add an ugly timeout to wait for the DOM elements to be rendered correctly (and have a size). I think this is what you wanted to achieve with scope.$on('content.loaded') in your example also.

Note the usage of the directives in the markup as set-height="header" for example, for the element, that has the header height. After each element registred their height to the service, the return value for heightFactory.getMarginTop() will be according to your algorithm.

// this directive will ste the window height and retrieve the margin value from the height service
angular.module('myApp', []).directive('footerMargin', ['$window', 'heightFactory',
  function($window, heightFactory) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {

        heightFactory.setHeight('window', $window.innerHeight);

        // delay only for testing purposes, remove this
        $window.setTimeout(function() {
          attrs.$set('style', 'margin-top: ' + heightFactory.getMarginTop() + 'px');
          console.log(heightFactory.getMarginTop());
        }, 1000);


        scope.$on('content.loaded', function() {
          if (heightFactory.getMarginTop() > 0) {
            attrs.$set('style', 'margin-top: ' + heightFactory.getMarginTop() + 'px');
          }
        });
      }
    };
  }
]);

// this directive is used for each element, whichs size you want need for your calculation
angular.module('myApp').directive('setHeight', ['heightFactory',
  function(heightFactory) {
    return {
      restrict: 'A',
      link: function(scope, element, attrs) {
        element.ready(function() {
          heightFactory.setHeight(attrs['setHeight'], element[0].offsetHeight);
          console.log(attrs['setHeight'] + ' set to ' + element[0].offsetHeight);
        });
      }
    }

  }
]);

// the algorithm for the margin is hardcoded, this service could also just return an object with the heights, and the algorithm would still be in your original directive. It depends on the use case imho.
angular.module('myApp').factory('heightFactory', [
  function() {
    var height = {};

    return {
      setHeight: function(element, value) {
        height[element] = value;
      },
      getMarginTop: function() {
        return (height['window'] - height['header'] - height['footer']) - (height['content'] + height['pagination']);
      }
    };

  }
]);
#header {
  background-color: red;
}
#footer {
  background-color: blue;
}
#pagination {
  background-color: grey;
}
<body ng-app="myApp">
  <header id="header" set-height="header">Header</header>

  <section id="content" set-height="content">
    <p>Some Content</p>
    <p>Some Content</p>
    <p>Some Content</p>
    <p>Some Content</p>
  </section>

  <div id="some element" footer-margin>Some Element</div>
  <section id="pagination" set-height="pagination">
    1 2 3 4 5 6
  </section>
  <footer id="footer" set-height="footer">Footer</footer>

</body>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
like image 74
Rias Avatar answered Oct 05 '22 08:10

Rias