Pure Bootstrap sticky footer with flex and Angular

Jose I Santa Cruz G
8 min readFeb 13, 2018

--

Sticky Footer, Five Ways vía https://css-tricks.com/couple-takes-sticky-footer/

Let's say you have the following problem:

  • Angular (Angular, not AngularJS) app, multiple pages (routes, states or however you call them)
  • All pages must have a footer on bottom. Some pages have long content.
  • Layout build using Bootstrap 4

You don't want to use CSS hacks, just plain Bootstrap 4 provided classes. There are many sites with the flex code for a sticky footer (actually only this article with a few tweaks, lead my findings to good results), but before making it work (a task that can take more time than expected) you have to understand a few things. Any way, first things first, weĺl be making this app from zero using Angular CLI.

It will take a while but trust me, I'll get to the footer, in the meantime you will maybe learn a few useful thing (or maybe not…). For the impatient ones, Ctrl+F Flex footer

Ingredients

  • You
  • A computer with a proper operating system (hopefully not MS Windows, but if so, it can work)
  • Angular CLI previously installed.
  • A terminal or command line app. My personal preference goes for Terminator on Linux, iTerm2 on Mac, or ConEmu on Windows
  • An Angular app, with Bootstrap.
  • Some pages to be created, lets say LoginPage, DashboardPage, AboutPage.
  • Footer component, originally called Footer

Creating the App

To create the app, which we will call StickyFooterApp, the pages and the footer component, lets open the terminal and execute the following commands. I'll be using the full sintax for Angular CLI:

/home/dev :~ ng new StickyFooterApp --routing
/home/dev :~ cd StickyFooterApp
/home/dev :~ ng generate component pages/LoginPage
/home/dev :~ ng generate component pages/DashboardPage
/home/dev :~ ng generate component pages/AboutPage
/home/dev :~ ng generate component components/Footer
/home/dev :~ npm install bootstrap --save

As you can see, the pages and footer are all components. Just to avoid mixing things up, I'm placing pages on a pages/ folder and components on a components/ folder (that's why pages and components are on boldface); it helps making this kind of separation when you have lots of pages and components.

When creating the app weŕe going to use the --routing parameter, so Angular CLI creates a route placeholder to place our app's route definitions.
And don't forget to install Bootstrap.

This is how the app' s folder structure should look like

OK, the general app structure is built, but let's make it have a little more sense, so we can style it and get to the footer issue.

Adjusting the routes

We need to do this, otherwise there will be no navigation on our app. So open the app-routing.module.ts file and edit the routes array so it's initialized with the following routes:

const routes: Routes = [
{ path: 'login', component: LoginPageComponent },
{ path: 'dashboard', component: DashboardPageComponent },
{ path: 'about', component: AboutPageComponent },
{ path: '',
redirectTo: '/login',
pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent }
];

Some important thing to notice here:

  • The default empty route, when navigating directly to the app, will be redirected to the login page.
  • Any route not defined here, will be redirected to a Page not found page. I didn't place the instructions to generate this page (pretty easy to figure it out) just to give the copy-pasters some extra work to do.

LoginPage

Copy & paste from the form example at https://getbootstrap.com/docs/4.0/components/forms/

Open the login-page.component.html file and paste:

<form>  <div class="form-group">
<label for="exampleInputEmail1">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email">
<small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>
<div class="form-group">
<label for="exampleInputPassword1">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password">
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary">Submit</button></form>

Lorem Lipsum in the About page

As we need a page with a long content lets head to http://slipsum.com/lite/ and generate some paragraphs and headings. Open the about-page.component.html file and paste (not the full pasting code here):

<!-- start slipsum code --><h2>So, you cold?</h2>
<p>Like you, I used to think the world was this great place where everybody lived by the same standards I did, then some kid with a nail showed me I was living in his world, a world where chaos rules not order, a world where righteousness is not rewarded. [...] </p>
<p>...</p>
<!-- end slipsum code -->

Footer component

Edit the footer.component.html file to look something like this (you can be more creative than me):

<footer>
<p>&copy; All rights reserved for <i>StickyFooterApp</i> 2018</p></footer>

Initially all html files generated by Angular CLI have a "page works" paragraph. that's a generic placeholder just to know that everything is supposed to be working correctly; be sure to replace it with something more useful than that.

As you can see I have not placed any Bootsrap styles yet.

Editing the pages container

All Angular app pages are contained within two files:

  • index.html which has the <app-root></app-root> tag where the app is going to be displayed
  • app.component.html which has the <router-outlet></router-outlet> placeholder, underneath the which all routes are going to be loaded/inserted.

Without any Bootstrap styles and without any editing, the login page (default starting page) looks like this:

Look at the form here, ugly and un-styled

First we need to add Bootstrap. To do so, open the file .angular-cli.json (it isn't a typo, the file DOES start with a dot), search for the "styles" array, and add as the first item a reference for the bootstrap.min.css file :

"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"styles.css"
],

After this edition and restarting the ng serve command the app look like this:

You sure have noticed the visible changes, just add Bootstrap, water and voila…

