0% found this document useful (0 votes)
236 views

Course - Ng-Bootstrap Playbook

This document provides an overview of an ng-bootstrap course. It introduces the instructor and describes some of the major topics that will be covered, including incorporating ng-bootstrap into Angular apps, forms components, structural components, informational components, and navigation components. The overview explains that ng-bootstrap provides native Angular implementations of Bootstrap components to enhance apps and improve the user experience.

Uploaded by

shyamVENKAT
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
236 views

Course - Ng-Bootstrap Playbook

This document provides an overview of an ng-bootstrap course. It introduces the instructor and describes some of the major topics that will be covered, including incorporating ng-bootstrap into Angular apps, forms components, structural components, informational components, and navigation components. The overview explains that ng-bootstrap provides native Angular implementations of Bootstrap components to enhance apps and improve the user experience.

Uploaded by

shyamVENKAT
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 49

Course Overview

Hi everyone. My name is Steve Michelotti. Welcome to my course, ng-


bootstrap Playbook. I am a software engineer and technologist, and I work at Microsoft.
If you're an Angular developer, then you'rein the right place. Ng-bootstrap provides a
native Angular implementation for the rich Bootstrap components, which can greatly
enhance your apps and give your users a much better experience.
In this course, I'm going to cover every ng-bootstrap component and show you how you
can easily incorporate them into the applications that you build.
Some of the major topics that we will cover include how to initially incorporate ng-
bootstrap into your Angular apps, forms components like datepickers and
typeaheads, structural components like modal dialogs and tabs, informational
components like tooltips and alerts, and navigation components like paging through
large grids of data.
By the end of this course, you'll be familiar with every single component in the ng-
bootstrap library and know how to work directly with the APIs.
Pre-requisites - Before beginning this course, you should feel comfortable with
Angular and Bootstrap.
I hope you'll join me on this journey to learn how to use ng-bootstrap with the ng-
bootstrap Playbook course, on Pluralsight.

Introduction
Introduction
Hi. This is Steve Michelotti. Welcome to the ng-bootstrap Playbook course
on Pluralsight. Over the last few years, Bootstrap has risen to prominence as the world's
most popular layout and componentlibrary. At the same time, Angular has dominated as
a framework to build single-page applications. Ng-bootstrap brings these two libraries
together in a seamless way, and that is the focus of this course. This will be a
completely code focused course in which we'll build an app from scratch. This is a
preview of the app that we'll be building. It's a simple app to help an individual
track their workouts of biking, running, and rowing. Here's the screen that shows all of
our previous workouts and tracks our progress towards our targets. And here's a quick
look at the data entry screen using ng-bootstrap components. For example, a datepicker
you see right here. Along the way, we'll add many enhancements to our app using ng-
bootstrap. I'll show you how quick and easy it is to get up and running with ng-
bootstrap.
Course Overview
Here's an overview of what you can expect from this course. In this first introductory
module, I'll talk about what ng-bootstrap is. I'll also discuss helpful prerequisites and
briefly show you the app that we'll be building throughout this course.
Then we'll dive immediately into code and I'll walk you through the initial app setup
with Angular, Bootstrap CSS, and a back-end HTTP web API. This is the setup even
before we incorporate ng-bootstrap. 
In the basic forms module, I'll cover all the ng-bootstrap components that
would typically be used when building forms. These include things like buttons,
the datepicker, timepicker, and more.
I'll then go into depth on the various ng-bootstrap components to enable you to structure
and organize the information the way you want. These are things like modal dialogs,
tabs, and accordions.
In the next module, I'll show you how easy it is to use the various informational
components in ng-bootstrap. These are typically things you'd use to give quick guidance
or feedback to the users of your apps. These are things like tooltips, popovers, andalerts.
In the final module, I'll cover a couple of navigational components. This will be
centered on paging through large grids of data.
What Is ng-bootstrap?
The most important question we need to answer first is what is ng-bootstrap? Let's
answer this question first, then dive into the details. In short, ng-bootstrap provides a
native Angularimplementation for Bootstrap components. Let's explore what we mean
by this a little more closely. Bootstrap itself consists of a couple of things. Number one,
it provides base CSS for layout, content, and other display aspects, and number two, a
JavaScript library for rich client-side components. These JavaScript components in
Bootstrap have historically been based on jQuery. So having answered the mechanical
question of what is ng-bootstrap, let's focus on the why. Why would we want ng-
bootstrap? Well, there are a few reasons. First, if you're already building an app with
Angular, you know there's a certain style we use for implementation with data binding
and TypeScript. This is very different from a jQuery-based approach, so it's
quite valuable to have an implementation that is native to the platform. There are
also tremendous performance and efficiency benefits for using native Angular
code. And in general, it will make our applications smaller if we can avoid the jQuery
dependency. Let me show you some concrete examples so we can more fully grasp this.
Here I am on the home page for Bootstrap itself. Let's click into the Documentation.
And as you heard me mention, Bootstrap already provides CSS for layout and other
things. For example, if I click into the Layout, we can see things like the famous
Bootstrap grid system. If I click into Content, we see various CSS for things like
Headings, Lists, Forms, and much more. So those first two things I showed you are
reallyfocused on the CSS that provides the layout and the visual aspects. But then that
second aspect you heard me mention has to do with components. Let's
click Components right here, and the first thingyou'll see is the Components listed
here, and the navigation on the left will look very familiar to you when we look at
the ng-bootstrap components because these are the components that ng-bootstrap
is focused on. For example, let's click into the Modal. As we scroll down, you can
see code examples of the Modal. And right off the bat, you can see this is a very jQuery-
centric implementation, and thiswould not be natural to write this type of code in an
Angular app. But we can also come down here and see live demos, and I can launch the
Modal dialog. This is the type of functionality we'll get with the ng-bootstrap library,
but with a native Angular implementation. Now let's flip over to the ng-bootstrap
website. This is your starting point for ng-bootstrap. Let's click on the Demo button, and
right off the bat you can see the list here of Components on the left side look
very familiar to the Components that we just saw on the Bootstrap site. And we're
going to cover every single one of these Components in this course. For example,
we can click the Modal, and now we see a TypeScript-based code example. Now
this looks like what we expect in an Angular app as opposed to that jQuery example.
Yet when we launch the demo, we get the same functionality. We even have a
datepickercontrol incorporated into this modal, in fact. Another nice thing about ng-
bootstrap is that it's completely open source. So I just clicked the button for the GitHub
site, and all the code is right here. In addition to that, you can click on the Issues tab
right here if you find any issues and you need to log a bug, for example. However, if
you just have a question about the library itself, and not necessarily a bug, then the
Issues tab is not where you should put that. If you're seeking help for ng-bootstrap, then
what's really nice is you can click this link here, Getting Help, this just takes us further
down the page, and notice the link for StackOverflow. There's a link here
for StackOverflow where you can get answers to ng-bootstrap questions. Now that
we're starting to get a solid foundation for these concepts, I just want to show one brief
demo to continue to drive these concepts home. Here I am in an empty VSCode
environment and I'm going to use the integrated terminal in VS Code to run a very
simple npm install command to simply install the baseline bootstrap library. Now I'm
actually not going to use thebaseline Bootstrap library at all in this course, I just want
you to see what comes with it. If I expand bootstrap, specifically the dist folder, we're
going to see that we have two folders, css and js, just like we've been talking about. If I
expand the css one, the only file we'd have to include in a traditional app is bootstrap.
min. css, as we can see right here. When we build our custom app, I'm going to use a
custom Bootstrap theme that you'll see me download from bootswatch. com, which is a
site that provides free Bootstrap templates with more interesting colors than the default.
So I actually won't even use the default CSS that comes with Bootstrap. Then if I
expand the js folder, we'll see bootstrap. min. js, and we absolutely won't use this file
because all the JavaScript functionality will be provided solely by ng-bootstrap. So
even though we're not using the Bootstrap library at all in this course, I want you to see
what's in it so you can understand which parts we are replacing with ng-bootstrap.
Helpful Prerequisites
There are a couple of prerequisites for this course that I strongly recommend you have.
And based on what we've already discussed, it won't be hard for you to guess them. It's
highly recommended thatyou have some familiarity with both Bootstrap and Angular.
This is a course that is focused on how to use the ng-bootstrap library. It's not a
course intended to introduce you to Bootstrap or Angular for the first time.
Fortunately, Pluralsight has you more than covered here. There are already numerous
Angular courses in the Pluralsight library. For Angular, I recommend checking out
the Angular learning path on Pluralsight, which you can see in this link here. It covers a
range of courses from beginner toadvanced. And if we scroll down, you can see
Beginner, Intermediate, Advanced, but Beginner is certainly sufficient for this course.
For Bootstrap, you can simply come into the Pluralsight search box and fill in bootstrap
as your search term, and you'll come up with a ton of courses. And most any of these
courses can give you great familiarity with Bootstrap. And again, a beginner level of
knowledge of Bootstrap is sufficient for this course. One thing to keep in mind is that
although there are lots of Bootstrap courses at Pluralsight, this course uses the
latest version of Bootstrap, which is Bootstrap 4. Therefore, it would also be a good
idea to check out the Bootstrap 4 course on Pluralsight, which you can see right here.
Course Repository
Technology changes incredibly fast these days. That means that library APIs
can change, and as a result, courses like this can easily go out of date. Because of this, I
created this GitHub repository, and I'll make sure to post information containing key
files you need to have, as well as information about whether the course is up to date and
potential workarounds you can use if it's not.
What We Will Be Building
Let's take a quick sneak peek at the app that we'll be building from scratch throughout
this course. Here I have the Workouts screen where I can set performance targets, we
see progress bars where we can track our performance targets. Down here we can see a
grid of various previous workouts. And we can see that we can page through the grid. I
can add a new entry, we have datepicker controls, timepicker controls, rating controls,
an ability to put in distance. We also have other sections of the app we can page
through, images. And every once in a while, I'll use somewhat contrived examples. For
example, on the home screen you have a collapsible panel where it doesn't do anything
useful, but I'll be able to use it to illustrate how the collapsible panel works. As we go
through the course, I'm going to show every single component that the ng-bootstrap
library currently contains. So with that, let's get started. It's time to start building our
app and using the ng-bootstrap library.

Initial App Setup


