DEV Community

Cover image for Tutorial: Styling Angular CLI Apps with Bootstrap
beeman 🐝
beeman 🐝

Posted on

Tutorial: Styling Angular CLI Apps with Bootstrap

Example of the pages of the final app

In this tutorial, you learn how to use Bootstrap to style Angular CLI Apps.

The goal is to build an application layout with a header and footer, a home page, about page, and a login page with form.

Check the live demo here or the GitHub repo.

Requirements

Make sure you have the following tools installed:

  • Node.js and NPM, visit the homepage for installation instructions.
    • Run node -v to verify you have version 12 or higher.
    • Run npm -v to verify you have version 6 or higher.
  • Angular CLI (npm install -g @angular/cli@latest to install)
    • Run ng --version to verify you have version 10 or higher.

If you prefer using yarn, first configure Angular CLI's default package manager. This makes sure the generated application has a yarn.lock file instead of a package-lock.json.

1. Create a new application

Open a terminal and run the following command:

ng new sandbox --routing --style scss --strict

The ng new command generates a basic Angular application in a directory called sandbox and installs the dependencies.

The --routing flag instructs Angular CLI to generate a routing module.
The --style scss parameter sets SCSS as the style preprocessor.
The --strict flag configures the application to run in strict mode.

At the end of the setup, the Angular CLI also initializes a git repository and does an initial commit.

2. Start the application in development mode

After the installation is finished, run the following command to navigate to the project directory.

cd sandbox

In the project directory you can start the development server using ng serve:

ng serve

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **

Navigate to the link displayed by the Dev server and verify that it works. The app is now ready to get some style(s)! 😎

3. Install Bootstrap

Run the following command in your project directory to install Bootstrap:

npm install bootstrap

When the installation is finished we now can tell Angular CLI to use these styles.

Open src/styles.scss and add the following lines:

@import '~bootstrap/scss/bootstrap';
// Set the full page height so we can stick the footer to the bottom
html,
body {
  height: 100%;
}

Next, open src/app/app.component.html and delete all the content, replace it with the following snippet:

<h1 class="text-center text-primary">Hello Bootstrap!</h1>

When you go back to your browser, you should see Hello Bootstrap in big blue letters! 🎉

Let's move on to make our app look a little better!

4. Configure the application layout

The basic application layout

In this step we create a UiModule and add three components to it: LayoutComponent, HeaderComponent and FooterComponent.

💡 It's a good idea to keep the UI separate from the rest of the app. This 'separation of concerns' also allows you to easily re-use the UI in other projects.

4.1 Create the UiModule

Run the following command to generate the UiModule.

ng generate module ui --module app

The --module parameter imports the UiModule in our main AppModule:

Next, generate the 3 components inside of this new module:

ng generate component ui/layout
ng generate component ui/header
ng generate component ui/footer

💡 The ng generate command supports shortcuts: use ng g c to generate a component, ng g m to generate a module, etc.

4.2 Implement the LayoutComponent

Open src/app/ui/layout/layout.component.html replace the content with the following snippet:

<!-- This flex container takes the full height -->
<div class="d-flex flex-column h-100">
  <app-header></app-header>
  <!-- The main area does not shrink, 'pushing down' the footer -->
  <main class="flex-shrink-0">
    <!-- This will render the routes -->
    <router-outlet></router-outlet>
  </main>
  <!-- This keeps the footer down if the main content does not fill up the space -->
  <footer class="mt-auto">
    <app-footer></app-footer>
  </footer>
</div>

We use this LayoutComponent in the router, and render the children in the location of router-outlet.

Before moving on, make sure to import RouterModule in UiModule.

Open src/app/ui/ui.module.ts and add the following code next to the other imports:

import { RouterModule } from '@angular/router';

Add RouterModule to the imports array:

@NgModule({
  declarations: [LayoutComponent, HeaderComponent, FooterComponent],
  imports: [CommonModule, RouterModule],
})

💡 If you forget to import the RouterModule, the server will tell you:

ERROR in src/app/ui/layout/layout.component.html:3:3 - error NG8001: 'router-outlet' is not a known element:
  1.  If 'router-outlet' is an Angular component, then verify that it is part of this module.
  2.  If 'router-outlet' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.</pre>

4.3 Use the LayoutComponent

Open src/app/app-routing.module.ts and replace the line const routes: Routes = [] with the following snippet:

const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    children: [
      // Here we will add our application pages
    ],
  },
];

Make sure to import LayoutComponent in src/app/app-routing.module.ts:

import { LayoutComponent } from './ui/layout/layout.component';

Open src/app/app.component.html and replace the content with the following snippet:

<router-outlet></router-outlet>

Save all the files and check your browser, you should see the default HeaderComponent and FooterComponent being rendered. Time to spice them up!

4.4 Implement the Header

Open src/app/ui/header/header.component.html and replace the content with the following snippet:

<!-- You can change the values `dark` here with any of the following: -->
<!-- dark, light, primary, secondary, success, info, danger, warning -->
<nav class="navbar navbar-dark bg-dark">
  <!-- This is the application title with a link to the root -->
  <a class="navbar-brand" routerLinkActive="active" routerLink="/">Angular & Bootstrap</a>
  <!-- This is a spacer so the links move to the end -->
  <div class="mr-auto"></div>
  <!-- This main navigation links are defined here -->
  <div class="navbar-expand">
    <div class="navbar-nav">
      <!-- Each link has the routerLink property set to a different route -->
      <a class="nav-item nav-link" routerLinkActive="active" routerLink="/home">Home</a>
      <a class="nav-item nav-link" routerLinkActive="active" routerLink="/about">About</a>
      <a class="nav-item nav-link" routerLinkActive="active" routerLink="/login">Login</a>
    </div>
  </div>
</nav>

Refer to the bootstrap documentation of the navbar for more details on the syntax of the navbar and how to make it responsive.

4.5 Implement the Footer

Open src/app/ui/footer/footer.component.html and replace the content with this:

<div class="py-3 bg-dark text-center text-muted">
  <small>Copyright &copy; 2020</small>
</div>

5. Add application Pages

With the application layout in place, it's time to add a few pages.

The command we use creates a module with a component and lazy-loads it in the AppModule.

💡 Lazy loading is the recommended way of routing in an Angular app as it makes sure the users don't download any code they won't run.

5.1 Create a Home page

Run the following command to generate the HomeModule:

ng g module pages/home --route home --module app

Open src/app/pages/home/home.component.html and replace the content with this:

<div class="container py-5">
  <div class="jumbotron">
    <h1 class="text-secondary">Home</h1>
  </div>
</div>

Go to your application in the browser and click the Home link in the header.

You will be taken to the route /home with the text 'Home'. However, the layout with the header and footer is gone!

To fix this, open src/app/app-routing.module.ts and move the newly created route inside the children array:

const routes: Routes = [
  {
    path: '',
    component: LayoutComponent,
    children: [
      // Here we will add our application pages
      {
        path: 'home',
        loadChildren: () => import('./pages/home/home.module').then(m => m.HomeModule),
      },
    ],
  },
];

After saving this file, the page should render properly.

5.2 Create an About page

Run the following command to generate the AboutModule:

ng g module pages/about --route about --module app

Open src/app/pages/about/about.component.html and replace the content with this snippet:

<div class="container py-5">
  <div class="jumbotron">
    <h1 class="text-secondary">About</h1>
  </div>
</div>

Open src/app/app-routing.module.ts and move the about route inside the children, so it sits next to the route to the HomeModule.

5.3 Create a Login page

The login page is a bit more complex because it has a form and uses the router to redirect.

Run the following command to generate the LoginModule:

ng g module pages/login --route login --module app

Open src/app/pages/login/login.component.ts and add the following code next to the other imports:

import { FormControl, FormGroup } from '@angular/forms';
import { Router } from '@angular/router';

Change the content of the LoginComponent class to this snippet:

export class LoginComponent implements OnInit {
  // The form group defines the fields used in the form
  form = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  // Inject the router so we can navigate after a successful login
  constructor(private readonly router: Router) {}

  ngOnInit(): void {}

  public submit() {
    // Use the form value to do authentication
    console.log(this.form.value);
    // Navigate after successful login
    return this.router.navigate(['/']);
  }
}

Open src/app/pages/login/login.component.html and replace the content with this snippet:

<!-- This flex container takes the full height and vertically centers the content -->
<div class="d-flex flex-column h-100 justify-content-center">
  <div class="container">
    <div class="row">
      <!-- This is a single column that is responsive -->
      <div class="col-12 col-md-6 offset-md-3">
        <div class="card">
          <div class="card-header">Login</div>
          <div class="card-body">
            <!-- The formGroup 'form' is defined in the component class -->
            <form [formGroup]="form">
              <div class="form-group">
                <label for="email">Email address</label>
                <!-- The formControlName defines the name of the field in the formGroup -->
                <input id="email" formControlName="email" type="email" required class="form-control" />
              </div>
              <div class="form-group">
                <label for="password">Password</label>
                <input id="password" formControlName="password" type="password" required class="form-control" />
              </div>
            </form>
          </div>
          <div class="card-footer">
            <div class="d-flex justify-content-between">
              <a routerLink="/" class="ml-2 btn btn-outline-secondary">
                Home
              </a>
              <!-- The button has a click handler, it will be disabled if the form is not valid -->
              <button (click)="submit()" [disabled]="!form.valid" type="submit" class="btn btn-outline-success">
                Log in
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

💡 The template of the LoginComponent is quite large. When adding more authentication pages, consider creating a separate AuthLayoutComponent and use that like LayoutComponent, then add LoginComponent, RegisterComponent, etc as the children of AuthLayoutComponent.

Go to your application in the browser and click the Login link in the header.

The login page renders the form in the center of the screen, and we don't need to add the route to the children array.

There is one last thing to fix. If you click the Home link in the login page, you will be taken back to the root of the application, which is blank.

Wouldn't it be great if we could go to the Home page instead?

5.4 Redirect the root route

Open src/app/app-routing.module.ts and add the following object on the top of the routes array:

const routes: Routes = [
  {
    path: '',
    // If this path is the 'full' match...
    pathMatch: 'full',
    // ...redirect to this route.
    redirectTo: 'home',
  },
  // The other routes go here
];

Where to go from here?

As stated in the introduction, this app is a starting point and should be fairly straight-forward to enhance it to your liking.

Additional libraries

Use either ng-bootstrap or ngx-bootstrap if you want to use Angular implementations of Bootstrap components like dropdowns, tabs, collapse, etc. Both libraries are great options, pick the one you like best.

If your apps have a lot of forms, use formly for a declarative way of defining your forms, without writing any of the form templates.

Themes and colors

To tweak Bootstrap's appearance, open src/styles.scss and set the variables. Make sure to define the variables before importing Bootstrap:

$dark: hotpink;
$jumbotron-bg: #333;
$secondary: limegreen;
$body-bg: #555;
@import '~bootstrap/scss/bootstrap';

Another great option is Bootswatch which offers more than 20 different Bootstrap based layouts

Conclusion

In this tutorial, you learned how to create a basic Angular application, and use Bootstrap to create a layout with header and footer.

The app has several pages that are lazy loaded. The Login page has a form with basic validation and a redirect back to the Home page.

In case you have any questions, feel free to leave a comment on DEV or send me a message on Twitter!

Big thanks to Jeffrey and Chau for reviewing this post!

Happy coding!

Cheers, beeman 🐝

Latest comments (4)

Collapse
 
rattanakchea profile image
Rattanak Chea

If I want some route to use Bootstrap style and some other route to use not use Bootstrap, or use another css library. Is it possible? Any thoughts?

Collapse
 
yassinrian profile image
YassinRian

an error will occur when compiling the " login.component.html"...to fix this you need to add
import { FormsModule, ReactiveFormsModule} from '@angular/forms'; to the login module

Collapse
 
beeman profile image
beeman 🐝

Thanks, you're totally right - my bad!

I'll update the tutorial today!

Collapse
 
yassinrian profile image
YassinRian

No worries...thanks for the nice writeup!