Angular Material fixed header and persistent footer

After struggling with this issue for quite some time, I think I may have come up with a solution. My goal is to have a fixed toolbar (navbar) along with a sticky footer that floats at the bottom of the main section. The challenge lies in making sure the footer remains sticky when there is no content present. It seems like I can either achieve a fixed toolbar or a sticky footer, but not both simultaneously. In my current setup, the toolbar remains fixed while the footer doesn't stick to the bottom, ultimately overlapping with the toolbar when the main section is empty.

<body ng-controller="MainCtrl" layout="row">

  <div layout="column" flex>
    <md-toolbar class="md-medium-tall">
        <div class="md-toolbar-tools">
            <span>HEADER</span>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleContent(!displayContent)">onOff</md-button>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleNum()">half/full</md-button>
        </div>
    </md-toolbar>


    <md-content>
        <div layout="column" flex>
            <div ng-if="displayContent" style="background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim">body {{card.title}}</div>
            <div style="background-color: red;" flex></div>
            <div style="background-color:orange;color:white;" >footer item</div>
        </div>  
    </md-content>           
  </div>    
</body>

The code above creates a sticky footer, but unfortunately, it also causes the toolbar to scroll along with the page content.

<body ng-controller="MainCtrl" layout="row">

  <div layout="column" flex>
    <md-toolbar class="md-medium-tall">
        <div class="md-toolbar-tools">
            <span>HEADER</span>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleContent(!displayContent)">onOff</md-button>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleNum()">half/full</md-button>
        </div>
    </md-toolbar>

    <div layout="column" flex>
      <div ng-if="displayContent" style="background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim">body {{card.title}}</div>
      <div style="background-color: red;" flex></div>
      <div style="background-color:orange;color:white;" >footer item</div>
    </div>  
  </div>    
</body>

Although the flexible approach taken here seems fitting for achieving my desired layout, there's still room for improvement. Despite exploring a more traditional method involving the calculation of the main section height using calc(100vh - header - footer), angular-material's changing toolbar size based on viewport dimensions threw me off track. Considering submitting a change request to be able to use a gap-filling <div flex></div> within the md-content section, but eager to explore better solutions before proceeding.

Answer №1

After some troubleshooting, I was able to identify the root cause of the issue. It turns out that when nesting divs under the main content section of md-content, there was a specific problem on Safari. To resolve this, I simply added flex="none" to the top-level div.

The solution worked seamlessly on Chrome:

<md-content layout="column" flex>
 <div flex layout="column">
  <section>
    <div ng-if="displayContent" style="min-height:20px;background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim track by $index">{{card.title}}
    </div>
  </section>
  <div flex></div>
  <footer flex="none" style="background-color:orange;color:white;">
    <div>footer item</div>
  </footer>
 </div>
</md-content>

The code now functions properly on both Chrome and Safari:

<md-content layout="column" flex>
 <div flex layout="column">
  <section flex="none">
    <div ng-if="displayContent" style="min-height:20px;background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim track by $index">{{card.title}}
    </div>
  </section>
  <div flex></div>
  <footer flex="none" style="background-color:orange;color:white;">
    <div>footer item</div>
  </footer>
 </div>
</md-content>

Answer №2

For scrolling, utilize md-content as the scroll wrapper. Place your main content inside with flex, and place the footer with flex="none". This arrangement ensures that the footer will always stick to the bottom of the md-content container thanks to the CSS property overflow: auto. Learn more about this layout approach at angular-material layout children

  <md-toolbar class="md-medium-tall">
    <div class="md-toolbar-tools">
      <span>HEADER</span>
      <span flex></span>
      <md-button class="md-raised" ng-click="toggleContent(!displayContent)">onOff</md-button>
      <span flex></span>
      <md-button class="md-raised" ng-click="toggleNum()">half/full</md-button>
    </div>
  </md-toolbar>

  <md-content layout="column" flex>
    <div flex layout="column">
      <div ng-if="displayContent" style="background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim">body {{card.title}}</div>
    </div>
    <footer flex="none" style="background-color:orange;color:white;">
      <div>footer item</div>
    </footer>
  </md-content> 

codepen

Answer №3

Here is a code snippet that might be helpful:

angular
  .module('myApp', ['ngMaterial'])
  .controller('MainCtrl', function($scope) {
    console.log('MainCtrl');
    $scope.cards = [{
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }, {
      text: 'Bla bla bla bla bla bla bla ',
      title: 'Bla'
    }];
    $scope.displayContent = true;
    $scope.displayLim = 100;
    $scope.toggleContent = function(showContent) {
      $scope.displayContent = showContent;
    };
  });
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular-animate.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular-aria.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angular_material/0.8.3/angular-material.min.js"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/angular_material/0.8.3/angular-material.min.css">
</head>
<body ng-app="myApp" ng-controller="MainCtrl" layout="row">

  <div layout="column" flex>
    
    <md-toolbar class="md-medium-tall">
        <div class="md-toolbar-tools">
            <span>HEADER</span>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleContent(!displayContent)">onOff</md-button>
            <span flex></span>
            <md-button class="md-raised" ng-click="toggleNum()">half/full</md-button>
        </div>
    </md-toolbar>
    
    <md-content layout="row" flex>
        <div layout="column" flex>
            <div ng-if="displayContent" style="background-color:SteelBlue;color:white;" ng-repeat="card in cards|limitTo: displayLim">body {{card.title}}</div>
            <div style="background-color: red;" flex></div>
        </div>  
    </md-content>
    
    <div layout="row" class="footer" layout-align="center center">
      <h2>My Footer</h2>
    </div>
    
  </div>    
</body>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering a CORS header issue while working with the Authorization header

Here is the code snippet I am currently working with: https://i.stack.imgur.com/DYnny.png Removing the Authorization header from the headers results in a successful request and response. However, including the Authorization header leads to an error. http ...

How to interact with AngularJS drop-down menus using Selenium in Python?

I have been working on scraping a website to create an account. Here is the specific URL: Upon visiting the site, you need to click on "Dont have an account yet?" and then click "Agree" on the following page. Subsequently, there are security questions th ...