Overview
In this module, we'll get up and running with ng-bootstrap. For this course, we're going
to create an app from scratch. So before we start utilizing ng-bootstrap components, let's
get our app set up.We're going to start by using the Angular CLI, that is the command
line interface, to create our app. Then we'll add an HTTP back end for some test data
for development purposes. We'll then create anAngular service class that is
responsible for interacting with our HTTP back-end web API. Next, we'll add some
basic functionality into the app, not yet utilizing ng-bootstrap. Our final step will be to
install ng-bootstrap into our app. Once we do that, we'll be ready to start exploring all
the components in the ng-bootstrap library.
Summary
This concludes the module on setting up our initial app functionality. We first used the
Angular CLI to create our app from scratch. You then saw me use the JSON Server
Node library to create a quick andeasy back-end HTTP web API for
development purposes. I then created an Angular service to act as the facade for
that back-end HTTP API. Next, I implemented the basic create, read, update, and
deletefunctionality into our app. First, a basic Home screen, then a Workout screen,
which shows the list of existing workouts, and also the ability to either add a
new workout or modify an existing workout. Asour final step, I simply used npm
install to install ng-bootstrap into our app. Now that we've got a great foundation
in place, the remainder of this course will be exploring every component of the ng-
bootstrap library in depth.
Add Backend
I'm now going to set up an HTTP back-end web API for development purposes. I
really just want something quick and easy for development since the focus on this
course is the front end and not the back end. So I'm going to use the convenient
Node library called JSON Server. To install it, you would run npm install and then
again -g for the global switch, and then simply json-server. Now again, I've already
got this installed so I'm not going to waste time doing it now, but that's the
command you would run. Let me show you how it works. The first thing I'm going to
do is add a new directory called data into my app. And then, I'm going to copy and
paste a file that I already have created into this folder. So here's the folder that I just
created with that mkdir command, I'm going to open this in my File Explorer, and
obviously it's empty. I'm going to paste my file right in here and let's just take a quick
look at that. You can see that it's a JSON file and it's got a workouts section here and a
few workoutsin there whether it's run, or bike, or rowing with dates and distances.
Some pretty basic information. And notice that's the workouts array right there. Let me
show you how this works. So back on mycommand line, let's move into that
data directory that I just created, and let's run this command, json-server, pointing at that
file right here. And you can see it's starting on localhost 3000, and it noticesthat I have 3
different arrays: performanceTargets, workouts, and locations, and it gives it the URL
righthere. And actually, I have two tabs running in my command line. The first
one here, remember, that's the ng serve, that's serving up the Angular app, and this one
right here is serving up our back-end web API. Just to give a quick illustration of this,
I'm going to open this URL in Postman so we can see I havelocalhost:3000/workouts. I
can send this request. This shows me all of my workouts. And you can see I have id of 1
is bike, id of 2 is row, distance of 200, and I can get individual ones as well. So if I
doworkouts/2 and send that request, it's just going to retrieve that individual item right
here. So JSON server along with the simple JSON file, it's not something you'd use in
production, but it's a nice way to get us quickly up and running with a back-end HTTP
web API for development purposes.
Installing ng-bootstrap
Now it's time to install ng-bootstrap, and this is really the easy part. All I'm going to do
is run an npm install command, and we'll specify ng-bootstrap. And that's how you have
to specify it, @ng-bootstrap/ng-bootstrap, and then --save to put it in our package.
json file. That was pretty simple. Let's go into our package. json file just to show that
that got put right there on line 23. And then of course, we need to let out Angular
project know about it. So we'll open up our app. module, and let's put this in our Third
party components section right here. As a matter of fact, while we're in here organizing
this, let's make sure we have some nice separation here. So I'm just going to move this
import right here, and these are the default Angular imports, so we're going to put those
at the top. So Third party imports, and these are our App imports. Now that we have ng-
bootstrap module imported on line 8, all we have to do is in the imports section down
here, we have to let our app know about it. So say NgbModule. forRoot, and that's it.
We've now got ng-bootstrap in our application and we're ready to use it. For the
remainder of this course, we're going to explore every component in the ng-bootstrap
library and how you can use them to really give some great functionality for your app.
Add HTTP Service
Now that I've added my HTTP web API back end, I want to add an Angular service
that will be the facade that consumes that HTTP service. Once again, I'm going to use
the terminal that's embedded into VS Code just for convenience factor, and I'm going
cd into the src\app directory. Now that I'mhere, I'm going to create a new
directory called services. And let's move into that directory. And now that I'm here,
I'm going to use the ng generate command again, and instead of generating acomponent,
I'm going to generate a service. And we're going to call it the WorkoutsApi. And we can
see that that API has been created right here. Now I'm going to add some initial
implementation here,and there's a few things that I want to point out about the snippet
that I just added. The first thing is you're going to see on line 7 we have a baseUrl, and
sure enough it's pointing to the localhost:3000where our back end lives. The other
thing we're going to see is that on line 9 we're getting the red squiggle under
the HttpClient, so let's add an import here on line 2 for the HttpClient to import that in
from the Angular library. Now these other API methods are relatively straightforward.
These are the CRUD methods, CRUD being C-R-U-D, create, read, update, and delete.
So we have getWorkouts and getWorkout, individual, so this one gets all of them and
this one gets just the id. Basically, the exact request you saw me do on Postman. Then
we'll add a workout with http. post. We'll update a workout with an http. put specifying
the specific one, and then we can save the workout either in update or an add. And then
we have delete down there at the bottom on line 36. Now in order for our Angular app
to use this, we have to go into our app. module. ts, and we have to add a couple of
things here. First in the imports section, I'm going to add the HttpClientModule, and by
hitting Tab there, it automatically did the import on line number 10, another nice feature
of VS Code. And then in the providers section,we're going to add that service that
we just created, the WorkoutsApiService. Once again, Tab will automatically import
that on line 11. That's all we need to implement and now our service is ready to go to
consume that HTTP web API back end.
Initial App Functionality
Now that we've got all the required components in place, let's implement our initial
functionality. One thing to note is this initial functionality is just going to be getting
some data and displaying it on the screen. We're not yet going to be using anything in
the ng-bootstrap library, that'll come next. I'm going to install a couple of
helpful libraries. So we'll use npm install. I'm going to install lodash, I seem to use that
in almost every project, and also ngx-loading, and I'm going to use the --save switch so
they get put in my package. json file. Great, those are installed now. Let me just show
you real quick in my package. json file, they've been installed there on line 24 and
25. Now we're going to go into the app. module. ts once again. Let's put our Third party
imports right here. And we'll import from ngx-loading, ngxloadingModule. And just
personal preference, let's just use single quotes rather than double quotes here for
consistency. In the imports section, let's use it. NgxloadingModule. forRoot. Now that
we've let our app know about it, let's go into the home component, specifically the
HTML, and I'm just going to add a very simple snippet. You can see I've got a couple
div tags, I'm using the jumbotron, and it's just going to give the title of
Workout Tracker, Welcome to Workout Tracker. Let'sgo to the HTML of my workout
components. Similar thing here. We're just going to add a basic snippet. Now, I've
added a bunch of HTML here, so let me walk you through it real quick so you
understand everything that's happening here. On line 2, we're using the ngx-loading
component. This is basically going to just show us a spinner when an HTTP call's being
made, will hide and show it based on whether the HTTP call is in progress. And then on
line 4 I'm just using regular Bootstrap card,this is just basic CSS that comes
with Bootstrap. We give it a title of Workouts on line 6. And I have a button here
called Set Performance Targets. Right now there's no functionality, it's just a
placeholder.And if we look down at the next card, we have Workout Entries and a
placeholder button to add new entries. And then the main thing is we have a table, and
the table has some headings: Date, Type, andDistance. And then the content of the table,
you can see on line 31 we have an ngFor where we are iterating over the workouts and
we're showing each workout in the table. Let's actually go implementthat functionality,
so I'll go into the corresponding workouts. component TypeScript file, and I'm going to
put an initial snippet for this file as well. Let me walk you through this snippet that I just
created. On line 11, I have a workouts array, and then on line 12 is just a Boolean
variable that the ngx component is going to look at to see whether it hides or shows.
Remember, I said that if HTTP is in progress it willshow a spinner, and when not, we
will hide the spinner. So on line 17, you can see loading = true, that's going to show
the spinner. We're then going to make the HTTP call to getWorkouts, that's calling
theAPI that we previously created. And then when it gets the data back, we're
simply going to assign it to an array, and then we'll tell the loading spinner to be false,
meaning hide itself. We also have a default implementation to delete the workout on
line 24. We're also doing very standard Angular out-of-the-box injection. In the
constructor, we're injecting that workoutsApiService. Now the other thing is we have
the abilities to add a new workout entry. So from this workouts page, we can navigate to
the entry editor where we can add or edit an existing workout. So let's go into the entry-
editor, specifically the HTML. I'm going to add a new snippet here as well. Again, at
first it looks like a lot, but let me walk you through it and you'll see that it's actually
very straightforward. We have a heading called Entry, and then the first thing
is basically a simple drop-down where you're selecting the type of workout. Either it's a
Bike, a Run, or a Row. Very simple out-of-the- box Angular code. Next we have a date
where it's just a simple textbox where you can specify a date for your workout. If I
scroll down a little bit more, we can see similarly we have a textbox where you can
specify the distance of your workout. And finally at the bottom, Save and Cancel
buttons. Let's open the corresponding entry-editorTypeScript file. If we look at
everything that's been injected, we have an individual workout item on line 11. So rather
than an array, it's just a single item. Once again, the loading Boolean value, whether
we're showing or hiding the spinner. And then the ngOnInit, we're looking at the route
parameters, because if they select an existing one, we want to get the existing workout
from the web API. If they just said new, that means we don't need to retrieve an existing
one, we're just going to create a new one forinsertion. Once we get the workout from the
HTTP web API, we'll just assign it to the individual workout item. And oh, by the way,
when it comes time to save, they'll click the Save button, which willsave the workout to
the web API and navigate them back to the workout screen. Alright, now I put a lot of
code in here, and even though it's all very standard, almost boilerplate Angular code,
let's have a quick look at it in action. And actually, before we run it, there's one final
thing I need to do, which is now that we actually have forms in our HTML, we need to
go down here to our app. module. ts and we need to add a reference to the
FormsModule, again, that's built into Angular. And then down here in the imports, we
need to use it. Now with that in place, we're ready to run our app. And here we are back
in the app. You can see the Home screen, Workout Tracker, Welcome to Workout
Tracker. I can navigate to the Workouts screen, and it actually happened very fast, but
we briefly saw the spinnercomponent. Let me show it to you one more time. Let me go
back to the Home screen, and you can look right in the middle of the screen. You can
see we had those three dots. That was the progress, that ngx loading control that showed
while the HTTP call was happening. Now in our case the HTTP call happened very fast,
so we just saw it very briefly, but if your HTTP call takes a longer time, you'll see it
a little bit longer. We have the grid here where the workouts are all being displayed in
the table. I can add a new entry. There's our drop-down, let's say Bike. Let's just put a
date in of 2018-10-14, and let's put a distance of 121 just for the fun of it, and we'll click
Save. There's our entry down here, 10-14, 121. I can edit it. Let's change it to 122. I
can save it. There it is saved at the bottom, 122. I can delete it, and it's gone. So we have
all the basic CRUD operations, C-R-U-D, create, read, update, delete. Andeven though
we haven't used any ng-bootstrap components yet, we've got some initial functionality
in our app. Now it's time to add ng-bootstrap to see how we can really enhance the
functionality of our application.
Initial App Setup
The Angular development tools are based in Node, so you'll need to go to nodejs.
org and install Node onto your local machine for your respective operating system.
I've already got Node installed locally, so I'll skip that step here. The next thing we need
to make sure that we do is to have the Angular CLI installed. You do this by typing npm
install and then -g to make sure it's installed globally, and then@angular/cli. Now I've
already got the Angular CLI installed, so I'm going to skip this step in the interest of
time. With the Angular CLI installed, we'll create our new app. I'll use ng new,
and we're going to give it a name, workout-tracker, and I also want to make sure that it
installs with routing. Nownot only did this create the app, but it's also going to run npm
install, which can take a few minutes. So with the magic of video editing, we're going to
fast forward in time and skip that step. Okay, great.Now that the install has been
complete, I'm going to cd into the directory that was just created, and then I'm going
to type code. to open this directory in VS Code, which is my editor of choice. You can
use whatever editor makes you happy, but as I mentioned, I'm going to be using VS
Code for this course. Now VS Code also has a built-in terminal, so I'm going to run a
few commands from this terminal right here, and specifically I'm going to scaffold out a
few new pages. So I'm going to type ng g c Home. And what's actually happening here
is this is ng generate component Home, but we have some shortcuts in here where I can
just do g for generate and c for component. So ng generatecomponent Home, and I'll
click Enter there. And you can see this has created us a new home component. And if I
expand my src directory, and expand app, we can see that it's created this folder right
here called home with these new components that have been generated right inside.
Let's repeat that for a couple more pages. I'm going to do ng g c workouts. This is
going to be the page that lists all our workouts. And then we'll say ng g c EntryEditor,
and finally, the last one I'm going to do is ng g c NavMenu. With those being created,
there's just a few more things we need to fix up. I'm going tocreate my own folder called
css, and I'm going to put a new file in here called bootstrap. superhero. min. css. I'm
going to flip over here to the Bootswatch website, bootswatch. com, and there's a bunch
of free themes here for Bootstrap. And I'm going to scroll down to this one right here
called Superhero, I can click PREVIEW. I can see what it looks like, yeah, that looks
good, this is the one I'm going to use. And I can actually open the CSS right here. And
I'm just going to select all of this CSS, and I'm going tocopy it, then I'll flip back to my
CSS file and just paste it right in here. And it's minimized, so it's all one line, and I can
save it. So now I've got the Bootstrap CSS in my project. Now I want to make sure the
CSS is actually used. So I'm going to open the angular. json file right down here, and in
the styles property I'm going to add a separate CSS for the one I just added,
src/css/bootstrap. superhero. min. css. There's also this syles. css file that was added by
default for our global styles. I'm going to add a quick snippet in here for some styles that
I want the app to use by default. The other thing I'm going to do is open the index. html
file. This is the single page of our single-page application. And I'm going toreplace all
of this HTML right here with a snippet. Not much has changed, you're just going to see
that the title says Workout Tracker up here on line 5, we have a div section in the body
where we have anavbar, and you can see this is where the app is going to load. So our
app-root starts between line 13 and line 20. We're then going to come into our
app. component. html and I'm going to replace this default implementation with some
very simplified HTML. You're going to see it has two things, the nav-menu, this is
the nav-menu that I added earlier here, and the router-outlet, which is where our primary
content will get placed. Now let's go look at that nav-menu. We'll open the HTML, and
you can see it's using built-in Bootstrap styling, and I have two navigation links, one for
Home, one for Workouts. So two pages so far. The last step is to open our app-routing.
module right here. And the routes is currently empty, we're going to add these. So
we have the HomeComponent, theWorkoutsComponents, and the EntryEditor. I'm
going to do Ctrl+dot to resolve the import, so that put the import for home import on
line 3. Let's repeat that again. Now we have the import on line 4, and the final import
right there. It's a nice feature of VS Code. We've got everything in place now. So let's
come back here, I'm going to clear that. Now that we have everything in place, I can
simply run an ng serve command, and we can see what this looks like in the browser.
And we can see that by default the Angular CLI will start this app on localhost with port
4200, so let's open that URL in the browser. Okay,so here it is. We have our Home
page, we have our Workouts page, and we've now got basic navigation in our app. So
we've got the styling from Bootstrap superhero CSS and some basic routing and
navigation for our pages.
Basic Forms
Overview
In this module, we're going to start off our exploration of ng-bootstrap with the basic
forms controls. We'll start out with buttons and I'll show you how ng-bootstrap button
groups can provide an alternative and more aesthetically pleasing option than regular
radio buttons or checkboxes. I'll then show how we can utilize the datepicker
component to implement rich user experiences when working with dates. Similar to the
datepicker, the timepicker component provides a huge improvement on user
experiences. I'll then show how you can use the rating component to easily provide
rating functionality. I'll finish off this module by showing you how easy it is to
implement the typeahead component toprovide a great experience for your users when it
comes to autocomplete functionality.
Datepicker - Inline
The final thing I want to show is how we can make our datepickers in-line. If we look at
the example we've been doing so far, this looks good, but the issue with it is that it sure
is taking up a lot of space. We have this entire calendar that's visible all the time, and
maybe we just want a situation where the calendar is only visible when we ask for it.
This is a good case where we might want to use an in-line datepicker. Let's see how we
can implement that. So what I'm going to do here is just take the existing
datepicker control and I'm going to comment it out completely. If I was checking this
into source control, of course, I would delete it, but in this case, to those of you that get
the course downloads, I want you to be able to see all the code that I've previously done
in this course. And I'm going to replace this with a little snippet here. Now let's look at
this snippet a little bit more closely. We can see that we now have a div class
with input-group and it's got an input right at the beginning. It's got a placeholder
in there showing the format. It's got a name, that ngModel that we copied exactly
from the control above. It also has a maxDate exactly like we did above. And it has
the ngbDatepicker attribute applied to it. We also have #d, which allows us to refer to it
directly. Then it gets really interesting because right below it we have input-group-
append, which enables us to put a button flush against that input control. And in that
button, we have a click event to toggle the calendar. So that d. toggle is referring to the
#d up above. So it's actually a pretty straightforward implementation. Before we run it, I
just want to add a little bit of CSS because we have this calendar attribute right here on
the button. So I'm going to go into my styles. css, and I'm going to add a new CSS with
a button calendar so we have a nice little image on the button calendar there. Now let's
go have a look at our app. If we come into our app right now, we can see that instead of
the big calendar control we just have this input and we have a button where we can
come and select it. And we click that button, it toggles the control. And once we toggle
it, we can select it, and there's our date, and everything works exactly as before, except
now we get to hide that big calendar control when we don't actually need it. So these are
just a few examples of how easy it is to get started with the datepicker control in the ng-
bootstrap library.
Timepicker
Once you're familiar with implementing the datepicker component, implementing
the timepicker is a very straightforward matter because the APIs are virtually identical.
Let's add another field here under Date. We'll call this one for time, and we'll make this
optional. I'll just come in here and copy this div section for distance, and I'll just paste it
right on top and format it up. And we'll make this one right here for time. So we have
Date up above, and then Time. Let's remove this textbox completely. And in its
place, we'll put ngb-timepicker. Let's give it a name of tp, and we'll say ngModel
= workout. time. Let's have a quick look at the app. And now we can see that we
have an area here to fill out the time. We have hours and then we have arrows, we can
go up and down. And we can go minutes, and you can see every time I click this
arrow, we go up 1 minute or down 1 minute, same with the hours. Let's see how we can
tweak this out-of-the- box functionality. First of all, let's add an attribute for meridian,
and we'll set that equal to true. And by doing that, we can see that we now have AM and
PM. So let's say we have 1:05, right now it's defaulting to AM, if I click it, it's now PM.
I click it again, it's AM, so we can click that on and off, that's the meridian. I'm going
to go down to the next line here for readability. I'm going to add another attribute here
for seconds, and we'll set that to true. And by doing that, you can see we have a third
textbox here, this is now for seconds. Let's come in here and set another attribute for
spinners, and let's set that equal to false. By doing that, we don't have the spinners
anymore. We can still type in the time manually, though that's not quite as helpful, so
a lot of times you might want to remove that. Let's go back here and either remove it or
we can just set it to true, which is like removing it since true is the default. And now
we've got our arrows back. Now setting the seconds for what time your workout starts is
probably overkill. And not only that, but to have to click the button one time for
every minute, this is getting probably a little bit too precise as well. But we canspecify
how big the increment steps are every time we click one of the arrows. So let's go back
in here. I'll just set seconds equal to false since we don't need that for our example. And
I'm going to addanother attribute called minuteStep. And I'm going to set this equal to
15. Now we have steps for each one. We have powerStep, secondStep, and minuteStep,
by default, they're all just 1. The only one I'm going to do here right now though
is minuteStep, and we'll set that, as I said, equal to 15. If we come back here, we'll see
the second textbox is gone. Now when I click the minute step, it goes up by 15minutes.
So 12:15, 30, 45, 1:00, and this is much more convenient now for setting a quick time. I
don't have to click it 60 times to get up to a certain time. So if I want to set it for 1:30
PM, I can do it right there. Let's say Row, I'll just give it a date, and we'll give it a
distance of 111, and I'll go ahead and Save it. Now I just want to point out what this
looks like in the database. So let's come over here and get our db. json file. And you can
see we have a time object again, hour, minute, second. Just like the datepicker where we
implemented a date adaptor, we could do the same thing here. We could build our own
custom time adaptor, but that would be a repetitive demo, so I'm just showing it to you
here, you can do whichever way makes you happy. Either keep the object or just use an
adaptor to make it a single value.
Rating
The next enhancement we're going to make to our application is we're going to
make use of the ng-bootstrap rating component. Rating components are convenient
when you want to provide some type of rating or quality measure to a certain thing.
You might be rating your opinion on how good you thought a movie was or some
social media application like that. In this case what we're going to do is add a
rating control when we put in our workout, again, it will be optional. But when we put
in our workout, we might say hey, I felt really good today, this was a great workout, or a
good run course or a bike course. Or, oh, I felt bad, the legs didn't feel good today,
whatever the case may be, we're just going to add a rating control here to our workout
Entry screen. So we'll flip over here, and we'll copydistance, and we'll add it right
above. Actually, I'll just copy it right below, that'll be the distance one. We'll change this
label to Quality. This is what we're going to rate, this is going to rate our workout. And
we will remove this default textbox right here. Then we're going to replace it with ngb-
rating. And then we're going to bind it to an attribute called rate, and that will go to
workout. rating. We'll also add a little margin to the top. When we look at what got
implemented, we can see we now have a rating control with 10 stars. As I mouse hover
over, you can see it starts to fill in the stars. I can click any one of these, so I click
the number 4, I click 7, I can click all 10, you can click anywhere in there dependingon
what kind of rating you want to give it. Let's add some more attributes. One thing you
can do is you can bind to an attribute called readonly, and we'll set this equal to true.
Now in this case, this would be for display purposes only. So for example, if I come and
look at it, I can't actually select anything in the stars. You might say, well, that's not
helpful, but if you have a situation where you do want to just display a rating, then it
would be helpful. But in our case, we want the user to actually be able to specify, so
it's not helpful, so let's change that back. Rather than delete it, I'll just set it to false so
you can see what the code looked like. Let's add another attribute. I'll say max = 5. And
now we can see we only have five stars. So I might just give it a three out of five stars,
four out of five, five out of five, whatever the case may be. Now it gives us these starts
out of the box and these white stars, or justoutline of white stars, but what if we want to
customize the appearance of these stars? Let's come in here to the CSS of the entry-
editor. And I'm just going to add a little snippet here. You can see I have a class for star,
whether it's filled in, whether it's a bad rating or a good rating. You can see we have red
colors there, we'll see what that looks like in a second. Then if we flip back over to
the HTML, and actually inside the ngb-rating component, we'll add a little snippet here.
And what you can see is that it's ng- template, and we're setting variables for fill and for
index. And you can see that we're making use of those CSS classes. So by default, the
class for the span is a star, you saw that in the CSS, and then if it's filled, fill equals
100, and if it's bad, index is less than 2. In other words, if the index is less than 2, let's
give it the bad CSS class. Alright, let's go take a look. So now we can see we've got
some gradients here, red and green, and if we mouse hover over we get a darker red or a
darker green. So we could give that a rating of three, and you can see the green stars lit
up, or we can give it a rating of five, and all five starts are lit up, or it it's just a one,
then only the red star is lit up. Let's give it a rating of four, and let's see what it looks
like in the database. So once again, we'll just select a date. I'll leave time empty, and
we'll give it a distance of 111, and this was a rating of 4. So we'll save that. If we look at
our db. json, we can see right here it got a rating of 4, we can see that on line 54. And if
we look at the app, we can select the existing item, and we come back into it, the
stars are pre-filled. So we can see that the four stars are filled in, which corresponds to
our previous selection. So the rating control is quite simple to implement, and if you
ever need this type of functionality in your app, I highlyencourage you to use it. It gives
a nice visual and it's extremely quick and easy to use.
Summary
In this module, we covered five of the basic forms controls that are in the ng- bootstrap
library. We started out by looking at buttons and how we could use them to provide
selection functionality in a more visually appealing way as compared with traditional
radio buttons, checkboxes, or even short drop- down lists. The datepicker control is
very powerful and enables your users to have a great experience while getting
a consistent format and being able to constrain which input is valid for theuser to select.
We can also use a full calendar or an in- line calendar that only appears when the user
wants to use it. If that's not enough, we can even use custom date formatters to precisely
control howwe want to interact with and store the data. The timepicker component has a
very similar and consistent API as the datepicker. If you're familiar with the datepicker,
then the timepicker will be easy to use. Like the datepicker, the timepicker enables you
to use custom formatters and also customize the size of the increment steps. The rating
control enables you to easily implement rating functionality when you need to
get opinions from your users. One of the most important features here is the ability
to visually customize the appearance of the rating component. We saw how easy it is
to use the typeahead control to give users easy searching functionality. We can do either
client-side or server-side filtering. And we have the ability to control the formatting of
what appears in the typeahead's drop-down and control how we want to store the data.
Datepicker - Basic Functionality
It seems like specifying dates has been a pain for developers and users alike since the
invention of the internet. There's formatting differences and time zone differences, and
it's not easy to provide a consistent experience. Take this example here. Using a textbox
is probably the worst idea when it comes to dates. If you just let the user type in a date
freeform, you might get something like this whereit's 10/15/2018, I'll just say Row
and will give a distance of 100, and if I click Save here, what we'll notice is that actually
this format's different than all these other formats where we had the year first and
dashes, here we have the month first followed by slashes, and it's a really inconsistent
format, you have to do a bunch of date parsing. So, this right off the bat is not a good
experience. Let's see how we can improve the situation with the ng-bootstrap datepicker
control. So here we are back in the HTML, and I've just got a simple textbox for this.
For now, let's just comment it out, we'll remove it later, and in its place we'll put ngb-
datepicker. I'm going to give it a name of d, and we're going to copy the name and the
model from the regular input control. Now without making any other modifications, let's
have a quick look at our app. And right off the bat we already have a pretty nice
experience here. We have a nice calendar control. If I click on, let's say a date right
here, and let's just give it Row and a distance of 111, and click Save, we can see that it
doesn't have the exact right format, it's actually stored as an object. This is
interesting. Let's examine this a little bit more closely. If we come into our db. json
fileand we look at how this actually got stored in our web API, we can see it right down
here on line 54 where instead of just a regular date string, for example, like you would
see on line 49, we have thisinteresting looking date object on line 54 where we have a
date that has a year, month, and day. This is the default behavior for the ng-bootstrap
datepicker control. And if this meets your needs out of the box, great, just use this. But,
you might also have a situation where you don't want this complex JSON date
object and you do want just a string, and you can customize this as well. So in order
to address this situation, we are going to go into the app directory, specifically
the services directory. And in here, we're going to generate another service, so ng, g s is
generate service, and we're going to call it a DateStringAdapter. And here it is, just got
created. And in here, we're going to say that it implements the ngbDateAdapter. And
you can see by hitting Tab, it resolved the import on line 2. And it's a generic type, so
we're going to give it a type of string. Now I'm going to provide some default
functionality here. And you can see it has a fromModel method and a toModel method,
and they both need an ngbDateStruct. Let's do Ctrl+dot to resolve that import, which
just got added to line 2. And you can see all that's happening here is the fromModel is
taking in a date string and it's returning a DateStruct object that we saw was being saved
in the database. The toModel is doing the opposite. It's taking in that DateStruct object
and returning a simple string. And we can see that string has formatting words, the year,
then a dash, then a month, then dash, and then finishing off with a day. So it's
actually pretty straightforward, but by having this in our code, everything will just
happen automatically. Now that we've got that in place, let's go into our app. module,
and in our providers section we have to let our app know that this exists, so we have to
use an object here where we say provide NgbDateAdapter, then we'll say useClass, and
this has to be the one we just created, which is theDateStringAdapterService. And
because I'm hitting Tab, the autocomplete is nicely putting these imports in, for
example, you just saw the DateStringAdapterService added to line 18. Now let's run our
app again. Here we are back in our app. We'll once again say October 16th, we'll
say Row, and we'll say 111. This time when we click Save, we can see that it's been
put down here. There's row 111, and theformatting's now correct, 2018-10-16, we can
see it right there. And not only that, but if we look in our db. json, we can see that on
line 54 we have a nicely formatted date object due to the date adaptor that we created.
We have a lot of flexibility there in terms of how it can get implemented.
Typeahead
The ng-bootstrap typeahead component is extremely useful anytime you want to give
your users the ability to start typing some string and have some autocomplete
functionality. This might be helpful when filtering a large list, for example. Maybe
something that's too big to put in an entire drop-down, or you want them to search via
substring, so their search term might not be at the beginning of the string. Let's add a
textbox for location to our Entry screen here. In a complete app we'd let users add
new locations, but in the interest of keeping this course focused on the front-end
components, we'll just focus on using data we already have. So let's add a typeahead for
locations so that users can specify the location of where their workout took place. Let's
come in here to our HTML, and under distance, I'm going to add a little snippet here for
location. You can see that we have a label here for location on line 63, and then on line
65 we have just a regular textbox with form-control. Name is location, ngModel is
workout. location. So nothing interesting here yet, just a label and a textbox. And we're
proving this to ourselves, we can see the location and textbox is right here. Nothing
interesting so far, just a regular textbox. Now let's open up our TypeScript file. Let's add
a new variable called locations. And as you can see, I've just hard coded an array here,
it's just a string array, Main gym, Home gym, Neighborhood 1 mile course,
Neighborhood 3 mile course. I've just put some random locations in here that someone
can choose from. The next thing I'm going to do is I'm going to come down here, and
I'm going to add a field for locationsSearch. Now I have a few red squiggles that I need
to resolve here. These are operators from RxJS, so let's add an import for that. And if we
scroll down here, we can see all the red squiggles are gone. So we're going to use
this locationsSearch in our text box. We'll flip back over here, and I'm just going to go
to another line here for readability, and we'll addngbTypeahead. So what's happening
right here is we're specifying this textbox is going to be atypeahead, and we're giving
the locationsSearch it should use once the user starts typing. Let's have another look at
that function. So if we look at this locationsSearch function here, this is going to be
what gets executed when the user starts typing. The first thing we have
is debounceTime. That means wait 200 ms before you start actually doing something
because we don't know how fast the user's going to be typing. We don't need to do
anything until it's changed. And then in the map functionality here, you can see it says
term goes to term. length is less than 2, meaning if we have less than 2characters, don't
even bother trying to do this filter. Otherwise, if we have more than 2 characters, now
we're performing a location search on line 49. And you can see that we're doing
toLowerCase, that's just a way for us to do a case insensitive search, and then
indexOf the term itself. So that indexOf willessentially make sure that it's a substring.
It might appear anywhere in the string, not just at the beginning. And then the slice at
the end is saying, you know we might have 20 results, but just show the top 10 results,
that's why it shows 0 to 10. So let's go see this in action. So if I come down here to my
Location textbox and I start typing, you can see the first character doesn't get me
anything, but then when I do at least two characters, now we have things that are
popping up. So nei filtered it to just those 3 items on the list, the Neighborhood 1, 3, and
5 mile courses. If I say ho, we can see that's Home gym or the Neighborhood. So in
the Neighborhood, we can see the ho in the middle has actually done a substring for
those. It's a little bit subtle on this font, but you can actually see that it's highlighting the
ho in bold. Cou, again, it's highlighting the cou in the course, so it comes up with these
different courses. So without much code, we were able to implement pretty good
typeahead functionality with not much work. Now so far I've just done this with hard
coded data. Let's see how this changes when we're actually getting our data from an
HTTP web API. The first thing I'm going to do is come down here, and let's go to
our workout service, and we'll add a little snippet to get the locations. It's going to call
our existing web API and say /locations. Now if we look at the db. json file, I've already
put a bunch of locations up here, and we can see that it has an id of 1 as Main gym, 2 as
Home gym, and this pretty much corresponds to the items you saw that I had hard coded
in the stringarray. But what's different is that these are actual objects, it's not just a
simple string array. Each item has an ID and a name, so these are actual objects now.
Let's have a look at what impact that might have on our functionality. So we'll come in
here to the beginning of ngOnInit when the component isfirst loading, and I'm just
going to add a little method call here for getLocations, and we'll set our array of
locations to this data. So that means I am going to change this to just be an empty array.
So we now have an empty array on line 18, and that array is going to get populated
when the HTTP call comes back on line 31. Now let's have a look at our app. I'm
going to start typing, and we can see that actuallynothing's happening right now. I'm
going to hit F12 to look at developer tools, and we can see that we have an error on the
console and I just want you to see what happens so we can illustrate the point of what
happens when you go from a regular string array to an object. It says toLowerCase is
not a function. So let's go back and have a look at that location's search function. What
we actually have to do now, this v, this is our term, this is not just a simple string
anymore, now it's an object that has an ID and a name. So we have to say.
name. toLowerCase. Now let's look again. This time I'm going to type nei again. We
can see that we have no errors, but ah, this is interesting. We correctly have filtered
down to three results, but this isn't a very good user experience. It's not showing us the
actual name of the term, it's just showing us object. So we need to go fix this. In order to
do this, the typeahead has anattribute called resultFormatter. And we're going to give it
a value of locationsFormatter. Let's copy this, and let's go over to the TypeScript file,
and under locationsSearch, we're going to add this locationsFormatter, and this is going
to be equal to, and we'll give it a lambda here, result. name. In other words, it's not just
using the string, we want to use the. name property of that object. Let's flipback here,
nei. Great. Now things are looking really good. Let's select the second one. And now we
have another problem. You can see that the drop-down itself looked good, but then
when we actually selected one, what we're seeing appearing in the textbox is not good.
Now let's address this. Flipping back here to the HTML, just like we had
resultFormatter, we have another attribute called inputFormatter. In other words, that
input, that textbox, what formatter can we use there? Well, actually we just want to refer
to the name property again, and we already have that locationsFormatter. So I'm just
going to copy this and paste it right in here. And now when we come in here we
can type nei, select the 3 mile course, and everything is looking good. Now this is all
well and good, but essentially what happened here is I got my objects from the database
when the pageloaded, and I still did client-side filtering when the user was typing. What
if I want the user to type and then do a server-side filter as a result of that? We'll look at
that next.
Datepicker - Navigation
Now in order to give your users really great experiences, there are a few issues you need
to be aware of. Let's take an example where we go to the previous month. Let me select
a date from September 22nd, and we'll just say Row, and we'll make it 99, and I'll click
Save. And there it is right there, September 22nd, we rode 99, fine. If I click Edit
though, when I come back, you don't see the date selected. In fact, it just shows the
current month, which is October. However, if I go back, it is correctly selected, but it
wasn't a very good user experience, I actually had to navigate backwards to see the
correctly selected date. Let's fix that now. Here I am back in the HTML. And on line 23
I'm going to add a startDate attribute, and we're just going to set this equal to a variable
called startDate. Here I am back in the HTML, and we need to add an attribute called
startDate. And we're going to set this equal to a variable called startDate. Now we
haven't actually added this variable yet, so let's copy that, and I'm going to flip it over to
the TypeScript component, and we'll just add a public variable for it. For now, we'll just
set it to any. Now I essentially want this to be set to the current month. So what I'm
going to do down here on line 25, after data loads for an existing workout, we need to
preset this. So I'm just going to create a variable called d for a new Date, and we're
going to set it equal to whatever the workout date is for the existing item that we're
loading in from the API. And then we're going to set the startDate variable, the one we
just created, to this object that wants a year and a month. So obviously, the year's going
to be, we'll do getFullYear, and the month will be the month of that dateobject. Because
it's 0 index based, we have to add + 1 onto it. Now let's go see what it looks like. Here I
am back on the Workouts screen. There's the row that says 99. If I edit it, we can see by
default, it goes back a month to September and September 22nd is selected, and that's
the month that's initially visible. Now, there are some other enhancements we can make
to this. For example, we might want todisable or limit certain selections from being
considered. Maybe we don't want to allow future dates because it doesn't make sense.
Let's see how we can continue to enhance our app to add some more of these features.
We're going to implement a somewhat similar pattern that we did for startDate for max
date. I'm just going to put this on a new line to make it a little bit easier to read. And
we're going to add a new attribute called maxDate, and we're going to set this equal to a
new variable called, you guessed it, maxDate. Let's copy that. Let's flip back over here.
And give it right here on top, and in fact,instead of making this an any like we
did previously, we're going to make this an NgbDateStruct. And if we look at
the definition of that, we'll see it has a year, a month, and a day. Now we're going to set
that inside the constructor because we want that to happen regardless of whether it's an
existing record or a new record. So we'll just create a new variable called today. And
we're going to set maxDate equal to: (Typing code) So we're doing a year, a month, and
day, we'll do Ctrl+dot to resolve the ngbDate,and now let's run it again. So I'll go into
the new entry screen for the first time, and what we can see here is that it's the 16th,
which today happens to be, but these other ones are grayed out, so I'm unable to select
any of these even when I'm clicking them. I can certainly select any of these, but if I
don't want to allow a future date, you can disable them easily.
Buttons
Ng-bootstrap buttons visually look like regular buttons, but this component
is specifically used when you want to provide selection functionality similar to
radio buttons, checkboxes, or short drop-downlists. If we look at our workout
Entry screen right here, we have a drop-down where you must select one value,
either Bike, Run, or Row. So this is currently implemented as a drop-down, this
could have been a radio button. We're going to switch this to use ng-bootstrap buttons
so that we have something a little bit more visually pleasing. Here I am back in
the HTML component, and you can see that on line 11 we have the drop-down that
we're using. I'm temporarily going to just comment this out, we'll delete it in a second,
but for now I just want you to be able to see what the previous examplelooked like. So
if we implement this with ng-bootstrap buttons, we're going to put it in a div tag. Now
inside this div tag, we're going to give the attribute ngbRadioGroup, and I'm going to
copy the name and the ngModel from this previous drop-down that we were using, and
I'm just going to paste them right here. Now inside this div we're going to add these
three buttons. And I'm using the ZenCodingfunctionality that comes built into VS Code
where you see I'm doing some of this autocomplete functionality. So we're going to
have a label control and inside that is going to be an input with type=radio. We need a
value for each one, which I'll leave blank for now. And, oh by the way, we need three of
those, so I'll do time 3 and expand that, and here we go. And for this example, we don't
need the four attributes, so I'm going to do bulk select in VS Code by holding down the
Shift+Alt key and delete those. And to each of these I'm going to add an
ngbButtonLabel. And then in this input right here, we're going to do Ctrl+D two times
to select the next two instances. And after here, we're goingto make each of these inputs
an ngbButton. Again, I'm just using this multi-select functionality that's built into VS
Code. And then for the label, we're going to give each one a different value: bike, run,
and row. And after each input, we want a nice visual name, so we'll just give the same
thing, Bike, we'll do an uppercase B, Run, and Row. And now we can see the completed
result. It still looks very much like a drop-down list, we have the name and the
ngModel, but we just have labels and inputs inside. The labels have ngbButtonLabel,
and the inputs have the ngbButton attributes, but the implementation should look
pretty familiar. Let's go have a look at our app. If we come in here, we can see that we
canselect Bike, Run, or Row. Let's select Row, and let's just give it date of 2018-10-15,
and we'll say the distance is 101, and then we'll Save it. If we edit it and come into an
existing one, we can see Row is preselected, we can see the different color there. So not
only does it work for new ones, but then we bring up existing ones, it also gets
preselected appropriately. So you can see the implementation looked pretty similar
to what you would do for a drop-down or radio button, but now we have a nice
little visual control that allows users to easily select something with their mouse when
you just have a few options.
Typeahead with Server-side Filtering
Now we've already implemented pretty solid typeahead functionality on our screen right
here. And I can come in and type a substring and it'll find the result of that substring.
Well, what's happened hereis I grabbed some values from the database and I'm just
doing client-side filtering. What if we have a really big list and we really need to do
server-side filtering? Let's look at how to implement that next.We'll come over here to
our entry-editor TypeScript. This locationsSearch is the one that's doing the client-
side filtering. I'm just going to comment this out because I want you to be able to
see how the client-side filtering worked. So for people that are downloading the
code samples, I don't want to delete it because I want you to be able to see it. I'm
going to put a new snippet in its place. So we're going to resolve a few
additional operators here. And we also need to implement this searchLocations method
in our API class, so let's do that real quick. And you can see here that we're passing in a
searchTerm and it's using a query string parameter called q to pass in that searchTerm.
And our back-end API will do a substring. And that's another nice feature of the JSON
Server. But if we come back here and look at our locationsSearch, let's see what's going
on here. The first two lines are the same with the debounceTime and the dintinctUntil,
so nothing will happen until at least 200 ms has passed without the user typing anything.
The tap operator essentially is injecting functionality into our workflow, so we're saying
hey, the next step I want you to do is set the loading equal to true. Then, we're actually
making the API call, sending in the substring, and once that's completed, we'll use Tab
again to set this. loading to false. So it's actually relatively straightforward. Back in our
app, let's do a couple search terms. We'll do nei again, everything's working pretty well,
Neighborhood 3 mile course, ho, and we have Home gym or Neighborhood 1 mile
course. So everything's looking pretty good. In fact, let's select one of them, we'll
do Neighborhood 3 mile course. And what's also interesting is if I go over look at the
Network tab, we can see that we did have these actual HTTP calls that were made
passing in our query string with that q variable of the substring that we typed in the
textbox. So let's see what happens if we actually save it. We'll just give it a Date. I won't
give a Time and Quality.Distance is 111. Let's Save it. Now if we go look at the
database, what we're actually going to see here is that on line 55 to 57, it's saved the
entire object because that is what was being bound to the database. And maybe we don't
want that entire object, maybe we just want a string. So let's see how we can deal
with that. First off, let's Delete that. And we're just going to inject one other thing into
the workflow here where we're going to say map the results to locations, and we'll just
use the lodash map function. Specifically, we want to map this to the name property.
Now we're getting a red squiggle because lodash hasn't been imported. So let's fix that
real quick. We'll just say import * as _from_lodash. But again, you look at it, it's
pretty straightforward. We've got the whole map, but then we're just mapping it
to locations. So the result of line 61, which we just injected, it's kind of like givingthat
regular string array that we had in the beginning. We're just mapping to a simple string
and not having to deal with a complex object. Let's go look at it one final time. So we'll
come in here again and we see this isn't working, and why is that? It's because we're
using formatters that are for an object, not just a regular string. So this illustrates the
point that we are really now just back to using regular strings. So let's go fix that up. So
I'm going to take the resultFormatter and the inputFormatter and put them down here.
And I'm just going to comment this line out. I want you to be able to see the actual code
I did, but we don't need it anymore, so we're just going to remove it. The other thing is
we're getting our locations from the server side when we type individual terms now, so
we don't need this one anymore either. Let's go have a final look at our app. So
we'll come in here. Locations, everything'slooking good, 3 mile course, let's give
a Distance of 111, we give it a date, we'll say it's a Run, let's Save it. Everything's
looking good here. Let's go look at our database. And we can now see that the location
has a simple string, it's no longer the complex object there on line 55. So as you've seen,
the typeahead control is very powerful. You can give a lot of functionality without
having to write a ton of code. Whether you're using client-side filtering or even server-
side filtering, you can really give your users a great experience.
Structural Components
Overview
In this module, we're going to turn our attention to the structural components of ng-
bootstrap. These are components that organize information and allow you to hide and
show information when it'sappropriate. We'll start out by looking at the collapse
component, which allows us to hide and show multiple panels depending on our
application's needs. We'll then graduate to the accordion, which you can conceptually
think of as multiple collapse components. We'll then focus on modal dialogs. I'll start
out by showing a very basic example of an inline modal. I'll then show how you can
create modals using components. This will allow modals to be reused in any area of
your application. I'll also show how you can pass data in and out of modals. Tabs have
been foundational components of UI applications for many years. I'll show you the
options available for using tabs in ng-bootstrap. The carousel component enables you to
show images, or any data for that matter, in a carousel control that a user can
iterate through. Let's get started looking at structural components now.
Accordion - Basic Functionality
Now we're going to add an accordion component into our app by adding it to the home
screen. You can think of the accordion as several collapsible panels strung together.
Currently our Home screen is extremely plain. We're just going to add an accordion to
the Home screen tab, a little bit of a welcome wizard, to users that come to our screen.
Now this is going to be a very simplistic implementation, alittle bit contrived for the
purpose of the demo, but let's add that accordion to the Home screen now. Currently in
my Home screen, all I have is this jumbotron with a welcome message. I'm going to add
the accordion right under here. Now, I'm going to add the accordion with three panels,
so we're going to use a little Zen Coding here. And we'll start out with an ngb-
accordion, and inside it we'll put three panels with the title. And inside each one of
those, we'll put an ng-template. Now you can see I have three panels inside my ngb-
accordion, and I'm going to give a title to each one. And I'm just going to delete the
quotes here for the ngbPanelContent because we just need to have the attribute present.
We'll put some simplistic content there. Now this is extremely simple implementation.
We just have three panels, the titles are First, Second, and Third, and the content
inside the panels says First Panel, Second Panel, and Third Panel. Here we are back in
the app. We can see the three panels. First, if I click it to expand it, I see First Panel, I
can then click Second Panel, and Third Panel. And you can see each one of these panels
will expand independently, and I can expand them all at the same time. Now by default,
you can see the title is using this orange font, which is just the primary Bootstrap class.
What ifwe want to customize the look and feel of a title? Let's go look at that. We're
just going to change the title of this First Panel, so let's delete the default title attribute.
And inside the panel, we're going to use another ng-template, except this time instead of
the attribute ngbPanelContent, we're going to usengbPanelTitle. Then inside, we can put
any custom HTML that we want. So all I've done here is add a p tag and I've given
it font-weight-bold. Now admittedly, this is a little bit subtle, but if you look closely,
you can see the word First is bold, and the words Second and Third are still not bold.
And of course, we can make any customizations we want. And remember, when we first
showed the default behavior,we could expand all three of these at the same time. But
what if we wanted to expand them such that only one was showing at a time? Let's look
at that next. We'll come to the ngb-accordion, and we'll add an attribute called
closeOthers, and we'll just set that equal to true. And while I'm at it, I'm going to give it
another attribute of type, and we're going to set that equal to success just to change
the color. And you can see we've changed that to a green color, for this theme success is
a green color. The other thing we're going to see is that I can expand the First Panel,
then when I click to expand the Second Panel, it will close the First Panel, same with
the Third Panel. So now since we set closeOthers equal to true, only one can be open at
a time. Now when we came into the screen, they were allcollapsed, but what if we
wanted to come into the screen and have one of the panels open by default? So I'm
going to add an ID attribute to each panel. So we'll come to the first panel and say
id=panel1, not very imaginative, panel2, and to the last one we'll add panel3. Now to
the ngb-accordion attribute, we're going to add activeIds=, and I'll actually set it to 2,
panel1, panel2. Now when I come into the screen, you might notice that only panel1 is
active, but I actually listed 2 panels that I wanted to be open, so why is it only showing
1? Well, one thing we have to remember is we set that closeOthersattribute equal to
true, which means only one can be open at a time. So let's come in here and remove
closeOthers completely. Now when we come into the screen, we can see both panel1
and panel2 are open and panel3 is closed, but of course we can always open it if we
want to. Now that we've seen the most basic implementation of a panel, let's see how we
can enhance the functionality a little bit so that we can add a sequential workflow.
Collapse
We've had this Set Performance Targets button right here. It's been a placeholder up
until now. I'm going to illustrate an example of the collapse component by adding a
little button next to this thatwhen the user clicks it, it will show some help texts about
this button so the user can understand what this button does. So we're going to come in
here to our workouts HTML, and right under that buttonfor Set Performance Targets,
we're going to add another button, and we're just going to put a little question mark
in that button. And up here, we're just going to add a little margin right so we havesome
space between the buttons, and I realize we need a space here. Let's take a quick look at
the app. And there we go, there's our blue button. Now let's add some functionality.
Right under thisbutton, we're going to add a div, and I'm just going to give it a little
margin top. And for this div, we're going to give it the attribute ngbCollapse. And I'll
assign this to this variable, isCollapsed. Then inside the snippet, just add a little HTML.
And all I've got here is a card that has a card-body, and it says This will enable you to
set performance targets for each activity, and it's just giving some little help texts for
this button. Now let's add a click handler for when this button gets clicked. And we're
simply going to set that value for isCollapsed equal to the opposite of whatever it
is right now. We come in here, we can see there's the panel with the help text. If I click
this button, it goes away. If I click it again, it appears. So we're just toggling with this
button and we're showing and hiding the div via the collapse component. Now this is all
well and good, but it's a little weird that the help was showing when we first came
into the screen. It'd probably be better to hide it initially and only show it if the user
actually wants to see that help text. So let's just come in here, and we'll negate this by
initially saying theopposite of isCollapsed. And now when we come into the screen, it's
not being shown, but of course if we click the button, we'll show it and hide it. We
can also add some styling to the button to reflect the state. So for example, let's remove
the btn-info, and inside the button I'm just going to add a little ngClass directive. If it's
collapsed, we'll show btn-info, otherwise btn-outline-info. And we'll put this on the next
line just so we can see it a little bit better. Now we come in again, we look at the screen.
Wecan see it's just the outline of the button. If we want to show the panel, we'll click it,
and now the button is solid and we can see the text. We click it again, the text is hidden
and now it's just the outline of the button. So it's just a way that we can give state to
the button when we're working in conjunctionwith collapse element. The
collapse component is extremely easy to use. It's just a Boolean variable that
you're dealing with, and so you can use it anytime in your app when you need to show
and hide content.
Modal - Basic Functionality
Modal dialog boxes are extremely common and convenient to use in a variety
of situations. We're going to incorporate one into our app right now. If we look at
our Workout Entry screen, you've seen that each one has a Delete button and if I Delete
this one down here, with a date of 10-20, you can see it deleted very quickly. However,
what if as a user I didn't mean to do that? If I click the Delete button, we want to have a
little pop-up modal that says are you sure you want to delete that? And we can have
them say yes or no. Let's implement that now. Here I am in the workouts component,
and you can see the table for my workouts right here. We're going to add an in-
line modal first to show the most basic example first. Now let's examine closely
the snippet of code that I just added. On line 57, we've added an ng-template, we can
see the attribute says let-modal. This means we're going to create a modal variable
for this ng-template. I'm also giving it an ID of #deleteModal with a hashtag there.Inside
this template, we have three divs, and it's easy for us to differentiate each one with a
CSS class. So the first one, you can see the class of modal-header. And then inside the
content is an h4 withmodal-title and it just says Delete? The second one is modal-body,
and inside we just have content saying Are you sure you want to delete? And finally, we
have another div that's a modal-footer, and ithas a couple buttons in here, Yes and
No. And you can see that each one has a click event, modal. close and modal.
dismiss. Now where is that modal variable coming from? Again, it's from right up here
on line 57 where we have let-modal. Now we have an inline template we can use,
but how do we actually get this to be shown? Let's click over here to our
corresponding TypeScript file. And inside theconstructor, we're going to
inject NgbModal. I hit Tab and we resolve the import on line 4, it gets added to
our code. Now in the deleteWorkout method at the bottom, I'm going to add a
secondparameter for the modal itself and we'll just call it deleteModal. Now we
can actually use the modal service. So I'm going to say this. modal, this is the modal that
I injected in the constructor, and we'll say open. Now which modal are we going to
open? We're going to open that deleteModal that we're about to pass in. And then we
need to grab the result. It ends with a promise, so we'll say then. And the code down
here that we were originally using to delete, we're going to put this inside. Now there's
two different conditions. They say yes, they want to delete it, and no, they don't, in
other words they're cancelling it. So the second parameter here is for reason, and for
now I'm just going to log it to the console. So this was actually pretty easy
to implement, however one thing we still have to do, remember, I passed a
second parameter here for deleteModal, that was the modal itself. If we go back to
the HTML and we still have to add that second parameter here, so in the
deleteWorkout method, I'm going to add deleteModal. This deleteModal is referring to
the ID on line 57 of that ng-template. Now let's look at the app. I'm going to click the
Delete button of this row, and you can see the modal pops up. We have the title,
Delete? Are you sure you want to delete? And then we have the Yes/No buttons. Now
I'm going to hit F12 for the developer tools here, and I'm going to click No. And we can
see two things. Number one, the dismissed reason is undefined because we
didn't actually pass any data when we were cancelling. The other thing you can see
is that row is still there. Let's try it one more time. I'm going to click Delete again. This
time I'm going to say Yes, and you can see the row's been deleted. Now one other thing
I want to be able to show you is how you can control the size of these modals. I'm going
to click Delete again on a different row and take note of how wide this modal is. We
have a lot ofempty space here on the right, which we really don't need. Let's go fix that.
I'm going to come back here to the deleteWorkout method, and we're going to add a line
of code. The options is going to be of type ngbModalOptions. And there are a few
options in here, but we're just going to add size, which is optional, and we'll pass sm for
small. Then we're going to pass it into the open method as the second parameter. Let's
click the Delete button again. And this time, notice how much smaller the modal is,
there's not a bunch of empty space at the end. And so this is a quick way you can control
the size. Now up to this point, I've just shown you how you can quickly use an inline
template for modal functionality. But you could also use components so you can reuse
modals everywhere in your app. Let's look at how to do that next.
Summary
In this module, we covered the primary structural components of the ng-
bootstrap library. These structural components are ideal for organizing data and
arranging various aspects of the screens in your user interface. I started out by
showing you the collapse component, which is a panel, which you can hide and show
with a simple Boolean value. I then transitioned to the accordion next because
theaccordion is essentially just a series of collapse panels strung together. Here we were
able to see the flexibility of the accordion in terms of programmatically controlling the
behavior and even using it for a sequential workflow. We then looked at various ways to
implement a modal, starting with a basic inline modal using an ng-template. I then
showed how we can build more robust modals by usingcomponents. Components allow
modals to have their own logic in the TypeScript file, and they can be called and
reused from anywhere in your app. Finally, we looked at how to pass data in and out
ofmodals as we implemented the Performance Targets modal in our app. We looked at
how to quickly implement tabs in our app. We examined numerous ways to control
the appearance of the tabs, as well as how to programmatically control the
behavior. Lastly, we looked at the carousel control and how easy it makes it to cycle
through images. Not only does ng-bootstrap enable us to control the behavior and
appearance of the carousel, but I also showed how you can programmatically implement
your owncustom navigation buttons for the carousel. Now that we've looked
at structural components, we'll look at some quick and easy informational components
in the next module.
Accordion - Sequential Workflow
So far, we've been introduced to the basic functionality of the accordion component.
And when we've seen this functionality, we can see that each one of the panels of the
accordion can be opened and closed independently. But let's say we want to implement
a sequential workflow where only one can be opened at a time and we force the user to
go in order, and we can even add some validation so we don't allow the user to continue
unless they've met a certain condition. So let's come in here to theaccordion, and let's
add a template reference variable. We'll call it acc for accordion. This will allow us to
easily reference the accordion in other bindings. For activeIds, we just want it to be
panel1 by default so that when we come into the screen, the first panel is active.
And we're going to add back in our closeOthers attribute so that only one can be open at
a time. Another thing I'm going to do just forreadability is I'm going to add a space
between each panel just to make it a little bit easier to follow. Panel1, we're going to
change the title to Welcome. And we'll give it slightly more interesting content here.
We'll just say Welcome to Workout Tracker. Under this, we're going to put a next
button, and we'll put it on the right side. Let's add a click event to this button. And the
acc references the templatereference variable. And we'll call the toggle method of the
accordion, and we'll give an id of panel2 because that's the panel that we want
to navigate to next. On the Second Panel, we're going to change the title to How To.
Let's copy the content from panel1, and copy it to panel2. And let's just change the text
a little bit. So we've just given a little How To text, and in the next button, we're going
to send them to panel3. For panel3 we're just going to change the title to Enjoy, and
we're not going to have a button for this one, just some text. Now remember, I did say
this example was going to be pretty contrived, so this is not the most exciting sequential
workflow you've ever seen in your life. We basically just come in and see a Welcome
tab, we click Next, a little How To help text to help the user understand how to use
the system, and then we go to the next one and it's a summary message saying we hope
you enjoy the app. So when we come in here, the first panel of the accordion is
expanded, which is exactly what we want. We click the Next button, it goes sequentially
to the next panel. If we click Next again, it goes to the third panel. Okay, so everything
is good so far. However, if we go back to the first panel, I can actually jump around in
any order I want, and this may or may not be the functionality you want. Perhaps
you want a true sequential workflow where you want the user to go in order, you don't
want them to just be able to jump to panel three from panel one, for example. Let's go
see how we can implement that. So on the accordion itself, we're going to add a panel
change event. And I'm just going to put this on the next line for readability. And we'll
call it beforeChange, sending in the event object. Now on the next buttons, instead of
calling acc. toggle directly, we're going to encapsulate this in a new method called
nextButton, and we're going to give it an ID of 1. Let's copy this and do the exact same
thing down here, and we'll give it an ID of 2 because that's the one we want it to go to
next. Now let's open our home. components. ts file, and we have to add a little bit of
logic.First off, we're going to add a variable called stepComplete, and we're going to
initialize this to 0. This keeps track of which step is currently complete. We're also
going to use ViewChild to get a reference to that accordion. We resolve the import for
ViewChild, that got added on line 1. Now we add the implementation for nextButton.
And you can see the parameter is step, so the first thing we do is what step is complete.
So from the first panel we say step 1 is complete, and then we increment
stepComplete by 1 and go to that panel. So in other words, 1 plus 1 is 2, we'll go to the
second panel. If we call it from the second panel, it will go to the third panel. Now I'm
also going to add abeforeChange event. And in this event you can see we're doing some
validation. So if we're trying to go to panel2, but less than step one is complete, we'll
call preventDefault to prevent that navigation or the toggling of the next panel to
happen. And similarly, if we're trying to go to panel3 and they haven'tcompleted at least
step 2, it will prevent that as well. So we come back into the app. I'm on panel one, but
if I try to click panel two, nothing happens because I didn't get there with the Next
button. Similarly, if I go to panel three, I can't get there either. If I click Next, it does
allow me to go to panel two. I still cannot click into panel three, but if I click the Next
button, then I can do it. So this shows a simple example of how you can implement a
sequential workflow using the accordion control.
Modal - Passing Data
In the last example, I showed you how you how you can use a component to create
a modal that has logic and is reusable across your app. But the problem that we had here
was when launching the modal, we don't get data. So even though we have data, when
we actually click the performance targets we don't see that data displayed here, so we
need to pass data into the modal. And additionally, if the user then types something
different and clicks Save, we need to get data out of the modal so we can save it back to
our API. Let's implement that next. Now just a quick refresher here, here's our db.
json file. And you can see on line 2, we already have performanceTargets with
1000,2200, and 1500, so we already have legitimate data there. If we open
our workouts. component. ts, this is where we're launching this modal. Also, in
the component for the modal, remember that on line 9 we put this variable here
for perfTargets. We want to pass data into that variable for perfTargets. So back in the
workouts component, I want to be able to pass this data in here. So after the open
method, directly after, we're going to get a handle to that modalRef variable, and then
componentInstance, then we can put anything here. In this case we want perfTargets
because that's exactly the name we gave the variable inside that component. And we'll
just set that equal to this. perfTargets, which we got when the screen loaded, and we set
it on line 26. We also have this TODO comment for actually saving the data. So we'll
just put a little snippet in here that says yes, if they're saving it show the
loadingcomponent, then savePerfTarget, and then we'll set back the variable of
perfTargets to the latest data. Alright, let's see what we have so far. I'm going to
click Set Performance Targets. And this time instead of all three of the boxes
being empty, we actually have the values displayed, and these are exactly thevalues that
we saw in the db. json file. Now you can see I'm clicking Cancel and Save, and nothing
happens because we don't have behavior added to those buttons yet, so let's add that
now. Back in the PerformanceTargetsModalComponent, we're going to inject into the
constructor a variable called activeModal of type NgbActiveModal. This enables the
component itself to be able to use the modalmethods. So in here, we're going to add
a method called save, and it's simply going to be this. activeModal. You can see I have
close and dismiss. And in the close method, we can actually pass data back out. So
we're just going to pass the perfTargets that the user may have changed here by typing
in different values, we'll pass it in right here to the close method. And because we're
passing perfTargets into the close method here, what's going to happen is it's actually
going to show as the result. In fact, let's make that really obvious here. I'm going to put
a little comment in the console and say Modal result. Let's also go back to the HTML.
And we have to have click handlers for Save and Cancel. So Save is easy, that's just the
click event, and we just added the method called Save. Now for Cancel what we're
going to do, of course we're going to add a click event again, but remember, we actually
have that variable called activeModal, so we can call that directly, activeModal.
dismiss. Because remember, we had that other method there for dismiss. Now let's go
look at the app. I'll hit F12 so we can actually see the developer tools as this runs.
I'm going to click the Set Performance Targets. And we can see the values
that populated here. I'm just going to make this super obvious, so we'll just change this
to 1501, 2201, and 1001. I'm going to click Save, and you can see the Modal result
down here in the console tools actually shows 1001, 2201, 1501. Let's prove this to
ourselves by navigatingcompletely away from the screen and then coming back into the
screen. And if we call up Set Performance Targets, we can see, sure enough, the values
are populated there. So they successfully got saved to the database. We can also click
Cancel and it will just cancel the modal and not save our results. So we've seen just
how flexible the modal component is to work with. We can implement it very simply
by just using an inline template. We can also make it a component to get more
robustfunctionality. You can add logic to the component, as well as pass data in and out
and reuse it anywhere in your app.
Tabs
Tabs are another component that are extremely common and very easy to use.
The example I'm going to use for tabs is again going to be a little bit contrived, but I just
want to be able to quickly get acrossto you the basic functionality of how to use tabs.
What I'm going to do is we're actually going to add a third navigation option here to our
main navigation menu, and it's going to be for a theoretical admin page. So let's go
generate that new page. Once again, I'm going to use ng g c, and we're going to call this
the Admin component. Now that that is created, we're going to come in here to our
routing, andlet's add a path for it. And we need to make sure we add it to our nav-menu
as well. We'll just copy this one, put it in right below, and we'll change this
from workouts to admin. And the title here will be Admin. Let's close those files
and actually open the admin. component. html. And the first thing we need to do is add
a tabset, and inside the tabset we're going to add three tabs. Inside each tab we'regoing
to have a ng-template, and we're going to have three of them. So you can see we have a
tabset with three tabs inside, and each tab has ng-template inside of that. And
theoretically this admin screen is going to have different tabs. One to configure
locations, another one for images we might use, and another one for user management.
So for the first title we're going to give Locations. The content will just be Locations
Tab for now. Again, not very imaginative. (Typing code) The other thing I'm going to
do is I'm going to select each of the ng-templates, and to each one we're going to add
ngbTabContent. Let's have a quick look at our app currently. Let's navigate to the
Admin screen. You can see I havethese three tabs, Location, Home Images, and User
Management. Since we have Locations Tab already selected, we can see the Locations
Tab content down here. If I click Home Images, I see Images Tab, User Management,
User Management Tab. So pretty simple. I can navigate to each one of these tabs and
then the content changes appropriately depending on which tab I have selected. Let's see
how we can customize the tabs themselves. For example, this second tab for Home
Images, let's tweak that a little bit. Let's remove the title that says Home Images. And
we're going to add an ng-template. Andinside this ng-template, we're simply going to
add a span with the class of text-success. Home Images will still be the title itself. And
to the ng-template, we have to make sure we designate it as ngbTabTitle. Let's also go
to the third tab, and we'll give it the disabled attribute, and we'll set it equal to
truebecause I want to show you what disabled looks like. So now you can see we
changed the color of the text on Home Images. Behavior, of course, is the same. And
User Management's a little bit grayed out here, we can prove that to ourselves. If we
try to click it, which I'm doing, you can see that nothing changes, Home Images is
still the selected tab. So now that you've seen that, I'm going to come back and just
set this to false because we want the default behavior. I just wanted you to see a
quickexample of what disabled true looks like. Now in the tabset itself, I'm going to
add a type attribute and we're going to set this equal to pills just to see what the different
style looks like. And you can see that these are pills. Now these pills are square. If I had
been using a different theme, maybe they would have rounded corners. That really just
depends on the Bootstrap theme that you're using. And we can click any one of these
and we have the pills layout rather than the more traditional tab layout. I'm actually
going to remove that now that you've seen it, and I like the way the tabs look a little bit
better. This time we're going to add justify=start. If we come back here, you're going to
see that actually it looks exactly the same as what we had in the beginning. Because by
default, the tabs are justified to the left, which is what we mean when we say justify
start. Let's see some other options we have. Let'schange this to center. Now you can see
the tabs are in the center of the screen. All the behavior of course is still the same. Now
let's change this to end. And now we can see the tabs are all the way at the end on the
far right. Let's change this to fill. And the tabs are really wide now because they're
filling the entire span that the width has. Now this is very subtle, but I want to actually
show you that these tabs are not equal width. I'm going to Inspect right here. If I
mouse hover over this first li, you're going to see it's 339 is the width, the second one is
368, and the third one is 402 because the string user management is actually a little bit
longer, and that made the entire tab longer, so 402 is the widest tab that we have. So
when you say fill, they're not necessarily going to be the same width. Let's tweak this a
little bit. This time instead of fill, we're going to say justified. Now this time when we
comein, it actually at first glance looks very similar to fill, but there's a
subtle difference, and I'm going to once again Inspect. And what we're going to see
is now they truly are equal distance. So this first one has a width of 370, next one is also
370, and the last one is 370. So it's a subtle difference, but the difference between fill
and justified is justified will have equal width. Fill won't be quite equal because the ones
with more text is going to be a little bit wider. Now let's add an additional attribute
calledorientation. And the first thing we'll do is horizontal. And by default,
nothing changes. Why? Because the default is horizontal. Let's change this to
vertical. And now we can see we have vertically stacked tabs. Now admittedly, this
looks really weird because the contents at the bottom. A lot of times when you
have vertically stacked tabs the content will be over to the right. And of course,
we could fix this with a little bit of CSS, but for now let's just switch this back
to horizontal because we like that better.Now in addition to all these different options
you can select, you can also programmatically select a tab. So under the tabset we're
going to add a button. And we're just going to say the text on the button is Go To
Images, which is that second tab. And in this tabset, we're going to give it a template
name, #ts=ngbTabset. So now we have that ts variable we can refer to. And for each
of these ngbTabs, we're going to add IDs. This first one is going to be tabLocations,
second one is going to betabImages, and the last one will have an id of tabUsers. Now
with those IDs in place, now we can add a simple click handler to this button. And we'll
say ts, that's referring to the variable on line 1 with the #ts. And when we dot into it,
we can use the methods that the tab component gives directly. So we'll use the
select method, and we'll give it tabImages because that's the ID of that second tab. Now
we come into the screen and we can see this button here. I'm going to click it for Go To
Images. And you can see that it has programmatically selected this Home Images tab,
and we can see the content there is Images Tab. Now we also have tab change events.
So let's add tabChange to the tabset. And we'll call it beforeChange, passing in the event
object. Now let's go open this admin component. And we'll just add a method right
here. I'm just going to log into the console just because I want you to be able to see what
this event object looks like. And again, a little bit of a contrived example here, but we're
going to say if event. nextId equals tabImages, we're going to prevent this. So any other
navigation isokay, but we want to prevent navigation to that second tab for images.
We'll do F12 to look at our developer tools and go to the console. I'm going to click
this button for Go To Images, and we can seethat the before tab change event
happened. We can see we have an activeId of tabLocations, nextId is tabImages, but
the preventDefault method got called, which is why we couldn't go into Home Images. I
can click and go into User Management, it lets us do this just fine. We can also
click back to the Locations. You can see the before tab event changes to show you
what the current tab is and what the next tab is. So you can use any logic you want to be
able to navigate around to these tabs based on any conditions that are appropriate for
your application. So the tab control is extremely flexible, you can get it up and running
very quickly, you can change the styling to make it appear visually however you want,
and you can take advantage of the events to customize the behavior according to what
you want in your app.
Modal - As a Component
In the last demo, I showed you how easy it was to quickly use an inline template
to create a modal like this for our delete confirmation. But what if we want the modal to
be reusable anywhere in the app?Or, what if we want the modal to be able to implement
some of its own logic? In this case, it's really more appropriate to use a component to
create your modal. What we're going to do in this next example is this Set Performance
Targets button, when this gets clicked, we're going to launch a modal to actually set
those performance targets. Right now when we click it, nothing happens. So let's go add
that functionality now. Now the first thing I want to show is that actually in our db. json
file, we already have a section for performanceTargets right at the top. And right now,
we can see that bike is set to 1000, run is 2200, row is 1500, so we already have some
data here. And we are going to add some functionality so we can use this modal to
change this data. So let's open the workouts-api. service. And we'll add a couple of
methods to get and save the performanceTargets data. And you can see these are very
straightforward. We're just getting the performanceTargets on line 49, and then on line
53, we're setting the performanceTargets with an http. put, passing in the perfTargets
that we used. Now let's go into our workouts component, and we're already injecting
the API on line 15. I'm going to add a variable to hold my perfTargets. Initially, that's
just going to be an empty object. Now there's a couple of things that need to happen. In
the ngOnInit method, currently, when that loads, we call the getWorkouts method.
But now, we not only want to call the method to getWorkouts, we also want to get
data for our current perfTargets. So let's implement that now. The first thing I want to
do is just delete this code that's getting the workouts, and setting the data onto the
workouts array. And I'm going to replace this with a forkJoin. I'm going to import
forkJoin from RxJS. Now let's examine what this code is doing. With forkJoin, it's
enabling us to make two calls simultaneously. For example, when we used to use
promises, we would use Q. all for this type of functionality. So when we first come in,
we want to call both getWorkouts and getPerfTargets. We want those calls to execute in
parallelsimultaneously. And then we call the subscribe method. And when they are
both complete, in other words, whichever one finishes last, then we can set the results.
So line 24, we're just setting the workouts like we've always done. And on line 25, we're
setting the perfTargetsResult to the perfTargets, and then we set loading equal to false. I
have a little console log there just so we canprove it to ourselves and see the data. Now
it's time to set up the component that we're going to use for the modal itself. Once again,
I'm going to use ng g c, and we'll call this PerformanceTargetsModal.Let's open up the
HTML for this component that we just created. We'll delete the full HTML. Let's add a
header first. This looks virtually identical to the inline modal that we used previously.
The div has a class of modal-header, and inside we have an h4 with a class of modal-
title. Now let's add the body. Notice I'm putting three form groups in here. We don't
need the action attribute. Inside the first form group, you can see we have a label and an
input. The first one is for the Row Target and a textbox for that Row Target. Now I've
added the exact same thing to all three form groups, so let's fix that realquick. The
second one we'll change from Row to Run. We need to change the variable as well. And
the last one we'll change to Bike. Let's add a modal-footer. And in here we just want
two buttons. Firstbutton is for Save, second button is for Cancel. And let's add some
different coloration. For the Save button, we'll call it btn-primary, and for the
Cancel button, we'll give it a different color, in this case btn-secondary. Now this
modal component has its own TypeScript file, so let's open up that. And we'll add
a variable for perfTargets. Now remember, this perfTargets that we just added on line 9
is what we're referring to in the binding here for the modal itself. So it looks like any
other component, it has its own variables. Now of course, we need to launch this modal.
So let's go back to our Workouts screen, specifically the HTML, and we'll go up to the
Set Performance Targets button. And in this button we'rejust going to give it a click
event called showPerfTargets. And we're going to put it into our TypeScript component.
Now we're getting some red squiggles, so let's fix that real quick. Up
here,PerformanceTargetsModalComponent, Ctrl+dot, and we'll import it. Now what you
can see here is we're getting a modalRef when we click showPerfTargets, but this time
not to some inline ng template that's in our HTML. In this case, we're actually referring
to a component. And then we have similar code, modalRef. result and then to the
promise. Now right now I'm not actually saving anything yet. I have a TODO comment
for that. Right now we're just implementing the modal itself. I'm going to come in and
click Set Performance Targets. Now you might notice that nothing happened there. And
I didthis very intentionally. Let's hit F12 to look at our developer tools. And if we look
at the error, what we can see here is it says No component factory found
for PerformanceTargetsModalComponent. Did youadd it to entryComponents? I
guarantee you at some point you're going to make this mistake when you're dealing with
modals and using components formed modals. So I want to show you this here so when
this happens to you, you know exactly how to fix it. We're going to come back to
our code and open the app. module. ts. And we're going to add a section here
for entryComponents. And inside here, we're going to
put PerformanceTargetsModalComponent, and don't forget a comma at the end here.
Now back in our app, we click the button, and we can see the modal is displaying
just fine. And we can type in the Row Target, Run Target, and Bike Target. Now
notice, it's not actually displaying the current values that I showed you that we had
in the database, so we need to make sure that we can pass data into the modal, as well as
get data out of the modal once the user types something different. We'll explore that
next.
Carousel
The next component I'm going to show you is the carousel component, which
enables users to cycle through images. In our theoretical admin site here, we're going to
use this tab for Home Images because we might want to select what image is displayed
on the home page. Now this is absolutely not going to be a full implementation of this
type of admin functionality. Again, some of this istheoretical. I just want to show you
in this example how to quickly use the carousel component. The first thing we need to
do is come back here and let's just comment out this preventDefault line of code. I won't
delete it because I want you to be able to see all the code I've previously done in the
course for those that downloaded the code samples for the course. With that line
commented out, I can now get back to this Home Images tab. Now I'm actually just
going to add some images directly to the application. And I already have a folder here
for images, which I'm just going to copy and paste into the app. So I'm going to scroll
down here, and this assets folder is often very convenient for this type of thing. So
I'm just going to right-click this, Reveal in Explorer, and let's go into this assets folder
and I'm just going to paste right in there this folder called images inside the assets
folder. And this has fourimages in here of biking, and rowing, and running, as you
might imagine for our workout tracker app. I'll close that, and sure enough right here
you can see the images, bike-1, row-1, row-2, and run-1. Now let's flip over here to the
HTML of our admin component. Let's give it a little bit more space here. And we
don't need this button anymore, so let's just comment this out. Again, I'm not going
to delete it completely because I want you to be able to see the previous code that
was done in the course. And in here we'll put an initial implementation for the carousel.
Now let's look closely at the code that I just added. So starting on line 12, we have a div
for row, and then on line 13 it's just a containing div for the mainImageContainer. Now
on line 14 to 21, this is where the actual carousel component is placed. And inside we
have two ng-templates, and each one of these ng-templates has an attribute
called ngbSlide. And inside each one of these we have an image tag and you can see the
URL for each image tag. Now I'm going to go to the CSS for this admin component.
And I just want to make sure that thatMainImageContainer has a position of relative and
we'll just give it a little padding at the top of 12. And here we are back in our app. I'm
going to click this tab for Home Images. And you can see that we have the carousel
here. And notice that we have these buttons on the right here, and the left, and this will
let us cycle through the images. So there's our second image, if I click it again, it will
go back to the first one. You can also see we have these indicators down here
showing which image we're on. So this one's highlighted, this is the second image,
and then this one's highlighted, and this is the first image. The other thing that you may
have noticed while I was talking there was whereas when I first came to the screen and I
started clicking these buttons to advance to the next slide, as I'm talking it's just been
advancing to the next slide on its own. And what's happening here is by default it will
wait for 5 seconds and then just naturally advance to the next slide, it does this on its
own. So let's see how we can customize some of this behavior. First off, let's make this
a little more data driven because right now I've just hard coded two images into the
HTML. So in the admin TypeScript, I'm just going to add ahard-coded array of images
here. Then in the HTML itself, let's delete the second template here. And for the first
one, we're going to give it an ngFor, and we'll say let img of images, that images
array that you just saw me add. Then in here, the src, we'll put the square brackets to do
data binding, img. src. In addition to the image, we can put another div tag inside the
template and we'll give this carousel-caption as the class. And inside we'll put an h3
img. title, and I'll just put a p tag, cool sub-title. We cantheoretically have a different
subtitle for each image as well. But right here we have this img variable, which has an
src and a title. And just to remind ourselves, we can see right here it has src and title,
and that's where it's getting it from. Now we have four images. I can click through. Row
1, cool sub-title, Row 2, cool sub-title. So we can see that each one has their own title.
You can also see here that we have these four markers for the placeholders on the
bottom. And in fact, you can click these placeholders too. So there's multiple ways to
cycle through. I can either cycle through by clicking these arrows on the right and left,
or I can even cycle around by clicking the markers right here. Now let's see some of the
interesting ways we can change the behavior of the carousel. The first thing I'm going to
do is change the interval of the carousel itself to 500. Let's see what change that makes.
So if I click to the Home tab, look how fast the images are transitioning. Basically,
what I did there was interval equals 500 ms. After 500 ms, immediately transition to the
next one. Let's come back here and let's add another attribute called pauseOnHover, and
we'll set that equal to true. Now let's come back in again. We still have the pictures
cycling every 500 ms, but this time when I move my cursor in, it stops. Move my cursor
out, it starts again. Move my cursor in, it stops. So that's pauseOnHover. Let's change
theinterval to 0. And you can see now the images don't cycle at all. So the 500 ms was a
little silly for our example. Those pictures were just flying by. But now what you might
notice is even though I've beentalking for more than 5 seconds, the picture hasn't
advanced at all. So now, we are only manual advancement. So I can click this myself, I
can click the indicators, but it, by default, will not just start cycling through by
itself because we've set the interval equal to 0. Now let's add another attribute. This
time we'll do showNavigationIndicators, and we'll set that equal to false. This
time when we come into the screen, notice we no longer have the four navigation
indicators towards the bottom of the image. I can still cycle through with my
navigation arrows, but we don't see where we are anymore because those
navigation indicators are gone. Let's add another attribute. And I'll go down to the
nextline for readability. And we'll say showNavigationArrows, and we'll set this equal
to false. Now we come in again, not only are the navigation indicators gone at the
bottom, but the arrows are gone on thesides. Now, of course, this isn't very helpful
unless you were, for example, to perhaps externalize your buttons. But for right now,
this just doesn't help us at all, so let's flip that back. So what we're going to do is
NavigationIndicators, we're going to set that to true, but we're going to keep
NavigationArrows to false. And this is because I'm going to show you how you can do
navigation with your own buttons. So first off, let's just check that yes, we don't have
the arrows, but we do have the indicator, and we actually can navigate with the
indicators if we want to. But let's add some buttons that let us navigate
programmatically ourselves. So right under this row, we're going to add a new row. And
inside this row we're going to have a couple of buttons. (Typing code) Let's have a
quick look at the app. And you can see we have two buttons. They don't do anything
yet, but they're right there below the carousel. Let's add click events for each of these
buttons. So to the Previous button we're going to add an event called, you guessed it,
Previous. And let's copy this, and add it to the next button, and change the name to
next. Then in the admin component, we're going to use a ViewChild for the
carousel itself. Let's resolve that import for ViewChild. Notice I'm getting a reference to
it with imgCarousel. That means to the actual carousel element we need to add that ID,
so we'll say #imgCarousel. Back in the TypeScript file let's add the methods for
previous and next. Okay, looks pretty straightforward. Let's have a look at our app. So
here we are in our app. We'll click Next, Next again, and every time we click Next, we
can see it'll advance to next image. Similarly, Previous will allow us to click the
previous image. So here we have navigated programmatically using buttons we created
ourselves rather than the buttons that came on the carousel image. So the carousel image
is extremely flexible, very quick to implement, and you can use it in many different
scenarios.
Informational Components
Overview
In this module, we're going to cover informational components in the ng- bootstrap
library. These type of components are actually extremely simple to use, and we'll use
them when we just need to give some quick guidance or information to our users. I'll
start out by showing you the alert component. This is useful anytime we need to give
the users a quick pop-up message. Next, I'll show the tooltip component, which
is typically shown on a mouse hover over a particular control or object. After welook at
the tooltip, we'll examine the popover component, which is almost identical to the
tooltip, but it just gives a few additional features like a title. We'll end by looking at
the progress bar and how it can be used to show a great visual indicator of progress to
our users.
Summary
That concludes this module on working with informational components in ng-
bootstrap. What you've seen is that these informational components can provide
quick and visual guidance to the user whenneeded in your app. I first showed the alert,
and we applied it to the validation for our distance. An alert can be configured to be
closed by the user, or not. You can also have self- closing alerts. You can also change
the visual appearance of alerts. Tooltips are typically used when mouse hovering over
acomponent that you want to provide context sensitive guidance for. You can
customize a visual appearance and configure the behaviors. I then showed you the
popover, and we saw how the implementation for the popover was virtually identical to
the tooltip. Unlike the tooltip, a popoverenables you to have a title. Also, the default
behavior is a little different than a tooltip because it doesn't appear on mouse hover by
default, although, we can configure it to appear on mouse hover.We can also hook into
custom events and change the visual appearance. The last component I showed in this
module was the progress bar. The progress bar is great for being able to show a visual
indicatorto your user of percentage of task completion. Although it's a 100-point scale
by default, you can change the max value so any scale will work. You can dynamically
change the color based on anything, such as percentage complete, and change other
visual aspects of this component.
Alert
We're going to use the alert component to make some enhancements to our
workout Entry screen. If we look at the Distance textbox, we want the users to enter
a number, but right now there's nothingstopping them from putting in letters like this.
Let's add some simple validation with an alert to make this better. Now here on line 58,
we have the input for the Distance textbox. What I'm going to do is I'm going to come in
here and we're going to add a required attribute to this. We're also going to add a pattern
attribute, and for the pattern we're going to say it must be a digit and the plus means any
number of digits. Then right after this form group row for the Distance textbox,
we're going to add a div tag. Now let's look at the details of the snippet of code that I've
just inserted here. We're going tosee that on line 62 we have an ngIf directive that says
if it's invalid and it's dirty or it's been touched, then let's show this div tag. Now inside
the div tag we've got two ngb-alerts. The first one there has a type of danger, so that will
show a certain color, and then the second one we can see is type ofwarning, and that
will show a different color. And we have different If conditions. The first If condition
says that if the errors has the required property then let's show this alert, and we can see
the text of this alert is Distance is required. And the second one will show up if the
pattern has been violated and it will show Distance must be a valid number. Now one
other thing we need to do to make this work is you can see I'm referring to wdistance in
these ngIf directives. We need to actually add this template identifier to the textbox. So
I'm going to come up here to the textbox, and I'm just going to put this ona new line for
readability, and we'll say #wdistance=ngModel. And now let's look at our app. Here we
are back in the app. I'll go to the Distance textbox. And let's type some letters, and we
can see that weimmediately get Distance must be a valid number, and we can see it's
that yellow color for the warning. If I take this away, it then says Distance is
required, and now it's the red color. Another thingto point out is that we have an X here,
we can click this to close it. But actually, as I'm clicking it nothing's happening, it does
not dismiss. Let's take a closer look at this. And to the first alert, I'mactually going to
add an attribute called dismissible, and we're going to set it equal to false. So let's come
in here and put an alpha character. We can see everything looks the same, I still have the
close button. Now if I take it away, this is where we said dismissible equals false, so it
doesn't show the button at all. But what if we did want to let them close it? Let's see
how we'd implement that. Now I'm actually going to add an alert on top of these first
two so we don't mess up what we already have going. I'm just going to create a new
one. And inside we'll just put some text, Dismissible Alert, pretty boring. And we'll say
dismissible is true. I'm introducing a new variable here, isDismissible. And we'll add a
close handler to this alert. So when it closes, we'll just set it equal to true if it's false and
false if it's true. So first, we'll actually make sure the div is showing. We can see this is a
Dismissible Alert, so this time if we click the button, it actually does dismiss. This
one still doesn't work because we did not add a close handler to this one, but for the first
one you saw an example of how you could give them the option to close it you want.
Because we have this close event, we can also create a self-closing alert. All we have to
do there is use the set timeout for the given number of seconds, and then set the Boolean
value after 5 seconds or whatever we want the interval to be. Now one other thing you
can do with these alerts is you can create custom CSS. In this case, I just used danger
and warning to use the out-of-the-box red and yellowish orange. Let's say we want
a completely different color. In this casewhat I'm going to do is come into the CSS and
let's add alert-custom. And in here, we'll just say background- color is purple. Then let's
come back to the dismissible alert, and for this one we'll say type=custom. This will
force it to use the alert custom class that we just specified in our CSS. And now we can
see that when it pops up we have this purple alert. So we can customize the
styling however we want. The alert component is really easy to use and you can use it
in many different circumstances. You can enable the users to close it or not, and you can
also do self-closing alerts after a period of time. For our purposes here, I'm just going to
comment out this Dismissible Alert so you can still see the code, but it doesn't actually
help with our workout tracker app.
Tooltip
The tooltip component is really useful when you want to be able to provide the user a
quick guidance. Most often you would see it used when you mouse hover over a
component like a button, or really any object on the screen. What we're going to do is
add a tooltip to this Add New Entry button over here to provide a tooltip when the user
mouse hovers over it. Right here on line 29, we have the Add New Entry button. I'm
going to add ngbTooltip inside of this. In fact, I'm going to put it on a new line
forreadability. And we'll just say ngbTooltip, and then we'll put our message inside. So
we're just giving aninformational message to help the user understand what this button
does. Coming back into our app, if I mouse hover over the Add New Entry button, we
can see the tooltip right on top of the button. Now notice that the tooltip appeared on top
of the button, and this is the default behavior. But what if we want to change the
placement? I'm just going to come in here and say placement=left. And as youmight
expect, we can do placement=right or bottom as well. And now when I come back into
the screen, I hover over it and we can see the tooltip appears on the left side. Now by
default, this tooltip doesn't look very exciting. We have a black background with white
letters. What if we want to change it up a little bit? So right under this button, I'm going
to add an ng-template, and we'll put the messagein here. You'll notice it's the same
text, but I put the word workout inside a strong tag, and I've changed the color via text-
primary. I'm also going to convert it to uppercase with text-uppercase. Let's give this a
template variable here, ttContent for tooltip content. I'm going to copy this, and now
what we're going to do is for ngbTooltip up here, I'm going to put this in square brackets
for data binding. Instead of data binding directly to this text, we're going to data bind
to the variable ttContent like this. Now when we come back into the app, I'm going
to mouse hover over, and we can see that sure enough the word WORKOUT is in a
different color and it's all uppercase. Now let's say we want to actually integrate
some custom CSS as well. I'm going to open the workouts. component. css, and I'm
justgoing to add a little CSS that changes the background color to this light blue.
Then inside the original button, we'll say tooltipClass=custom-tooltip because that was
the name that we just gave it in theCSS. The other thing we need to do in this instance
when we're using CSS in this way is go into the containing TypeScript component, and
inside the component, we have to specify an encapsulation to be equal to
ViewEncapsulation. None. We'll resolve it, and there we go. This make styles available
to the child component, in this case, the tooltip. And now when we come back into the
screen let's hover over the button, and we can see our blue background. Now granted,
I'm not a visual designer here and this may not be the best color scheme, but I'm just
making it an obvious blue color so you can see what's happening for the demo. Now up
until this point, I've just been showing you how to show the tooltip on mouse hover, but
we can actually use custom triggers as well. Let's take a look at that. Under the ng-
template, I'm going to add a button, and inside this button we'll just say it's Tooltip Test.
And I'm going to add a tooltip to this button directly, but this time I'm actually going to
give it sometriggers. Now what you can see here on the triggers is I've said click and
blur, so it's not going to show on hover, but it'll show when it gets clicked or blur when
it loses focus. So here's our button, TooltipTest. You can see I mouse hover over
it, nothing happens. I'm going to click it, and then we see just a test it shows. Even when
I go away from it, I mouse hover off of it, it still shows up. But if I click away from it,
then the blur event kicks in and will hide itself. Now some of those triggers, for
example, the click and blur that I just showed you, are built in, but you can also change
the triggers to manual and write code yourself to control this. So in the triggers, we're
going to change this to manual. Back in the app, notice that even as I'm clicking it
nothing's happening, and, of course, mouse hovering over it doesn't get it to show either.
Now back in this button, we're going to add a couple of things here. First, we'll add
a template variable here, we'll just call it t for tooltip, ngbTooltip, and then the click
event for the button itself, t. open. So the tooltip has various methods. One of those is
the open method, and we're going to call it directly. Now because it's a click event, I can
call any method I want and pass context like a parameter, but in this case, I'm just
calling open. Now come over to the Tooltip Test, mouse hovering doesn't open it. If I
click it, this time it does because I called t. open. Now if I click out of it, then
it'll disappear. Now I can also change the behavior of how it closes. I'm going to add an
attribute called autoClose, and I'm going to set it to a value of inside. This means that it
won't close until we actually click inside the tooltip. So I come back in, I'll click the
button, sure enough there it shows. I'm going to click outside, but it's actually not going
away. Now watch what happens when I click inside the tooltip itself. Now it goes
away. That's the result of us setting autoClose equal to inside. It'll also close when I hit
the Esc button. So even though I'm out here and I'm going to hit the Esc button on my
keyboard, and it also goes away there. Now I'm going to comment out this green button
because this was just a test on how to use it, we're not using it for the app. I just wanted
to show it there to illustrate some of the additional capabilities of the tooltip, but
again, I'll leave it here commented out instead of deleting it so for those of you
who downloaded the code for the coursesamples, you can see the syntax. Tooltips are a
quick and easy way to add context and help your users at any point.
Popover
We just finished implementing the tooltip right here on the Add New Entry button. Next
thing we're going to do is popovers. Popovers are almost identical to tooltips both in
function and in implementation. The differences are quite subtle, but here are some
things to consider. Tooltips areusually small, quick hints, and appear on hover.
Popovers have an explicit title area and get additional related content. They're generally
bigger, and you typically dismiss them with clicks rather than hover status. Popovers are
almost a little more like modals. But having said that, they're virtually identical
inimplementation, and most of the time you can really pick which one you want to
use based on your visual preference. When we come in here to the Entry screen,
we're going to add some popovers to give some additional context to some of
these buttons on the screen. Specifically, we're going to help the user understand
what these three buttons at the top are. So right next to those three buttons for
Run, Bike, and Row, we're going to add a new button. And we'll just put a question
mark inside that button. By default, we specify text the same way we did in the
tooltip, but we also include a title. We'll just call this Activity Type. Then we'll specify
the actual content. Now let's look at the app. We come back into the screen and we can
see our little button here with the question mark. I'm going to click it.The popover will
appear automatically on click. And you can see that we have the Activity Type in this
title area up here, and then the content area. These buttons specify the type of activity,
so it's a little help button that helps you understand what these orange selections are.
Notice that unlike the tooltip, we don't see it automatically by hovering. By default, it
actually appears on the click. Just like the tooltip, we can control the placement, and by
default, it shows up at the top. Let's come in here and say placement=right. Now if I
come in and click it, we can see the placement shows up on the right side of the button.
Now one thing you need to make sure is if you have enough room in the columns. In
this case, I certainly have enough room. We can see this is appearing, no problem.
But what if we change the width of the column? Right here, the column is 6, let's
change that to 3. Now when we come back into the screen and click it, we can see that
it's actually still adhering to the width of the column, we don't have as much room now.
So this is one thing to watch out for when you're using the popover. Let's change this
back to 6, and now it's wide again, just how we want it. Now you canactually use
custom HTML, as you might expect. Let's go do that next. We'll add an ng-template,
inside we'll put an h4 Activity Type. And we'll give this a name of popTitle. Let's add
another template below it, and let's call this popContent. And I just put some text in
here. These buttons specify the type of activity, and you can see we have some custom
styling on the word activity. So for the title, I'm going to copy popTitle. I'm going to
make sure that this is inside of square brackets now, and we'll paste it right in there.
Similarly, we'll take the popContent, again, square brackets so we have data binding.
We can take this entire message out, paste it in there. Now if we come into the app, we
can click it. Activity Type is now very large because it's in the h4, and we can also see
that we have the custom HTML with the custom styling on the word Activity. We can
also incorporate custom CSS. We'll come into the CSS here of the component. We'll
add a background for custom-popover and then the header's here. This one's kind of a
light blue, which we can see, and this one's a green. Back in the HTML, we're going
togive it popoverClass custom-popover. Just like we did in the previous example,
we have to come in here and specify the encapsulation. Again, this makes
styling available to child components, in thiscase, the popover. Now granted,
these styles might be a little bit much. The green and blue are frankly a little obnoxious.
Like I said, I'm not a visual designer. Mainly here I just want to make it super obvious
what's going on. You can put your own tasteful colors in here. I also want to briefly
show you triggersbecause this is very similar to what I showed you in the tooltip. This
time instead of click and blur, I'm going to use mouseenter and mouseleave. Back in
the app, now it shows up on mouse hover, and it disappears when it goes away. So now
it actually is acting a lot like a tooltip. Just like tooltip, you can have manual triggers as
well with custom methods. It's identical on implementation, so I'm not going to repeat
everything here. The example I showed you in the tooltip will be identical to the
example that you would use with popover. But now the last thing I want to show you is
visibility events, which you didn't see previously in the tooltip, although they are
available in the tooltip. So I'm going to add to thisbutton a title of pop, and then I'm
going to add a couple of visibility events. First one is shown, and we'll send
the component actually into this method as a parameter. And I'm going to copy
this entire thing, and paste it here, and change shown to hidden. Now let's copy this
method right here and go into our TypeScript file, and implement this method down at
the bottom. And the only thing I'm going to do is log a message to the console. We'll
come to the screen one final time. I'm going to hit F12 to see the console. I mouse hover
over, and we can see the Popover open state is true. Let's mouse hover away, and we
can see it gets set to false, and we can see the message on the screen, true, false,
true,false. So this is another example of how you can actually attach the visibility events
if you need it in your app. When you think of the tooltip and popover components, on
the surface, they actually are extremely simple. But ng-bootstrap actually provides
several robust implementation methodologies for you to interact with these in a very
rich way.
Progressbar
Often in our apps, we want to be able to show progress. For these circumstances, the
progress bar that comes with ng-bootstrap is a great option. Take our Workout screen
for example here. We havethis Set Performance Targets, and we can open it to see what
our targets are, but we have no way of seeing what our current progress is against these
targets. Let's use the progress bar to give our users abetter experience. The first thing I'll
do is come in to the workouts. components. ts file, and let's add a property for
totals. Initially we'll just set it here to an empty object. I also want a method to calculate
the totals, and that's the method I did right here. And then actually, I can see I have
some red squiggles here, and that's because I said total instead of totals, let's put an s
at the end there, and let's come back here, and that's gone. And you can see that all I'm
doing here is using lodash to get the list of all the workouts, and then to get the
bikeTotal, I'm filtering it by the type equal to bike, and then I'm summing up the
distance and getting the value. And I'm doing this for each one, the bikeTotal, rowTotal,
and runTotal. And then on line 60, I'm setting it all into this totals object. When the page
firstloads, I want to make sure this also gets calculated. So we'll come in here and we'll
say calculatePerformance, because at this point we'll have already loaded the workouts
data into our screen. Now that we have those numbers to get the totals, let's go into our
HTML. We've had thiscomment here, this TODO item for our progress bar, we can now
delete this TODO comment. In its place, we'll put an hr, underneath we'll put a p tag,
and inside the p tab, we'll have ngb-progressbar. And I'm just going to set the value
equal to 50 for now. Now so far, this isn't very impressive. I just have this orange
bar, and it's spanning 50% of the full width of the screen. If I change this value to
100, you can guess what's going to happen. We reload the screen, and now the bar
spans 100% of the width of the screen. We can also come in here and add an
attribute called showValue, and we'll set it equal to true. And we come back into the
screen, it actually will show us 100%. Now let's implement some actual functionality.
Let's set this value to totals. bike. Now if we look here, it's still showing 100%. If
welook at the bike total, which I can see down here on my console, the bike total is 301,
which is greater than 100, so it will set it as 100%. So we need to make this relative.
We're going to set a max value, and this max value will be the perfTargets, specifically
the bike target. Now it's showing the appropriate amount because we said the total for
the bike was 300, and if we look at the bike target, it's 1, 000, so yeah, that's 30. 1%.
So far, so good. But we need two more progress bars because so far all we have is the
bike, we need the row and the run as well. So let's copy this and paste it twice. We'll
make this one for row and this one for run. Okay, now we're getting somewhere. We
have percentages for each of the three performance types. Let's also see how we can
change the height. I'm simply going to add a height attribute, I'm going to set it equal to
30px. I'll just do it for the first one. We come back into the screen, and we can see that
this first one has 30px, and we can see it's got a greater height than the other 2. Let's
actually set them all to 20. Now we come back into the screen, and they all have the
same height again. We can also apply custom labels. So inside the first one, I'm going to
actually make a label, we'll explicitly say that it's for the bike. And what we want to
have here is we want to say this is how many meters we've completed of the total. I'm
also going to remove showValue because we have a custom label now. And if we look
here, we can see it shows the first one is for bike and says 301 of 1000. We can see
exactly how far we are to our target. Let's do a custom label for the other two. And
I'll just fix up the formatting a little bit to make it easier to read. So inside each p tag, we
have the progress bar, first one for bike, second one for row, and third one for run. I also
have to make Ieliminate the showValue for the other two just like I did for the first one.
And when we come back into the screen, we can see the custom labels are complete.
Now there's one more thing I want to do here.The width of the progress bar, of
course, is reflecting the percentage. So with the bike, it's about 30%, we can see the
bar expands about 30% of the screen. With the run, it's almost at the end, so it's almost
100%. But wouldn't it be even better if we could change the color based on how close
we are to the target? Let's do that next. I'm going to come into the TypeScript file for
this page, and I'm going to add a quick little method here called getPBType, get
progress bar type, and we'll do this based on percentages. So for each one, we'll take the
total divided by the target times 100 to get thepercentage. If it's less or equal to
25%, that's going to be success, otherwise between 25 and 50, info, then warning, then
danger, just to make the color a little bit more intense as we get closer to our target.
Flipping back to the HTML, let's give each one a type. So instead of hard coding a
Bootstrap class like success or warning, we're actually going to call this getPBType
method, passing in theappropriate values. So this first one here is for bike, so we're
passing in the totals for bike and the perfTargets for bike. Let's copy this and let's paste
it down here. And, of course, we need to change this bike to row. Same thing
down here, let's change this to run. And now coming back to the app, everything
looks exactly like we would expect. We aren't very close here, so it's blue, we're getting
closer, yellow, we're getting really close, it's red. And this will change dynamically. So,
for example, thebike is 301 out of 1000. Let's say that we change the target from the
bike, instead of 1000 to be 305. And we can see that has gone way up because we're at
like 98, 99%. And we can change it back. So really nice feel to get those dynamic,
visual indicators. We can do some additional styling as well. Just to the first one only,
I'm going to add striped=true. And when the app reloads, you can see we havethis
striping that's now applied to this first one. We can also say animated=true. And when
the screen reloads, we can see that we have a little animation effect that's going on right
now. Now I'm actually not going to use either of those attributes in our app, striped is
true or false and animated is true or false. I just wanted to show you that they
were options there, even though we're not using them in our app. So now we've
got everything switched back to the default. So in just a short time you've seen how easy
it is to work with progress bars. They'll automatically span the width based on the
percentage of how complete you are to the target. You can make them
relative, although, by default, they use a scale of 100. You can also dynamically change
the colors, as well as any other customstyling that you need to apply to these progress
bars.
Navigation Components
Overview
In this module, we're going to finish off our exploration of ng-bootstrap with a look at a
couple of components that are used in navigation scenarios. The dropdown component
is actually a pretty simple component. It provides the ability for a regular button to
transform into a drop-down button. I could have included this in the module for basic
forms, but I decided to include it here simply becauseyou often see this type of control
being used in navigation scenarios. The pagination component enables users to
page through large data sets. We'll apply this to our workout tracker app as our data
starts getting large.
Summary
In this module, we covered a couple of components related to navigation. I first showed
you the dropdown component, which transforms a regular button into a drop-down style
button. This is most often used in navigation scenarios, but it can also be used in
command button scenarios. You can just use a regular button and I also showed you
how to do a split button. We ended by looking at the pagination component. The
pagination component makes it really easy to implement paging for large data sets with
some very intuitive visual controls. We can control the page size and the number of
pages shown. The pagination component is flexible enough to support any style of
paging, both client-side and server-side, and I showed you examples of how to do both.
This concludes the course on ng-bootstrap. Thanks for watching, and I hope
you enjoyed the course. This library makes working with Angular and Bootstrap
really fun, and enables you as a developer to build amazing apps that will delight your
users. Happy coding.
Dropdown
I'm going to add a drop-down button to our Home screen here. Now this demo's going
to be somewhat contrived. Because we're not building a production app, I want to
focus this demo on the mechanics you need to implement this button. But the idea
here is that we're going to add a button to the Home screen that would provide some
basic screen navigation. Here I am in the HTML of the Home screen, and I'm just going
to, right under the Welcome to Workout Tracker message, I'm going to put a button
right here, starting with a div tag. Now to this div tag, I'm going to add an
attribute called ngbDropdown. Then inside the div tag, I'm going to add a button. We'll
say that the button's called Navigation, and to this button we'll
add ngbDropdownToggle. Under this button, we'll have three items. And inside this last
div, we'll put ngbDropdownMenu. And I can see I made a slight typo here, so let's
remove this w before we look at the app. Now we're back in our app. We see
the Navigation button. So the top level button, remember, the title I gave that button was
Navigation. We can see we have a little down caret next to it. I can click that, and then
we can see the menu that pops out: Home, Workouts, or Admin. So it's as if we're
providing navigation to go to any of our screens up at the top. And in fact, you often see
these drop-down menus and these buttons placed in the navigation menus in the nav bar
at the top. Now the default behavior was to put this little arrow inside the button, and we
can click anywhere in this button and it'll expand. But let's see what it looks like if we
implement this as splitbuttons. So to the first div, I'm going to remove the
ngbDropdown attribute, and I'm going to add a class here for btn-group. To this first
button, I'm going to remove the ngbDropdownToggle, and under this button we're going
to put a div that is of class btn-group. And we'll add a button in here. Notice it has a
class of dropdown-toggle-split. And we're going to add the attribute
ngbDropdownToggle. Nowwe're going to take this existing div that has the
ngbDropdownMenu, and we're going cut and paste it right under the button inside this
second btn-group. Now the final part is the btn-group that's nestedinside, this is the one
where we have to say ngbDropdown. With all this in place, let's go have a look at our
app. Here we are back in the app. And at first glance, it looks pretty similar in that we
havenavigation and then the down arrow right here, but if we mouse hover over, you
can actually see this is a distinct button. And if we click this first button,
nothing happens. But if we click the second button, we can see the drop-down
happens off the second button. Now we haven't actually attached any behaviors to
this, which is why when I click it nothing happens. But we can do navigation
directly from here. Another thing we could do is let's say we weren't doing navigation,
but maybe it was a command button. If I click Workouts, we could write code such
that the last command that was selected is whatgot added to this button right here.
So, for example, if we click Workouts, then we could code the app such that okay, if
I now come in and click this button, it's Workouts, or I do Admin, then the next time I
come in here it's Admin. Really, there's a lot of flexibility for how you can implement it.
But this is an example of how you can visually do split buttons with the dropdown
component in ng-bootstrap.
Pagination
The last component we're going to look at is the pagination component. This is critical
when we have lists that get really large. Now up to this point, we don't really have a lot
of entries for our Workout Entries, so let's go rectify that right now for the purpose of
this example. Back here in our app, I've been using this db. json file the whole time in
lieu of my web API and database server. Let's open this up. And I'm just going to paste
a new file in here called big-db. json. The next thing I'm going to do is I'm going to
come in here and I'm going to stop my JSON server and I'm going to restart it, but this
time instead of pointing to that db. json file, I'm going to point to the big-db. json file.
Now when we come back into the app, actually we can see right off the bat, the progress
looks way different. And as we scroll down, we can see we have a lot of entries now on
this grid, and this is not the most ideal userexperience. We don't want the users to have
to scroll this much when using our app. This is a great example where we can bring in
pagination. So as a first step, here we have the table where we'redisplaying all this data.
Let's add a pagination control here, starting with the div. Inside this column, we'll
put ngb-pagination. And we'll start out with a collectionSize=workouts. length. This
is just the size of the array that we've already retrieved from the web API. Now we've
got a basic layout here, and you can see that it automatically has 5 pages, and by the
way I have 50 entries in this workout array. So by default you can see that we have
pages of 10. Now I actually haven't implemented any filtering yet,which is why we still
have a really long scrolling list, but we'll get to that in a minute. But for now, let's just
focus on the pagination control itself. Now one thing you can see is as I click button,
it advanced to the next one, which is 2, click it again 3, 4, 5. And then I can't go
anywhere past 5, but I can use thisother button to go down by 1. Let's add a new
attribute for boundaryLinks and set that equal to true. And this time when we come back
in, we can see that we have not only that same link that could advance by 1, but we also
have these boundary links with the double arrows. If we click that, it'll jump us right to
the end, or right to the beginning. Now as we saw, we had a page size by default of 10,
but we can actually control this. I've set the pageSize attribute equal to a
pageSize variable. We have to go add this to our TypeScript, so let's just add it
right here. And by default, let's set it equal to 5. When we come back into our
app, suddenly we can see that we have 10 pages instead of 5 because instead of
the default page size of 10, now it's 5. We know we have 50 entries, so the idea is that
we have 10 pages, 10 times 5 is 50. Now as the entries keep getting bigger, we may not
want to show every single page. I mean, think if we had 100 pages, that wouldn't make
any sense. So we can set a max size. Let's come in here and set a maxSize=5. And now
when we come back in, you just see that we have 5 and then some ellipsis, and then
we'll show the page at the end of 10. I can still go through each one, I can count up by
one, one page at a time. Now notice as we were doing that, it went all the way to the
end. We can actually keep that in the middle. Let me show you what I mean by that. I
have another attribute where I can say rotate=true. By rotate=true, that means the other
pages will rotate around the current page and keep the current page in the middle. This
will be particularly obvious when I'm on pages 5, 6, and 7. So I go up by one at a time.
I'm on page 4, now I go to page 5. Notice the green indicator of the current page is
still right in the middle. I'm going to click it again, it's still right in the middle, except
we've rotated the numbers. So 6 is now in the middle and 4 through 8 are visible. We
click it again, and once again 7 is right in the middle. So when we say rotate=true, it'll
make every effort to keep the active page in the middle of the pagination list. Now that
we're getting familiar with the basic functionality of the pagination component, let's
start implementing some real functionality so that we can actually filter by page when it
navigates. First thing is to set this page attribute, and we're going to data bind it to a
variable called currPage, for current page. We're also going to give it a pageChange
event, and we're going to set that equal to a method called refreshGrid. Now we need to
create the variable for currPage and the method for refreshGrid in our TypeScript, so
let's do that now. Of course, by default, we want currPage equal to 1. We also have to
do some filtering here. So what I'm going to do is I'm going to use two different arrays.
I'm going to create a new array called workoutsOrig, and we're going to set that equal to
the original full array that was loaded when the screen first loaded. And then
this variable that we had to begin with, workouts, this is the variable we're actually
going to filter as we navigate through our pages. Let me show you what I mean. Instead
of sending workouts to the workout results from the HTTP web API, we're going to set
it to workoutsOrig, and then right after that, we're going to call refreshGrid. Now this
method, refreshGrid, doesn't actually exist yet, so let's add it now. If we look at this
method, it's only two lines of code. If we have, in our case, a 50-item array, we need to
know which page we're going to show from this array. This is going to based on
the current page and the page size. So we know the current page is going to be
correct because we have a two-way data binding set up for the current page in that
currPage variable. And because we know we need a 0-base index, we start out by
saying currPage - 1, then we multiply it by the page size. So if our current page is 2,
we subtract it by 1, 2 minus 1 is 1, then we multiply 1 times page size of 5, so the offset
would be 5. Now on line 41 is where we actually need to do the filtering. I'm using
lodash here, and the drop method will essentially drop everything that comes before the
offset. So we take the original array, the workouts array that has everything, and we
drop the offset. So if we dropped 5, we dropped the first 5, or maybe we would drop the
first 25, whatever that offset calculated out to be. And then the slice will take however
many after that offset. So if the offset was 5, we start at 5. If the page size was 5, that
would be from 5 to 10. Or if the page size was 10, it would go from 5 to 15, whatever
the case may be. So the slice will take just the number that we want, in this case, page
size. The final thing we need to do before we run it is we need to set the collectionSize
now equal to workoutsOrig. length because this is going to be the total size of the array
itself. Now let's have a look at our app. Now first off when we come in, we can see
that we have 10 pages just as we expect because we gave it page size of 5. And
sure enough, now we see our grid only has 5 items on it. And to make this really easy, I
just did incrementing dates, so we have September 1st to September 5th. Let's go to
page 2. Now we have 6 through 10. Page 3, 11 through 15, and so on and so forth. And
we can skip to anywhere in here, so I can go all the way to 10 and I can see the last 5.
So this enables us to do paging, and we can jump to any page. Because we know the
total number, we know how many pages to display, and our filteringmechanism makes
this really easy. Now one important note here is that in this example, I did client-side
filtering. Meaning I got the entire data set down to the client, and when I did that
filtering method, I just did a lodash client-side filtering method. Well, that's pretty easy.
I only had 50 items in the array.Even if I had 1000 items in the array, that would have
been pretty easy. But what if you're in a situation where you have 10, 000 items or 100,
000 items in the array. It's absolutely not practical to bring all those items down, and
then filter them in JavaScript. In that case, you want server-side filtering. Well, ifyou're
using server-side filtering, actually the implementation barely looks any different when
you're using the ng-bootstrap pagination component. Really, the key is you want to
make sure that yourserver-side API supports server-side filtering. Remember, we're
using the JSON server for our HTTP web API. And when we click the results of getting
all the workouts, we get all the workouts, so we get all 50. But this API actually
does support server- side filtering as well. I can do this by just coming in here
and giving some query string parameters. I can say _page=2, and I can also say
_limit=5, meaning I want a page size of 5. If I do that, now it'll get me just page 2 with
a page size of 5. So we can see we have 9/6, and as I scroll down, we can see
9/6 through 9/10. And sure enough, if we go to page 2, we can see we have 9/6
through 9/10, so we get the exact same results. Now one thing to note is if you are
doing server-side paging, not only do you need to be able to specify the page number
and the page size, but you're also going to need to know the total number of items
if you're hoping to give your users the ability to see what the last page is. For example,
if they wanted to jump to the last page. So that's an API consideration you'll need to
take into account. Now that we've seen how we can do server- side paging with our
JSON server library, let's do a quick implementation with it. Now the one thing is that
JSON server doesn't support giving you a max count, so we'll have cheat a little bit for
our demo for that one, but I can show you everything else. Now the first thing I want to
do is I'm going to go into our service, and we already have this method here
for getWorkouts, but I want to add a secondone here for getWorkoutsPaged. You
can still see it's calling the workouts endpoint, but this time it's using the page and limit
query string parameters just like you saw me doing on Postman. With that in place,
there's only a couple changes we need to make. For refreshGrid, I'm going to comment
out these lines completely since we're no longer doing client-side paging. In its place
we'll say api. getWorkoutsPaged sending in the currPage and the pageSize. And we can
use our original workouts array. We don't need workoutsOrig anymore because we're
no longer holding the entire data set in anarray in memory. This means on line 30 I can
change this from Orig back to regular workouts. And instead of calling getWorkouts,
right when the page loads, we're going to call getWorkoutsPaged. And, of course, we're
going to pass in the currPage and the pageSize right here. Now we no longer need to
call refreshGrid, so we're going to comment that out because right from the initial page
load, we're getting page results. So we're getting those page results by default,
currPage and pageSize are set to currPage of 1 and pageSize of 5 by default. So we're
getting page results and setting it right to that workouts array. And that's it, that's
the only difference we need to make. I mean I could come in here and comment
out workoutsOrig completely just to prove that everything still works. The one thing
we have to cheat on because the JSON server library doesn't support a max count is
I'm going to have to come in here, and for the collectionSize where we said
workoutsOrig. length, we're going to have to hard code this to 50. This is just a little
cheat I'm going to do for the demo. In a real-world app, of course, I would want my web
API to support sending me a total, so I could do this paging easy. Now let's go back to
the app. The app loads, and we can see we're on page 1 by default, it just has 5 items.
We go to 2, to 3, to 4, we can jump all the way to 10. There's maybe a slight delay, but
it's actually doing server-side paging now. Let's prove this to our self by opening the
network tools. Let's go to page 2, page 3, page 4, page 10, and you can see the results
right down here. So whether you're doing client-side paging or server-side paging,
the pagination component of ng-bootstrap makes this super easy.

You might also like