As this is the first edit, there are no margins nor paddings, so our attention has to go to the following files:

  • index.html so we can define a container (with margins and paddings). This is achieved just adding the container class to the body tag. Add an additional pt-3 class for extra top padding.
  • app.component.html so we can delete all "extra and un-needed" html code (Welcome to app, image and link list). Go ahead, delete that code; if you have the ng serve command running you should see this as live changes.

The result:

Nice looking Bootstrap form here

Adding the footer

The Footer component is supposed to go at the bottom of every page. So we'll add the footer code at the end of the app.component.html file. Remember we just deleted the extra html generated by Angular CLI:

<router-outlet></router-outlet>
<app-footer></app-footer>

And it looks like:

Whoooa! there's a footer here.

Adding some navigation for the app

Our app has several pages, so we need to navigate them in order to see the footer's behavior on small content and large scrolling content pages. Ir order to achieve this we will make these changes:

  • LoginPage: Submit button will navigate to the DashboardPage
  • DashboardPage: Will present a side menu with a link to AboutPage and LogoutLoginPage
  • AboutPage: Will present the same side menu as the DashboardPage.
    Note to self: As the side menu is included on most pages, it hould be a new component.
DashboardPage with side menu and footer
<!-- Dashboard page -->
<h2>Dashboards</h2>
<div class="row">
<div class="col-4">
<div class="list-group">
<a routerLink="/dashboard" routerLinkActive="active" class="list-group-item list-group-item-action">Dashboards</a>
<a routerLink="/about" routerLinkActive="active" class="list-group-item list-group-item-action">About</a>
<a routerLink="/login" routerLinkActive="active" class="list-group-item list-group-item-action">Logout</a>
</div>
</div>
<div class="col-8"> <div class="card-deck">
<div class="card">
<img class="card-img-top mx-auto w-50" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">This is a longer card with supporting text below as a natural lead-in to additional content. This content is a little bit longer.</p>
<p class="card-text"><small class="text-muted">Last updated 3 mins ago</small></p>
</div>
</div>
<!-- search for the card deck example in getbootstrap.com
and copy two cards -->
</div> </div>
</div>
About page with long scrolling text, side menu and wrong title (see what copy & pasting does?)

Navigation from theside menu is as simple as using routerLinks.
The Submit button on the LoginPage requires some extra code.

<!-- Login page -->
<h2>Login</h2>
<form (submit)="doLogin()">
[additional code not included due it's not modified]

and on the login-page.component.ts file you'll have to add the doLogin function:

/**
* Validates user and password (not really)
* Actually it just navigates to the DashboardPage
*/
doLogin() {
console.log('doLogin()');
this.router.navigate(['dashboard']);
}

About the footer styles

At this point the reader perhaps hates me, because there's no code related to the sticky footer using plain Bootstrap styles. Trust me, I'm getting there, just took the long way.

Most sites recommend the following or similar:

.Site {
display: flex;
min-height: 100vh;
flex-direction: column;
}

.Site-content {
flex: 1;
}

(original code from https://philipwalton.github.io/solved-by-flexbox/demos/sticky-footer/ )

.Site would be applied over the body and .Site-content over the site's content (Doh!), the footer requires no styles at all. Sorry pals but this doesn't work. I guess real life requirements have more complex layouts.
Nevertheless, this is partially correct, the body tag has to be at less the viewport's height, otherwise the footer won't be able to be placed at the window's bottom (without padding, margin or absolute positioning hacks).

The highlighted zone is the html tag's height. The body tag has the same height as the html tag.
html, body { height: 100%; }

Read https://stackoverflow.com/questions/27612931/styling-html-and-body-selector-to-height-100-vs-using-100vh for more on the differences on 100% vs 100vh. Just to be sure if styling inline wrappers classes , use 100vh.
When styling both html and body, height: 100% and height: 100vh have the same effect → 100% height.

Then whatever contains all your app's components has to be also 100% tall, and as the first link I placed and Bootstrap documentation and examples about auto margin show, the footer component MUST HAVE margin-top: auto

Flex footer (finally)

Please get bored and read all the above code and explanations (at least the About the footer styles section).

Footer on the bottom

You just have to edit 2 files:
index.html:

<!doctype html>
<!-- Full height html -->
<html lang="en" class="h-100">
<head>
<meta charset="utf-8">
<title>StickyFooterApp</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<!-- Full height body -->
<body class="container pt-3 h-100">
<!-- Full height app container, and also use flexbox columns -->
<app-root class="d-flex flex-column h-100"></app-root>
</body></html>

app.component.html:

<router-outlet></router-outlet>
<!-- Footer top margin must be set on auto -->
<app-footer class="mt-auto"></app-footer>

And that's it, no extra styles on your styles files, just plain Bootstrap magic. Clearly not this year's coding discovery. Hope you enjoyed and learned something new.

Full example at: https://github.com/jsanta/StickyFooterApp

--

--

Jose I Santa Cruz G

Polyglot senior software engineer, amateur guitar & bass player, geek, husband and dog father. Not precisely in that order.