How to set footer border depending on the height of other elements, in Angular and without jQuery?

As I said in the question, what am I trying to achieve is the equivalent of code like these in Angular:

jQuery

$(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'); } } }); 

When preparing the document, I have to set the upper upper edge of the footer based on calculating the height of the other elements after loading the page content. Thinking in jQuery is easy to do, but in Angular, I just did not find any other way than translating it directly so using the 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 added identifiers to my elements to get them in Angular, but even if it works, it seems to me that this is not a very good way to do this, and it is also difficult to compile it.

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

Can you help me figure out how to approach this better than Angular?

+5
source share
2 answers

More Angular could be using a service and another directive that defines the elements you want to select and sets their height for the service.

Then your original directive will extract the pre-calculated height from the new service.

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

Note the use of directives in markup as set-height="header" , for example, for an element with a header height. After each element registers their height for the service, the return value for heightFactory.getMarginTop() will match 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> 
+2
source

try it

 angular .module('myApp') .directive('FooterMargin', [ function() { function calculateMargins() { // elements var $windowHeight = angular.element(window).height(), $headerHeight = angular.element('#header').height(), $footerHeight = angular.element('#footer').height(), $paginationHeight = angular.element('#pagination').height(), $contentHeight = angular.element('#content').height(); // margin return ($windowHeight - $headerHeight - $footerHeight) - ($contentHeight + $paginationHeight); }; return { restrict: 'A', link: function($scope, element, attrs) { // optional (window resize) angular.element(window).off("resize").on("resize", function(){ // will trigger event to get new margin $scope.$broadcast('content.loaded'); }); // some event $scope.$on('content.loaded', function() { var marginTop = calculateMargins(); if (marginTop > 0) { element.css('margin-top', marginTop); } }); } }; } ]); 

Pay attention to the use of "angular.element". This is a proxy method used as an abstraction for jQLite and jQuery.

I added a window resize event. This is optional, but the idea is that if the window is resized, the footer will be recounted.

Finally,

Most of this CAN be achieved using CSS. I doubt you can make it work. Requesting a DOM for 5 elements is expensive. However, use your discretion.

It is also good practice to define methods outside the LINK method. This, in theory, reduces the memory allocation that the browser makes when directing and "digesting."

+2
source

All Articles