Rebuilding an example Bootstrap 4 layout in Angular 8

Build a full layout with ngx-bootstrap in Angular 8 on Stackblitz

By: Ajdin Imsirovic 03 September 2019

This post builds on the ngx-bootstrap 8-part series of articles, in which we saw how to add all the individual ngx-bootstrap components to example Angular 8 apps.

Here is the list of posts for the entire series:

  1. How to prototype Bootstrap 4 layouts with Angular 8, part 1
  2. How to prototype Bootstrap 4 layouts with Angular 8, part 2
  3. Working with ngx-bootstrap tabs in Angular 8
  4. Working with ngx-bootstrap alerts in Angular 8
  5. Working with ngx-bootstrap buttons, carousel, datepicker, and dropdowns in Angular 8
  6. Working with ngx-bootstrap modals, pagination, popover, and progress bars in Angular 8
  7. Working with ngx-bootstrap’s rating, sortable, timepicker, and tooltip components in Angular 8
  8. Working with ngx-bootstrap’s typeahead component

We’ll begin by looking the completed layout on Stackblitz.

Following the component-building recipe

A layout in Angular will usually consist of several components.

The simplest possible recipe for adding a new component to an Angular app is as follows:

  1. Add the component folders and files (the files’ contents comes from the respective ngx-bootstrap component’s official docs)
  2. Import the usage module, and list it in imports. Then import the class component, and list it in declarations.
  3. Use the newly added component’s template as a custom HTML tag inside the parent component (in our case, app.component.html)

A pre-requisite for the above steps to work, is for you to already have added the ngx-bootstrap dependency and Bootstrap 4 styles, as described here.

Thus, our first commit on this project is the barebones Angular app, and the second commit shows the changes we added to install ngx-bootstrap and Bootstrap 4 styles to our project.

Throughout the rest of this post, we’ll be adding components by relying on this component-building recipe.

Our layout’s structure

We’ll rebuild the example Bootstrap 4 layout available here.

If we think about the linked layout, we can conclude that it consists of the following areas:

  • header (with collapsible toggle button)
  • jumbotron
  • repeatable card (with an image, some text, and a couple of buttons)
  • footer

We should split each of these features into their own component, as so the frist step in our recipe will be to add the following files and folders:

app/
├── header/
│   ├── header.component.html
│   └── header.component.ts
├── jumbotron/
│   ├── jumbotron.component.html
│   └── jumbotron.component.ts
├── cards/
│   ├── cards.component.html
│   └── cards.component.ts
├── footer/
│   ├── footer.component.html
│   └── footer.component.ts
├── ...

The contents of these files is almost the same at this point. Here’s the code for the header’s template file (header.component.html):

<div>header works!</div>

Here’s the code for the header’s class file:

1
2
3
4
5
6
7
import { Component } from '@angular/core';

@Component({
  selector: 'header-area',
  templateUrl: './header.component.html'
})
export class HeaderComponent {}

We’ll add the other files in the similar way. Have a look at the commit if you’d like to copy-paste them.

Back in app.component.html file, we’ll import the new components:

1
2
3
4
5
6
<div class="p-5">
  <header-area class="display-4"></header-area>
  <jumbotron-area class="display-4"></jumbotron-area>
  <cards-area class="display-4"></cards-area>
  <footer-area class="display-4"></footer-area>
</div>

Of course, we’re faced with the following error now:

Template parse errors:
'header-area' is not a known element:
1. If 'header-area' is an Angular component, then verify that it is part of this module.
2. If 'header-area' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the 
...

To fix it, we’ll need to import the components’ class files into the app.module.ts file, and also add them to the declarations array, like this:

// ...code skipped for brevity...
import { HeaderComponent } from './headers/header.component';
import { JumbotronComponent } from './jumbotron/jumbotron.component';
import { CardsComponent } from './cards/cards.component';
import { FooterComponent } from './footer/footer.component';
// ...code skipped for brevity...
declarations: [
    AppComponent, 
    HelloComponent,
    HeaderComponent,
    JumbotronComponent,
    CardsComponent,
    FooterComponent
]

After making all these changes, our app now looks like this: Added all the components

Once again, this is our current commit.

Next, we’ll add the jumbotron, since it’s a simple addition to our app.

Adding the jumbotron

We’re adding jumbotron first because it consists only of HTML and CSS. Thus, it’ll be enough to copy-paste the example code from Bootstrap docs.

We’ll paste the code for the first jumbotron example into jumbotron.component.html:

1
2
3
4
5
6
7
<div class="jumbotron">
  <h1 class="display-4">Hello, world!</h1>
  <p class="lead">This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.</p>
  <hr class="my-4">
  <p>It uses utility classes for typography and spacing to space content out within the larger container.</p>
  <a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
</div>

We’ll also remove the display-4 class from all of our custom HTML tags inside app.component.html so that now it looks like this:

1
2
3
4
5
6
<div class="p-5">
  <header-area></header-area>
  <jumbotron-area></jumbotron-area>
  <cards-area></cards-area>
  <footer-area></footer-area>
</div>

Here’s a screenshot of our update at this stage: Added example jumbotron

And here is the commit with this jumbotron update.

Adding header code

We’ll copy the header HTML code straight from the example on Bootstrap docs, and paste it into header.component.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<header>
  <div class="collapse bg-dark" id="navbarHeader">
    <div class="container">
      <div class="row">
        <div class="col-sm-8 col-md-7 py-4">
          <h4 class="text-white">About</h4>
          <p class="text-muted">Add some information about the album below, the author, or any other background context. Make it a few sentences long so folks can pick up some informative tidbits. Then, link them off to some social networking sites or contact information.</p>
        </div>
        <div class="col-sm-4 offset-md-1 py-4">
          <h4 class="text-white">Contact</h4>
          <ul class="list-unstyled">
            <li><a href="#" class="text-white">Follow on Twitter</a></li>
            <li><a href="#" class="text-white">Like on Facebook</a></li>
            <li><a href="#" class="text-white">Email me</a></li>
          </ul>
        </div>
      </div>
    </div>
  </div>
  <div class="navbar navbar-dark bg-dark shadow-sm">
    <div class="container d-flex justify-content-between">
      <a href="#" class="navbar-brand d-flex align-items-center">
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" aria-hidden="true" class="mr-2" viewBox="0 0 24 24" focusable="false"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"></path><circle cx="12" cy="13" r="4"></circle></svg>
        <strong>Album</strong>
      </a>
      <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarHeader" aria-controls="navbarHeader" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
      </button>
    </div>
  </div>
</header>

The commit for this stage is available here

However, our toggle button is not working? Why is that?

It’s because Bootstrap, out-of-the-box, uses jQuery. However, in Angular, using jQuery is frowned upon, because the whole point of Angular is not to update the DOM directly - which jQuery does - but rather let the Angular’s VDOM engine do the heavy lifting.

This also means that we need to somehow make the toggle button work with Angular, without relying on jQuery.

Making the toggle button work

Luckily, here on codingexercises.com, we’ve already covered implementing a toggle on a navbar, using ngx-bootstrap’s collapsible component.

So, we’ll begin by adding the BrowserAnimationsModule and CollapseModule, and listing it inside the imports array in app.module.ts.

// ...code skipped for brevity...

import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { CollapseModule } from 'ngx-bootstrap/collapse';

// ...code skipped for brevity...

  imports: [
    BrowserAnimationsModule,
    CollapseModule.forRoot(),
    ...
  ]

Stackblitz will require us to install the @angular/animations package, so let’s click the INSTALL PACKAGE button.

We also need to use the isCollapsed property inside the header’s class file, as well as add the toggleCollapsed() function:

1
2
3
4
5
6
7
8
9
10
export class HeaderComponent {

  isCollapsed = true;

  toggleCollapsed() {
    this.isCollapsed = !this.isCollapsed;
    console.log(this.isCollapsed);
  }  

}

Now we’ll bind the click event to the toggle button inside the template file, like this:

1
2
3
4
5
6
7
8
9
<button 
    class="navbar-toggler" 
    type="button" 
    data-toggle="collapse" 
    data-target="#navbarHeader" 
    aria-controls="navbarHeader" 
    aria-expanded="false" 
    aria-label="Toggle navigation" 
    (click)="toggleCollapsed()" >

And finally, we’ll bind the isCollapsed property to the collapsing div, like this:

<div class="collapse bg-dark" id="navbarHeader" [collapse]="isCollapsed">

Now we have a working collapsible toggle button in our layout.

Working collapsible toggle button in the header component

Here’s the commit for the working toggle button.

Similar to how we copy-pasted the header, we’ll add the footer’s HTML from the example on Bootstrap docs:

1
2
3
4
5
6
7
8
9
<footer class="text-muted">
  <div class="container">
    <p class="float-right">
      <a href="#">Back to top</a>
    </p>
    <p>Album example is © Bootstrap, but please download and customize it for yourself!</p>
    <p>New to Bootstrap? <a href="https://getbootstrap.com/">Visit the homepage</a> or read our <a href="/docs/4.3/getting-started/introduction/">getting started guide</a>.</p>
  </div>
