Pure Bootstrap sticky footer with flex and Angular
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.
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>© 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:
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:
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:
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:
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 Logout → LoginPage
- 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.
<!-- 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>
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).
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).
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