{{announcement.body}}
{{announcement.title}}

How to Use Bootstrap to Build Beautiful Angular Apps

DZone 's Guide to

How to Use Bootstrap to Build Beautiful Angular Apps

Take a look at this detailed tutorial that demonstrates how to use Bootstrap to build an Angular application from start to finish.

· Java Zone ·
Free Resource

Since 2005, I’ve been a proponent of CSS frameworks. I was even the leader of an open-source project, AppFuse, and during that time, we held a design contest (using Mike Stenhouse’s CSS Framework) in order to provide themes for our users.

Over the next few years, a few new frameworks came into my awareness; Blueprint in 2007 and Compass in 2008. None of these frameworks, however, could hold a candle to Bootstrap. When it first came to my attention, it was called Twitter Bootstrap—invented in 2010 by Twitter employees Mark Otto and Jacob Thorton. To quote their words from "Building Twitter Bootstrap", Issue 324 of A List Apart:

Our goal is to provide a refined, well-documented, and extensive library of flexible design components built with HTML, CSS, and JavaScript for others to build and innovate on.

It was in August of 2011 that Bootstrap was released more generally and it swiftly became the most popular GitHub project. Developers everywhere began to use it. 

The following year, Angular JS came onto the playing field and took the world by storm. Version 0.9 first appeared on GitHub in October 2010 and version 1.0 was released on June 14, 2012.

In combination, these frameworks had an amazing run. It’s incredible to think that they’ve lasted this long, especially given that both these projects had massive rewrites!

Table of Contents

  • Isn’t Angular Dead?
  • Angular Still Loves Bootstrap
  • Integrate Bootstrap with Angular 9
  • Secure Angular and Spring Boot with OpenID Connect
  • Use Sass to Customize Bootstrap
  • Make Your Angular App Beautiful with Bootstrap
    • Fix Bootstrap’s Responsive Menu
    • Update the Note List Angular Template
    • Add Validation and Bootstrap to the Note Edit Template
  • Add a Searchable, Sortable, and Pageable Data Table with Angular and Spring Data JPA
    • Add Search by Title with Spring Data JPA
    • Add Sort Functionality with Angular and Bootstrap
    • Add Sorting and Paging in Spring Boot with Spring Data JPA
    • Add Pagination with Angular and Bootstrap
  • Angular with Bootstrap + Spring Boot is Powerful
  • Learn More About Angular and Spring Boot

Watch this tutorial as a screencast.


Isn’t Angular Dead?

I’ve heard many developers say that Angular is dead. As a veteran Java developer, I’ve heard this said about Java many times over the years as well. Yet it continues to thrive. Angular is similar in that it’s become somewhat boring. Some people call boring frameworks "legacy." Others call them "revenue-generating."

Whatever you want to call it, Angular is far from dead. At least according to the popularity of two recent articles we’ve published! Many of you have flocked to our recent Angular posts—the proof is in the traffic numbers.

Angular Still Loves Bootstrap

You might think that Angular Material is more popular than Bootstrap these days. You might be right, but I believe that who you follow on Twitter shapes your popularity perspective. Most of the fabulous folks that follow me still use Bootstrap.

Twitter survey

Integrate Bootstrap with Angular 9

Today I’d like to show you how to integrate Bootstrap into an Angular 9 application. I’ll start with the CRUD example from my aforementioned Angular 9 + Spring Boot 2.2 tutorial. I’ll integrate Bootstrap, convert the app to use Sass (because CSS is more fun with Sass), make the app look good, add form validation, and write some code to develop a searchable, sortable, and pageable data table. The last part sounds hard, but it only requires < 10 lines of code on the Spring Boot side of things. Kotlin and Spring Data JPA FTW!

Prerequisites:

Clone the previous application into an okta-angular-bootstrap-example directory.

Shell
 




x
2


1
git clone https://github.com/oktadeveloper/okta-spring-boot-2-angular-9-example.git \
2
 okta-angular-bootstrap-example


In a terminal, navigate into this new directory and its notes folder. Then install the dependencies for the Angular app.

Shell
 




xxxxxxxxxx
1


1
cd okta-angular-bootstrap-example/notes
2
npm i


Add Bootstrap and NG Bootstrap:

Shell
 




xxxxxxxxxx
1


 
1
npm i bootstrap@4.4.1 @ng-bootstrap/ng-bootstrap@6.0.0



You can leave off the version numbers if you like. However, this tutorial is only guaranteed to work with the versions specified. 

Import NgbModule in app.module.ts:

src/app/app.module.ts

Java
 




x
11


 
1
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
2
 
          
3
@NgModule({
4
  ...
5
  imports: [
6
    ...
7
    NgbModule
8
  ],
9
  ...
10
})
11
export class AppModule { }


If you run ng serve -o, you’ll get a blank screen. Look in your browser’s developer console, and you’ll see why.

Uncaught Error: It looks like your application or one of its dependencies is using i18n.
Angular 9 introduced a global `$localize()` function that needs to be loaded.
Please run `ng add @angular/localize` from the Angular CLI.

Cancel the process and run ng add @angular/localize to fix this error. Now, if you restart your app, you’ll see it’s pretty simple. And kinda ugly.

App output

Let’s fix that!

Modify styles.css to add a reference to Bootstrap’s CSS file:

src/styles.css

CSS
 




xxxxxxxxxx
1


1
@import "~bootstrap/dist/css/bootstrap.css";


And change app.component.html to use Bootstrap classes.

src/app/app.component.html

HTML
 




xxxxxxxxxx
1


 
1
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
2
  <a class="navbar-brand text-light" href="#">{{ title }} app is running!</a>
3
</nav>
4
 
          
5
<div class="container-fluid pt-3">
6
  <router-outlet></router-outlet>
7
</div>


Now we’re getting somewhere!

Output

To make the login button work, you need to configure both apps' security configurations.

Secure Angular and Spring Boot with OpenID Connect

Using your Okta developer account (you created one, right?), log in to your dashboard, and register your Spring Boot app:

  1. Navigate to Applications > Add Application

  2. Select Web and click Next

  3. Give the application a name and add http://localhost:8080/login/oauth2/code/okta as a login redirect URI

  4. Click Done

Create an okta.env file in the notes-api directory and copy your settings into it.

Shell
 




xxxxxxxxxx
1


1
export OKTA_OAUTH2_ISSUER=https://{yourOktaDomain}/oauth2/default
2
export OKTA_OAUTH2_CLIENT_ID={yourClientId}
3
export OKTA_OAUTH2_CLIENT_SECRET={yourClientSecret}


Start your Spring Boot app by navigating to the notes-api directory, sourcing this file, and starting your app.

Shell
 




xxxxxxxxxx
1


 
1
cd notes-api
2
source okta.env
3
./gradlew bootRun


Then, create a new OIDC app for Angular:

  1. Navigate to Applications > Add Application

  2. Select Single-Page App and click Next

  3. Give the application a name and set the login redirect URI to http://localhost:4200/callback

  4. Click Done

Modify auth-routing.module.ts to use your Okta domain and client ID.

notes/src/app/auth-routing.module.ts

TypeScript
 




xxxxxxxxxx
1


 
1
const oktaConfig = {
2
  issuer: 'https://{yourOktaDomain}/oauth2/default',
3
  redirectUri: window.location.origin + '/callback',
4
  clientId: '{yourClientId}',
5
  pkce: true
6
};


Now you should be able to log in with your Okta credentials and create notes to your heart’s content!


Create notes

You’ll notice there’s some styling, but things aren’t quite beautiful. Yet…

Commit your progress to Git from the okta-angular-bootstrap-example directory.

Shell
 




xxxxxxxxxx
1


 
1
git commit -am "Add Bootstrap and configure OIDC"


Use Sass to Customize Bootstrap

Before you make things awesome, I’d like to show you how to convert from using CSS with Angular to using Sass. Why? Because Sass is completely compatible with CSS, and it makes CSS more like programming. It also allows you to customize Bootstrap by overriding its variables.


If you’re not into Sass, you can skip this section. Everything will still work without it.

First, run the following command in the notes directory to convert the Angular project to use Sass.

Shell
 




xxxxxxxxxx
1


 
1
ng config schematics.@schematics/angular:component.styleext scss


If you run the following find command:

Shell
 




xxxxxxxxxx
1


 
1
find . -name "*.css" -not -path "./node_modules/*"


You’ll see three files have a .css extension.

Shell
 




xxxxxxxxxx
1


 
1
./src/app/home/home.component.css
2
./src/app/app.component.css
3
./src/styles.css


You can manually rename these to have a .scss extension, or do it programmatically.

Shell
 




xxxxxxxxxx
1


 
1
find . -name "*.css" -not -path "./node_modules/*" | rename -v "s/css/scss/g"


I had to brew install rename on my Mac for this command to work.

Then, replace all references to .css files.

Shell
 




xxxxxxxxxx
1


 
1
find ./src/app -type f -exec sed -i '' -e  's/.css/.scss/g' {} \;


Modify angular.json to reference src/styles.scss (in the build and test sections).

JSON
 




xxxxxxxxxx
1


 
1
"styles": [
2
  "src/styles.scss"
3
],


And change styles.scss to import Bootstrap’s Sass.

src/styles.scss

CSS
 




xxxxxxxxxx
1


 
1
@import "~bootstrap/scss/bootstrap.scss";


To demonstrate how you can override Bootstrap’s variables, create a src/_variables.scss and override the colors. You can see the default variables in Bootstrap’s GitHub repo.

Sass
 




xxxxxxxxxx
1


 
1
$primary: orange;
2
$secondary: blue;
3
$light: lighten($primary, 20%);
4
$dark: darken($secondary, 10%);


Then import this file at the top of src/styles.scss:

Sass
 




xxxxxxxxxx
1


 
1
@import "variables";
2
@import "~bootstrap/scss/bootstrap.scss";


You’ll see the colors change after these updates.

Color changes

Comment out (or remove) the variables in _variables.scss to revert to Bootstrap’s default colors.

Make Your Angular App Beautiful with Bootstrap

You can see from the screenshots above that angular-crud generates screens with some styling, but it’s not quite right. Let’s start by adding a Navbar in app.component.html. Change its HTML to have a collapsible navbar (for mobile devices), add links to useful sites, and add login/logout buttons. While you’re at it, display a message to the user when they aren’t authenticated.

src/app/app.component.html

HTML
 




xxxxxxxxxx
1
32


 
1
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
2
  <a class="navbar-brand" href="#">
3
    <img src="/assets/images/angular.svg" width="30" height="30" class="d-inline-block align-top" alt="Angular">
4
    {{ title }}
5
  </a>
6
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
7
    <span class="navbar-toggler-icon"></span>
8
  </button>
9
 
          
10
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
11
    <ul class="navbar-nav ml-auto">
12
      <li class="nav-item">
13
        <a class="nav-link" href="#">Home</a>
14
      </li>
15
      <li class="nav-item">
16
        <a class="nav-link" href="https://twitter.com/oktadev">@oktadev</a>
17
      </li>
18
      <li class="nav-item">
19
        <a class="nav-link" href="https://github.com/oktadeveloper/okta-angular-bootstrap-example">GitHub</a>
20
      </li>
21
      <li class="nav-item pl-lg-3">
22
        <button *ngIf="!isAuthenticated" (click)="oktaAuth.loginRedirect()" class="btn btn-outline-primary">Login</button>
23
        <button *ngIf="isAuthenticated" (click)="oktaAuth.logout()" class="btn btn-outline-secondary">Logout</button>
24
      </li>
25
    </ul>
26
  </div>
27
</nav>
28
 
          
29
<div class="container-fluid pt-3">
30
  <a *ngIf="!isAuthenticated">Please log in to manage your notes.</a>
31
  <router-outlet></router-outlet>
32
</div>


Remove the login and logout buttons from home.component.html.

src/app/home/home.component.html

HTML
 




xxxxxxxxxx
1


 
1
<p><a routerLink="/notes" *ngIf="isAuthenticated">View Notes</a></p>


Run ng serve and you’ll be able to see your stylish app at http://localhost:4200.

App login

Fix Bootstrap’s Responsive Menu

If you reduce the width of your browser window, you’ll see the menu collapses to take up less real estate.

Menu collapse

However, if you click on it, the menu doesn’t expand. To fix that, you need to use the ngbCollapse directive from NG Bootstrap. Modify app.component.html to have a click handler on the navbar toggle and add ngbCollapse to the menu.

src/app/app/app.component.html

HTML
 




xxxxxxxxxx
1


 
1
<button (click)="isCollapsed = !isCollapsed" class="navbar-toggler" ...>
2
  ...
3
</button>
4
 
          
5
<div [ngbCollapse]="isCollapsed" class="collapse navbar-collapse" ...>
6
  ...
7
</div>


Then add isCollapsed in app.component.ts and change the title to be capitalized.

src/app/app/app.component.ts

Java
 




xxxxxxxxxx
1


 
1
export class AppComponent implements OnInit {
2
  title = 'Notes';
3
  isAuthenticated: boolean;
4
  isCollapsed = true;
5
 
          
6
  ...
7
 
          
8
}


Now, you’ll be able to toggle the menu!

Menu toggle

Update the Note List Angular Template

Modify the note-list.component.html so the breadcrumb doesn’t float right and all the content is in the same card.

src/app/note/note-list/note-list.component.html

HTML
 




xxxxxxxxxx
1
45


 
1
<ol class="breadcrumb">
2
  <li class="breadcrumb-item"><a routerLink="/">Home</a></li>
3
  <li class="breadcrumb-item active">Notes</li>
4
</ol>
5
<div class="card">
6
  <div class="card-body">
7
    <h2 class="card-title">Notes List</h2>
8
    <div class="card-text">
9
      <form #f="ngForm" class="form-inline">
10
        <div class="form-group">
11
          <label for="title">Title:</label>
12
          <input [(ngModel)]="filter.title" id="title" name="title" class="form-control ml-2 mr-2">
13
        </div>
14
 
          
15
        <button (click)="search()" [disabled]="!f?.valid" class="btn btn-primary">Search</button>
16
        <a [routerLink]="['../notes', 'new' ]" class="btn btn-default">New</a>
17
      </form>
18
    </div>
19
    <div *ngIf="noteList.length > 0">
20
      <div *ngIf="feedback.message" class="alert alert-{{feedback.type}} m-2">{{ feedback.message }}</div>
21
      <div class="table-responsive">
22
        <table class="table table-centered table-hover mb-0" id="datatable">
23
          <thead>
24
          <tr>
25
            <th class="border-top-0" scope="col">Id</th>
26
            <th class="border-top-0" scope="col">Title</th>
27
            <th class="border-top-0" scope="col">Text</th>
28
            <th class="border-top-0" scope="col" style="width:120px"></th>
29
          </tr>
30
          </thead>
31
          <tbody>
32
          <tr *ngFor="let item of noteList" [class.active]="item === selectedNote">
33
            <td>{{item.id}}</td>
34
            <td>{{item.title}}</td>
35
            <td>{{item.text}}</td>
36
            <td style="white-space: nowrap">
37
              <a [routerLink]="['../notes', item.id ]" class="btn btn-secondary">Edit</a>&nbsp;
38
              <button (click)="delete(item)" class="btn btn-danger">Delete</button>
39
            </td>
40
          </tr>
41
          </tbody>
42
        </table>
43
      </div>
44
    </div>
45
</div>


That looks better!

Notes list

Add Validation and Bootstrap to the Note Edit Template

If you click the New button, you’ll see the form needs some work too. Bootstrap has excellent support for stylish forms using its form-group and form-control classes. note-edit.component.html already uses these classes; you just need to rearrange some things to use the proper card-* classes.

src/app/note/note-edit/note-edit.component.html

HTML
 




xxxxxxxxxx
1
31


 
1
<ol class="breadcrumb">
2
  <li class="breadcrumb-item"><a routerLink="/">Home</a></li>
3
  <li class="breadcrumb-item active">Notes</li>
4
</ol>
5
<div class="card">
6
  <div class="card-body">
7
    <h2 class="card-title">Notes Detail</h2>
8
    <div class="card-text">
9
      <div *ngIf="feedback.message" class="alert alert-{{feedback.type}}">{{ feedback.message }}</div>
10
      <form *ngIf="note" #editForm="ngForm" (ngSubmit)="save()">
11
        <div class="form-group">
12
          <label>Id</label>
13
          {{note.id || 'n/a'}}
14
        </div>
15
 
          
16
        <div class="form-group">
17
          <label for="title">Title</label>
18
          <input [(ngModel)]="note.title" id="title" name="title" class="form-control">
19
        </div>
20
 
          
21
        <div class="form-group">
22
          <label for="text">Text</label>
23
          <input [(ngModel)]="note.text" id="text" name="text" class="form-control">
24
        </div>
25
 
          
26
        <button type="submit" class="btn btn-primary" [disabled]="!editForm.form.valid">Save</button>
27
        <button type="button" class="btn btn-secondary ml-2" (click)="cancel()">Cancel</button>
28
      </form>
29
    </div>
30
  </div>
31
</div>


That’s an improvement!


Notes detail

To make the title field required, add a required attribute to its <input> tag, along with a name so it can be referenced in an error message.

HTML
 




xxxxxxxxxx
1


 
1
<div class="form-group">
2
  <label for="title">Title</label>
3
  <input [(ngModel)]="note.title" id="title" name="title" class="form-control" required
4
         #name="ngModel" [ngClass]="{'is-invalid': name.touched && name.invalid,  'is-valid': name.touched && name.valid}">
5
  <div [hidden]="name.valid" style="display: block" class="invalid-feedback">
6
    Title is required
7
  </div>
8
</div>


Now when you add a new note, it’ll let you know that it requires a title.

Title required

If you give it focus and leave, it’ll add a red border around the field.

Red border

Add a Searchable, Sortable, and Pageable Data Table with Angular and Spring Data JPA

At the beginning of this tutorial, I said I’d show you how to develop a searchable, sortable, and pageable data table. NG Bootstrap has a complete example I used to build the section below. The major difference is you’ll be using a real server, not a simulated one. Spring Data JPA has some slick features that make this possible, namely its query methods and paging/sorting.

Add Search by Title with Spring Data JPA

Adding search functionality requires the fewest code modifications. Change the UserController#notes() method in your Spring Boot app to accept a title parameter and return notes with the parameter’s value in their title.

notes-api/src/main/kotlin/com/okta/developer/notes/UserController.kt

Kotlin
 




xxxxxxxxxx
1
10


1
@GetMapping("/user/notes")
2
fun notes(principal: Principal, title: String?): List<Note> {
3
    println("Fetching notes for user: ${principal.name}")
4
    return if (title.isNullOrEmpty()) {
5
        repository.findAllByUser(principal.name)
6
    } else {
7
        println("Searching for title: ${title}")
8
        repository.findAllByUserAndTitleContainingIgnoringCase(principal.name, title)
9
    }
10
}


Add the new repository method to the NotesRepository in DemoApplication.kt.

notes-api/src/main/kotlin/com/okta/developer/notes/DemoController.kt

Kotlin
 




xxxxxxxxxx
1


 
1
@RepositoryRestResource
2
interface NotesRepository : JpaRepository<Note, Long> {
3
    fun findAllByUser(name: String): List<Note>
4
    fun findAllByUserAndTitleContainingIgnoreCase(name: String, term: String): List<Note>
5
}


Restart your server and add a few notes, and you should be able to search for them by title in your Angular app. I love how Spring Data JPA makes this so easy!

Add Sort Functionality with Angular and Bootstrap

To begin, create a sortable.directive.ts.

src/app/note/note-list/sortable.directive.ts

Java
 




xxxxxxxxxx
1
29


1
import { Directive, EventEmitter, Input, Output } from '@angular/core';
2
 
          
3
export type SortDirection = 'asc' | 'desc' | '';
4
const rotate: { [key: string]: SortDirection } = {asc: 'desc', desc: '', '': 'asc'};
5
 
          
6
export interface SortEvent {
7
  column: string;
8
  direction: SortDirection;
9
}
10
 
          
11
@Directive({
12
  selector: 'th[sortable]',
13
  host: {
14
    '[class.asc]': 'direction === "asc"',
15
    '[class.desc]': 'direction === "desc"',
16
    '(click)': 'rotate()'
17
  }
18
})
19
export class SortableHeaderDirective {
20
 
          
21
  @Input() sortable: string;
22
  @Input() direction: SortDirection = '';
23
  @Output() sort = new EventEmitter<SortEvent>();
24
 
          
25
  rotate() {
26
    this.direction = rotate[this.direction];
27
    this.sort.emit({column: this.sortable, direction: this.direction});
28
  }
29
}


Add it as a declaration in note.module.ts.

src/app/note/node.module.ts

Java
 




xxxxxxxxxx
1
10


1
import { SortableHeaderDirective } from './note-list/sortable.directive';
2
 
          
3
@NgModule({
4
  ...
5
  declarations: [
6
    ...
7
    SortableHeaderDirective
8
  ],
9
  ...
10
}


Add a headers variable to note-list.component.ts and an onSort() method.

src/app/note/node.module.ts

Java
 




xxxxxxxxxx
1
23


1
import { Component, OnInit, QueryList, ViewChildren } from '@angular/core';
2
import { SortableHeaderDirective, SortEvent} from './sortable.directive';
3
 
          
4
export class NoteListComponent implements OnInit {
5
  @ViewChildren(SortableHeaderDirective) headers: QueryList<SortableHeaderDirective>;
6
 
          
7
  ...
8
 
          
9
  onSort({column, direction}: SortEvent) {
10
    // reset other headers
11
    this.headers.forEach(header => {
12
      if (header.sortable !== column) {
13
        header.direction = '';
14
      }
15
    });
16
 
          
17
    this.filter.column = column;
18
    this.filter.direction = direction;
19
    this.search();
20
  }
21
 
          
22
  ...
23
}


Update the note-filter.ts to have column and direction properties.

src/app/note/note-filter.ts

Java
 




xxxxxxxxxx
1


1
export class NoteFilter {
2
  title = '';
3
  column: string;
4
  direction: string;
5
}


Modify the find() method in NoteService to pass a sort parameter when appropriate.

src/app/note/note.service.ts

Java
 




xxxxxxxxxx
1
18


 
1
import { map } from 'rxjs/operators';
2
 
          
3
...
4
 
          
5
find(filter: NoteFilter): Observable<Note[]> {
6
  const params: any = {
7
    title: filter.title,
8
    sort: `${filter.column},${filter.direction}`,
9
  };
10
  if (!filter.direction) { delete params.sort; }
11
 
          
12
  const userNotes = 'http://localhost:8080/user/notes';
13
  return this.http.get(userNotes, {params, headers}).pipe(
14
    map((response: any) => {
15
      return response.content;
16
    })
17
  );
18
}


Update note-list.component.html so it uses the sortable directive and calls onSort() when a user clicks it.

src/app/note/note-list/note-list.component.html

HTML
 




xxxxxxxxxx
1


1
<thead>
2
  <tr>
3
    <th class="border-top-0" scope="col">#</th>
4
    <th class="border-top-0" scope="col" sortable="title" (sort)="onSort($event)">Title</th>
5
    <th class="border-top-0" scope="col" sortable="text" (sort)="onSort($event)">Text</th>
6
    <th class="border-top-0" scope="col" style="width:120px"></th>
7
  </tr>
8
</thead>


Add CSS in styles.scss to show a sort indicator when a user sorts a column.

src/styles.scss

CSS
 




xxxxxxxxxx
1
21


1
th[sortable] {
2
  cursor: pointer;
3
  user-select: none;
4
  -webkit-user-select: none;
5
}
6
 
          
7
th[sortable].desc:before, th[sortable].asc:before {
8
  content: '';
9
  display: block;
10
  background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAmxJREFUeAHtmksrRVEUx72fH8CIGQNJkpGUUmakDEiZSJRIZsRQmCkTJRmZmJgQE0kpX0D5DJKJgff7v+ru2u3O3vvc67TOvsdatdrnnP1Y///v7HvvubdbUiIhBISAEBACQkAICAEhIAQ4CXSh2DnyDfmCPEG2Iv9F9MPlM/LHyAecdyMzHYNwR3fdNK/OH9HXl1UCozD24TCvILxizEDWIEzA0FcM8woCgRrJCoS5PIwrANQSMAJX1LEI9bqpQo4JYNFFKRSvIgsxHDVnqZgIkPnNBM0rIGtYk9YOOsqgbgepRCfdbmFtqhFkVEDVPjJp0+Z6e6hRHhqBKgg6ZDCvYBygVmUoEGoh5JTRvIJwhJo1aUOoh4CLPMyvxxi7EWOMgnCGsXXI1GIXlZUYX7ucU+kbR8NW8lh3O7cue0Pk32MKndfUxQFAwxdirk3fHappAnc0oqDPzDfGTBrCfHP04dM4oTV8cxr0SVzH9FF07xD3ib6xCDE+M+aUcVygtWzzbtGX2rPBrEUYfecfQkaFzYi6HjVnGBdtL7epqAlc1+jRdAap74RrnPc4BCijttY2tRcdN0g17w7HqZrXhdJTYAuS3hd8z+vKgK3V1zWPae0mZDMykadBn1hTQBLnZNwVrJpSe/NwEeDsEwCctEOsJTsgxLvCqUl2ACftEGvJDgjxrnBqkh3ASTvEWrIDQrwrnJpkB3DSDrGW7IAQ7wqnJtkBnLRztejXXVu4+mxz/nQ9jR1w5VB86ejLTFcnnDwhzV+F6T+CHZlx6THSjn76eyyBIOPHyDakhBAQAkJACAgBISAEhIAQYCLwC8JxpAmsEGt6AAAAAElFTkSuQmCC') no-repeat;
11
  background-size: 22px;
12
  width: 22px;
13
  height: 22px;
14
  float: left;
15
  margin-left: -22px;
16
}
17
 
          
18
th[sortable].desc:before {
19
  transform: rotate(180deg);
20
  -ms-transform: rotate(180deg);
21
}


Add Sorting and Paging in Spring Boot with Spring Data JPA

On the server, you can use Spring Data’s support for paging and sorting. Add a Pageable argument to UserController#notes() and return a Page instead of a List.

notes-api/src/main/kotlin/com/okta/developer/notes/UserController.kt

Kotlin
 




xxxxxxxxxx
1
29


 
1
package com.okta.developer.notes
2
 
          
3
import org.springframework.data.domain.Page
4
import org.springframework.data.domain.Pageable
5
import org.springframework.security.core.annotation.AuthenticationPrincipal
6
import org.springframework.security.oauth2.core.oidc.user.OidcUser
7
import org.springframework.web.bind.annotation.GetMapping
8
import org.springframework.web.bind.annotation.RestController
9
import java.security.Principal
10
 
          
11
@RestController
12
class UserController(val repository: NotesRepository) {
13
 
          
14
    @GetMapping("/user/notes")
15
    fun notes(principal: Principal, title: String?, pageable: Pageable): Page<Note> {
16
        println("Fetching notes for user: ${principal.name}")
17
        return if (title.isNullOrEmpty()) {
18
            repository.findAllByUser(principal.name, pageable)
19
        } else {
20
            println("Searching for title: ${title}")
21
            repository.findAllByUserAndTitleContainingIgnoreCase(principal.name, title, pageable)
22
        }
23
    }
24
 
          
25
    @GetMapping("/user")
26
    fun user(@AuthenticationPrincipal user: OidcUser): OidcUser {
27
        return user;
28
    }
29
}


Modify NotesRepository to add a Pageable argument to its methods and return a Page.

notes-api/src/main/kotlin/com/okta/developer/notes/DemoApplication.kt

Kotlin
 




xxxxxxxxxx
1
10


1
import org.springframework.data.domain.Page
2
import org.springframework.data.domain.Pageable
3
 
          
4
...
5
 
          
6
@RepositoryRestResource
7
interface NotesRepository : JpaRepository<Note, Long> {
8
    fun findAllByUser(name: String, pageable: Pageable): Page<Note>
9
    fun findAllByUserAndTitleContainingIgnoreCase(name: String, term: String, pageable: Pageable): Page<Note>
10
}


While you’re updating the Spring Boot side of things, modify DataInitializer to create a thousand notes for your user. Make sure to replace <your username> with the email address you use to log in to Okta.

Kotlin
 




xxxxxxxxxx
1
11


1
@Component
2
class DataInitializer(val repository: NotesRepository) : ApplicationRunner {
3
 
          
4
    @Throws(Exception::class)
5
    override fun run(args: ApplicationArguments) {
6
        for (x in 0..1000) {
7
            repository.save(Note(title = "Note ${x}", user = "<your username>"))
8
        }
9
        repository.findAll().forEach { println(it) }
10
    }
11
}


Restart your Spring Boot app to make the data available for searching. Click on the Title column to see sorting in action!Add search feature

Add Pagination with Angular and Bootstrap

The last feature to add is pagination with NG Bootstrap’s <ngb-pagination> component. Begin by adding page and size variables (with default values) to note-filter.ts.

src/app/note/note-filter.ts

Java
 




xxxxxxxxxx
1


1
export class NoteFilter {
2
  title = '';
3
  column: string;
4
  direction: string;
5
  page = 0;
6
  size = 20;
7
}


At the bottom of note-list.component.html (just after </table>), add the pagination component, along with a page-size selector.

src/app/note/note-list/note-list.component.html

HTML
 




xxxxxxxxxx
1
11


 
1
<div class="d-flex justify-content-between p-2">
2
  <ngb-pagination [maxSize]="10"
3
    [collectionSize]="total$ | async" [(page)]="filter.page" [pageSize]="filter.size" (pageChange)="onPageChange(filter.page)">
4
  </ngb-pagination>
5
 
          
6
  <select class="custom-select" style="width: auto" name="pageSize" [(ngModel)]="filter.size" (ngModelChange)="onChange(filter.size)">
7
    <option [ngValue]="10">10 items per page</option>
8
    <option [ngValue]="20">20 items per page</option>
9
    <option [ngValue]="100">100 items per page</option>
10
  </select>
11
</div>


Add NgbModule as an import to note.module.ts.

src/app/note/note.module.ts

Java
 




xxxxxxxxxx
1


1
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
2
 
          
3
@NgModule({
4
  imports: [
5
    ...
6
    NgbModule
7
  ],
8
  ...
9
}


In note-list.component.ts, add a total$ observable and set it from the search() method. Then add an onPageChange() method and an onChange() method, and modify onSort() to set the page to 0.

src/app/note/note-list/note-list.component.ts

Java
 




xxxxxxxxxx
1
38


1
import { Observable } from 'rxjs';
2
 
          
3
export class NoteListComponent implements OnInit {
4
  total$: Observable<number>;
5
 
          
6
  ...
7
 
          
8
  search(): void {
9
    this.noteService.load(this.filter);
10
    this.total$ = this.noteService.size$;
11
  }
12
 
          
13
  onChange(pageSize: number) {
14
    this.filter.size = pageSize;
15
    this.filter.page = 0;
16
    this.search();
17
  }
18
 
          
19
  onPageChange(page: number) {
20
    this.filter.page = page - 1;
21
    this.search();
22
    this.filter.page = page;
23
  }
24
 
          
25
  onSort({column, direction}: SortEvent) {
26
    // reset other headers
27
    this.headers.forEach(header => {
28
      if (header.sortable !== column) {
29
        header.direction = '';
30
      }
31
    });
32
 
          
33
    this.filter.column = column;
34
    this.filter.direction = direction;
35
    this.filter.page = 0;
36
    this.search();
37
  }
38
}


Then update notes.service.ts to add a size$ observable and parameters for the page size and page number.

src/app/note/note.service.ts


Java
 




xxxxxxxxxx
1
30


 
1
import { BehaviorSubject, Observable } from 'rxjs';
2
 
          
3
...
4
 
          
5
export class NoteService {
6
  ...
7
  size$ = new BehaviorSubject<number>(0);
8
 
          
9
  ...
10
 
          
11
  find(filter: NoteFilter): Observable<Note[]> {
12
    const params: any = {
13
      title: filter.title,
14
      sort: `${filter.column},${filter.direction}`,
15
      size: filter.size,
16
      page: filter.page
17
    };
18
    if (!filter.direction) { delete params.sort; }
19
 
          
20
    const userNotes = 'http://localhost:8080/user/notes';
21
    return this.http.get(userNotes, {params, headers}).pipe(
22
      map((response: any) => {
23
        this.size$.next(response.totalElements);
24
        return response.content;
25
      })
26
    );
27
  }
28
 
          
29
  ...
30
}


Now your note list should have a working pagination feature at the bottom. Pretty slick, eh?

Adding pagination

Angular with Bootstrap + Spring Boot is Powerful

Phew! That was a lot of code. I hope this tutorial has helped you see how powerful Angular and Spring Boot with Bootstrap can be! You can find all of the source code for this example on GitHub.


I fixed the Angular tests so they work with Bootstrap in this commit.

I also wanted to let you know you can get a lot of this functionality for free with JHipster. It even has Kotlin support. You can generate a Notes CRUD app that uses Angular, Bootstrap, Spring Boot, and Kotlin in just three steps.

  1. Install JHipster and KHipster

    Shell
     




    xxxxxxxxxx
    1


     
    1
    npm install -g generator-jhipster@6.6.0 generator-jhipster-kotlin@1.4.0


  2. Create an easy-notes directory and a notes.jdl file in it

    Java
     




    xxxxxxxxxx
    1
    18


     
    1
    application {
    2
      config {
    3
        baseName notes
    4
        authenticationType oauth2
    5
        buildTool gradle
    6
        searchEngine elasticsearch
    7
        testFrameworks [protractor]
    8
      }
    9
      entities *
    10
    }
    11
    entity Note {
    12
      title String required
    13
      text TextBlob
    14
    }
    15
    relationship ManyToOne {
    16
      Note{user(login)} to User
    17
    }
    18
    paginate Note with pagination


  3. In a terminal, navigate to the easy-notes directory and run khipster import-jdl notes.jdl

That’s it! 

Of course, you probably want to see it running. Run the following commands to start Keycloak (as a local OAuth 2.0 server) and Elasticsearch, and launch the app.

Shell
 




xxxxxxxxxx
1


1
docker-compose -f src/main/docker/keycloak.yml up -d
2
docker-compose -f src/main/docker/elasticsearch.yml up -d
3
./gradlew


Then, run npm run e2e in another terminal window to verify everything works. Here’s a screenshot of the app’s Notes form with validation.


Want to make it work with Okta? See JHipster’s security documentation.

Learn More About Angular and Spring Boot

I used the following resources to gather historical information about Angular and Bootstrap.

If you want to learn more about Angular or Spring Boot, I recommend these blog posts:

If you have questions, please leave a comment below. If you liked this tutorial, follow @oktadev on Twitter and subscribe to our YouTube channel.

This article originally published here.

Topics:
angular, application, bootstrap, css, node, web development

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}