</footer>

To get the correct styling, we’ll also need to copy the entire custom CSS file from the example layout, available at this link.

The best place to copy this file to is probably styles.scss file, since there might be some CSS code that applies to the entire layout.

This is the copy-pasted CSS code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.jumbotron {
  padding-top: 3rem;
  padding-bottom: 3rem;
  margin-bottom: 0;
  background-color: #fff;
}
@media (min-width: 768px) {
  .jumbotron {
    padding-top: 6rem;
    padding-bottom: 6rem;
  }
}

.jumbotron p:last-child {
  margin-bottom: 0;
}

.jumbotron-heading {
  font-weight: 300;
}

.jumbotron .container {
  max-width: 40rem;
}

footer {
  padding-top: 3rem;
  padding-bottom: 3rem;
}

footer p {
  margin-bottom: .25rem;
}

Now our app looks like this:

Updated the footer and the global styles

At this point, it’s probably a good idea to commit everything, so here’s the latest update.

Adding the card component

Similar to what we did before, we’ll copy-paste the HTML for the cards component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div class="card mb-4 shadow-sm">
  <svg class="bd-placeholder-img card-img-top" width="100%" height="225" xmlns="http://www.w3.org/2000/svg"
    preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: Thumbnail">
    <title>Placeholder</title>
    <rect width="100%" height="100%" fill="#55595c"></rect><text x="50%" y="50%" fill="#eceeef"
      dy=".3em">Thumbnail</text>
  </svg>
  <div class="card-body">
    <p class="card-text">This is a wider card with supporting text below as a natural lead-in to additional content.
      This content
      is a little bit longer.</p>
    <div class="d-flex justify-content-between align-items-center">
      <div class="btn-group">
        <button type="button" class="btn btn-sm btn-outline-secondary">View</button>
        <button type="button" class="btn btn-sm btn-outline-secondary">Edit</button>
      </div>
      <small class="text-muted">9 mins</small>
    </div>
  </div>
</div>

To contain the width of the card so that it doesn’t strech the full width of its parent container, we’ll also add three wrapping divs:

  • the first wrapping div will have a CSS class of container-fluid,
  • the second div will have a CSS class of row, and
  • the third div will have a CSS class of col-md-4

While we’re at it, let’s also remove the padding class, p-5, from the wrapping app.component.html div.

The commit for this stage in our app can be found here.

This is what our app looks like with a single card added:

Added a single card to our layout

Making the cards repeat

One of the benefits of working with a framework like Angular is that is makes it very easy to repeat an element for any number of times.

Here is the code we’ll use:

*ngFor="let card of ','.repeat(12).split(',')"

We’ll add this code to the col-md-4 div, to repeat it 12 times.

It’s that easy!

Our Angular 8 layout is now starting to look a lot like the the example layout from the official Bootstrap documentation.

Next, we’ll fix the jumbotron so it looks exactly like the one in the source layout. We’ll also add a few other minor tweaks so we can call it a day.

As always, here’s the commit at this stage.

Improving the jumbotron and adding other minor fixes

We’ll begin by copy-pasting the code for the jumbotron from the official example layout:

1
2
3
4
5
6
7
8
9
10
11
<section class="jumbotron text-center">
	<div class="container">
		<h1 class="jumbotron-heading">Album example</h1>
		<p class="lead text-muted">Something short and leading about the collection below—its contents, the creator, etc. Make
			it short and sweet, but not too short so folks don’t simply skip over it entirely.</p>
		<p>
			<a href="#" class="btn btn-primary my-2">Main call to action</a>
			<a href="#" class="btn btn-secondary my-2">Secondary action</a>
		</p>
	</div>
</section>

Next, we’ll wrap everything in between the header and the footer component with a main HTML tag. We’ll do it inside app.component.html, like this:

1
2
3
4
5
6
7
8
<div>
  <header-area></header-area>
  <main role="main">
    <jumbotron-area></jumbotron-area>
    <cards-area></cards-area>
  </main>
  <footer-area></footer-area>
</div>

We’ve also moved the image title that reads “Thumbnail”, so that it’s visually centered in the image area.

The full-page view of the re-created layout in Angular 8 is available at this Stackblitz link.

The commit at this point can be found here.

To update the width of the main area, we can simply add the following CSS to styles.css:

main {
  max-width: 1200px;
  margin: 0 auto
}

Also, since the arrays are counted from zero, we’ve updated the repeater to repeat(11) instead of repeat(12).

With this updated commit we wrap up this article.

Feel free to check out my work here: