Getting Started With Angular (2023) Create and Deploy Angular App
Getting Started With Angular (2023) Create and Deploy Angular App
Getting Started With Angular (2023) Create and Deploy Angular App
Angular
Create and Deploy Angular
Applications
Introduction�������������������������������������������������������������������������������������������������������������xv
v
Table of Contents
String������������������������������������������������������������������������������������������������������������������������������������� 25
Array�������������������������������������������������������������������������������������������������������������������������������������� 26
Any���������������������������������������������������������������������������������������������������������������������������������������� 26
Template Strings������������������������������������������������������������������������������������������������������������������������� 27
Manipulating Strings������������������������������������������������������������������������������������������������������������� 27
Functions������������������������������������������������������������������������������������������������������������������������������������ 30
Arrow Functions�������������������������������������������������������������������������������������������������������������������� 36
Destructuring Objects and Arrays����������������������������������������������������������������������������������������������� 40
Interfaces������������������������������������������������������������������������������������������������������������������������������������ 44
Classes���������������������������������������������������������������������������������������������������������������������������������������� 46
What Is a Class?�������������������������������������������������������������������������������������������������������������������� 47
Inheritance���������������������������������������������������������������������������������������������������������������������������� 53
Abstract Classes�������������������������������������������������������������������������������������������������������������������� 60
Classes and Interfaces���������������������������������������������������������������������������������������������������������� 61
Promises������������������������������������������������������������������������������������������������������������������������������������� 64
Decorators���������������������������������������������������������������������������������������������������������������������������������� 70
Summary������������������������������������������������������������������������������������������������������������������������������������ 72
Questions and Answers�������������������������������������������������������������������������������������������������������������� 73
Questions������������������������������������������������������������������������������������������������������������������������������� 73
Answers��������������������������������������������������������������������������������������������������������������������������������� 73
vi
Table of Contents
vii
Table of Contents
viii
Table of Contents
ix
Table of Contents
Index��������������������������������������������������������������������������������������������������������������������� 367
x
About the Author
Victor Hugo Garcia has a Master’s degree in Computer
Science, and more than 12 years of experience as a full-stack
developer, using different frameworks such as Angular,
Laravel, Yii, Zend, Cake, and Vue. He has developed multiple
web and mobile applications for various organizations. He
has also developed various courses on web development for
Udemy. He loves teaching, reading, and writing technical
and fantasy books.
xi
About the Technical Reviewer
Sourabh Mishra is an entrepreneur, developer, speaker,
author, corporate trainer, and animator. He is a Microsoft
guy; he is very passionate about Microsoft technologies and
a true .NET warrior. Sourabh started his career when he was
just 15 years old. He’s loved computers from childhood. His
programming experience includes C/C++, ASP.NET, C#,
VB.NET, WCF, SQL Server, Entity Framework, MVC, Web
API, Azure, jQuery, Highcharts, and Angular. Sourabh has
been awarded a Most Valuable Professional (MVP) status. He has the zeal to learn new
technologies and shares his knowledge on several online community forums.
He is the author of Practical Highcharts with Angular by Apress, which talks about
how you can develop stunning and interactive dashboards using Highcharts with
Angular. He is the founder of “IECE Digital” and “Sourabh Mishra Notes,” an online
knowledge-sharing platform where one can learn new technologies very easily and
comfortably.
He can be reached via the following:
• YouTube: sourabhmishranotes
• Twitter: sourabh_mishra1
• Facebook: facebook.com/sourabhmishranotes
• Instagram: sourabhmishranotes
• Email: [email protected]
xiii
Introduction
Web developers are in an all-time demand. Frontend developers, those in charge of
building the parts of the applications that interact directly with the end user, are a
crucial part of software companies, providing a navigation experience that is in no short
measure responsible for the success of a project.
Frontend developers use different programming languages, such as JavaScript,
HTML, and CSS. However, in the current state of affairs, they also need to get a firm
grasp of libraries and frameworks.
Angular is a very powerful, enterprise-grade framework that lets you build apps
of any size that are scalable and maintainable. It has a vibrant community and is
continually updated with new features to improve productivity.
This book aims to form you as a competent Angular developer who can easily work
in a company or as a freelancer, making the process enjoyable.
xv
Introduction
xvi
CHAPTER 1
Introduction to Angular
Framework
The purpose of this chapter is to give a brief introduction of Angular and then
proceed with the setup of our development environment. There are a few mandatory
installations; the rest, although not required, are convenient. A good IDE is a
tremendous help, and having the right packages can significantly boost our productivity.
If you are reading this book, you have probably already heard about Angular. It is
even likely that you have followed some tutorials and even developed applications.
However, my goal is to start without making assumptions about the level from which
you start.
We will start from the basics, to build increasingly complex examples that allow you
to acquire the necessary skills to work with Angular in real projects.
About Angular
On its website https://angular.io/, Angular is defined as a platform for developing
web applications. This brief definition, however, hides the power of this framework,
which can be used to build web, mobile, and even desktop applications.
The name Angular was originally known as AngularJS. This was a framework that
reached a lot of popularity, with interesting features but with limitations. So, Angular
2 arrived. However, this name can lead to confusion, since in fact Angular 2 was not a
simple update, but a completely different framework. In fact, migrating an application
made with AngularJS to Angular 2 is a process that is not at all simple and that should be
considered with extreme care.
We quickly saw how Angular 2 became 3, 4, and so on. At the time of writing these
words, Angular is in version 15.
1
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_1
Chapter 1 Introduction to Angular Framework
But I want to make clear the following: in the book, we will simply refer to the
platform as Angular. Each time updates are made that involve substantial changes, the
sections will be updated accordingly, so that this book will continue to be useful for
many years.
Therefore, it seems necessary to explain the way in which Angular handles its versions.
Semantic Versioning
Angular works using what is known as semantic versioning. That is to say, Angular
versions are always presented in the major.minor.patch form, for example, 5.2.7.
Then from right to left, we have
Frequency of Releases
A new major version is usually expected every six months. Before each major update,
it is expected to have one to three minor updates. The patches are launched practically
every week.
It is useful to have a release calendar on hand. This can be found in angular.io. Of
course, these dates are not written in stone and can be modified, but they provide a
general guide.
We are ready to start now. Let’s do it!
2
Chapter 1 Introduction to Angular Framework
Installations Needed
NodeJS
https://nodejs.org/en/
What we are interested in from Node is npm (node package manager), a powerful
package manager that comes with Node and that allows us to install libraries using just
a line of code. It’s essential. To verify that everything went correctly, we can type node
--version in the command line as shown in Figure 1-1.
If you have a version equal to or greater than this, we can continue without problems.
Google Chrome
www.google.es/chrome/browser/desktop/
While the examples we will use will work in any modern browser, it is advisable to
use Chrome for the valuable developer tools that it has.
TypeScript
www.typescriptlang.org/
TypeScript is a superset of JavaScript, which has very interesting features such as
being strongly typed and the introduction of proper classes. We will make intensive
use of TypeScript to define our Angular components, but do not worry, in this book
there is a whole section dedicated to this language where everything needed to work
with confidence is explained and shown by examples. Those of you who already have
experience with TypeScript can simply skip the section without problems.
We’ll be installing TypeScript globally, so we can use the tsc command anywhere
in our terminal. We can verify the installation typing the command tsc --version as
shown in Figure 1-2.
3
Chapter 1 Introduction to Angular Framework
Angular CLI
angular.io
angular-cli (https://github.com/angular/angular-cli)
The Angular CLI is Angular’s command line and allows us to automatically generate
the skeleton of our components, pipes, services, and others. Here, the command
that interests us is ng version, which should give us an output similar to the one in
Figure 1-3.
4
Chapter 1 Introduction to Angular Framework
Ionic
Ionic is a framework for the development of hybrid mobile applications, which allows
deploying these apps on devices with Android, iOS, and Windows from a single
code base.
This framework deserves a book by itself, but from its beginnings, it had a great
integration with Angular. A section is included in this book where a complete Ionic
application is developed, in order to illustrate concepts that we will need in the following
Angular applications that are presented, such as impure pipes.
IDE
If you already have an IDE or text editor of your choice, you can probably work with
it without any problem. However, the examples in this book are made using the IDE
Visual Studio Code (https://code.visualstudio.com/). It is a free and open source
application, and it really is excellent.
If you decide to install VS Code – again I recommend it – then I ask you to install the
following packages:
• Angular v5 Snippets
• Angular2-inline
• JS-CSS-HTML Formatter
• JSHint
• Material Icon Theme
• Terminal
5
Chapter 1 Introduction to Angular Framework
• TSLint
• TypeScript Hero
• TypeScript Importer
Not required, but they will make the development much simpler.
2. Then we will access the next screen where we simply type the
name of the package to be installed, select it, and install it, as
shown in Figure 1-5.
6
Chapter 1 Introduction to Angular Framework
• Atom Bootstrap3
• Atom TypeScript
• File Icons
7
Chapter 1 Introduction to Angular Framework
8
Chapter 1 Introduction to Angular Framework
2. From the Install option, we will access a new screen where we can
type the name of the packages that interest us, choosing those we
want to install (Figure 1-8).
9
Chapter 1 Introduction to Angular Framework
Note The installations of TypeScript, the Angular CLI, and Ionic can present
problems or not work as expected, especially if you have previous versions.
Remember that it is very possible that Google is the answer. The ability to
investigate and find solutions is invaluable for a developer.
Postman
Postman (www.getpostman.com/) is an API development environment. When we work
with services, we will be using APIs from both third parties and their own. This tool
allows us to test these APIs, sending GET, POST, PUT, and patch requests, authentication
tokens, and many other things. The functionality of this tool goes much more than what
will be touched in this book, and I recommend that you investigate it as thoroughly as
possible.
10
Chapter 1 Introduction to Angular Framework
Errors
Each line of code included in the book has been carefully reviewed. However, errors can
occur, and in that case, I would greatly appreciate being contacted and informed of any
problem in order to be able to correct it immediately. Everyone will benefit from it.
On the other hand, where the code of the example exceeds a few lines of code, in
addition to presenting it in the book, I will be providing the link to the code snippet
in the official book repo, so that you can refer to that link and copy the code more
comfortably if you wish.
Writing the code on your own, following the examples presented, would be an
excellent way to gain confidence and get used to the syntax, but you will always have the
complete code of the applications to be able to use it.
Summary
This chapter started with a brief introduction of Angular and then dealt with the setup
of our development environment. Other than Node, TypeScript, and angular-cli, the
rest of the installations are not mandatory, but they are convenient. A good IDE is a
tremendous help, and having the right packages can significantly boost our productivity.
Learning any new framework can be a bit intimidating at the beginning, especially
one as powerful as Angular. But continue and be patient; let’s address it one step at a
time. Learning it will be easy for some people, while others will have to work harder
to achieve it. But what I can guarantee is that as we move forward, the development of
applications will become a pleasant and exciting experience. The time and effort we
dedicate will be highly rewarded. Let’s get started!
11
CHAPTER 2
Introduction to TypeScript
and ES6
In this chapter, we will touch on the bases of TypeScript: data types, let and const,
functions, arrow functions, classes, and interfaces. Although not extensive, this guide is
complete enough to understand every bit of code we will be building and to understand
pieces of code from other sources when you need to look for something later on your
journey as a developer.
Getting Started
We are ready to start working. First, we will create a folder called Angular, where we will
save the code of all the applications that we will build throughout the book.
You can place this directory anywhere you wish. Inside this folder, we will create a
new one called typescript.
Now let’s open this folder with our IDE or text editor; we should see something
similar to Figure 2-1.
13
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_2
Chapter 2 Introduction to TypeScript and ES6
With this folder in place, we are ready to start learning about the main characteristics
of TypeScript. We’ll start learning how to declare variables and constants and when to
use one or the other.
First File
1. Inside the IDE, let’s create a new file called 01-let-const.ts with the
following code:
var i = 1;
14
Chapter 2 Introduction to TypeScript and ES6
// do something
}
console.log( i );
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width = device-width,
initial-scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie = edge">
<title>var let and const</title>
</head>
<body>
<script src="01-let-const.js"> </script>
</body>
</html>
For those who are working with VS Code, you will probably have
installed by default (otherwise, it would be convenient to install it)
a package called HTML Snippets. If so, you can see that simply by
typing “doc” and pressing enter, you will get a simple page skeleton
(Figure 2-2).
15
Chapter 2 Introduction to TypeScript and ES6
16
Chapter 2 Introduction to TypeScript and ES6
17
Chapter 2 Introduction to TypeScript and ES6
18
Chapter 2 Introduction to TypeScript and ES6
tsc 01-let-const
Now if we refresh the browser, the error will have disappeared. But
let’s not do it yet. Let’s analyze the code again:
var i = 1;
console.log( i );
The output is 6. Now let’s make some small changes to the code:
let i = 1;
for (let i = 1; i <= 5; i ++ ) {
// do something
}
console.log( i );
20
Chapter 2 Introduction to TypeScript and ES6
The only changes are that we have replaced var with let. Now,
what do you think the output will be? If you went ahead, you will
see that it continues to be 6. But what happens is that we have not
recompiled the file 01-let-const.ts.
var i = 1;
for (var i = 1; i <= 5; i++) {
// do something
}
console.log(i);
The result is 1. To obtain more clues about this behavior, open the
generated 01-let-const.js file again:
var i = 1;
for (var i_1 = 1; i_1 <= 5; i_1++) {
// do something
}
console.log(i);
21
Chapter 2 Introduction to TypeScript and ES6
Can you tell the difference? Now in the loop, no reference is made
to the same variable i, but the tsc has created a new variable i_1.
And that is the difference between var and let. Let allows us to
declare variables with the same name, as long as their scopes are
different. And what is the scope of a variable? We can define it as
the fragment of code in which a variable lives.
22
Chapter 2 Introduction to TypeScript and ES6
7. Open the console and this time type the following command:
tsc 01-let-const -w
The only difference is the w flag, which tells the tsc to be aware
of any changes that occur in the file. For example, the VS Code
terminal produces the output before that command (Figure 2-9).
console.log(START);
We did not have to recompile the file, because the tsc is monitoring the changes and
generating the js file automatically. Fantastic.
23
Chapter 2 Introduction to TypeScript and ES6
What happens if we now add the following line after the last call to the console?
START = 20;
This is logical considering that a constant – which we declare with const – will have a
value that is not expected to change.
This is the same reason why we cannot just write:
const START;
Data Types
Always in the typescript directory, let’s create a file called 02-data-types.ts. As we
mentioned earlier, TypeScript is a JavaScript typed superset. This means that we can
define the type of variables that we declare. This fact allows us to detect errors before our
programs are executed, which is a great advantage.
24
Chapter 2 Introduction to TypeScript and ES6
Boolean
A Boolean type variable can contain the value true or false. In our new file, add the
following line of code:
Number
As in JavaScript, all numbers in TypeScript are floating-point numbers. Add the following
statements:
String
A string is basically a chain of characters. Normally, you can use it to display messages
or any kind of information to the user. Almost every language has tools to manipulate
strings, and TypeScript is no exception.
25
Chapter 2 Introduction to TypeScript and ES6
Array
An array is a set of elements. Every element in the array is identified with a position called
index. In TypeScript, arrays start at 0 (zero). We have two ways to write them. The first is
and also
Any
Variables of type any can contain a data of any type. Although apparently a
contradiction, these types of variables are useful when you are not sure of the type of a
given data that can be received from the user’s input or from third-party libraries.
There are other types of data, but we will see them as the applications we develop
require it. As a precaution, here is the content of the complete file:
Let’s now create a new directory within the typescript folder, called template-strings.
26
Chapter 2 Introduction to TypeScript and ES6
Template Strings
Manipulating Strings
1. Now we create a file called app.ts. Think about the following
situation. We have the declaration of some variables, which we
want to concatenate producing a text string:
console.log( text );
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width = device-width, initial-
scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie = edge">
<title>Template String</title>
</head>
<body>
<script src="app.js"></script>
</body>
</html>
We already know that if we open this file in the browser and access
the console, we’ll get an error because the file app.js doesn’t
exist yet.
27
Chapter 2 Introduction to TypeScript and ES6
tsc app
tsc app -w
tsc -init
The content of this file is long, although most of the lines are
commented, but let’s see one of them in particular:
28
Chapter 2 Introduction to TypeScript and ES6
tsc -w
Let’s talk about the line where the string is concatenated, and then
write the following:
6. Save the changes and refresh the browser. The output is exactly
the same.
7. Through the template string, we can not only present ordinary text
but also HTML tags. Let’s add the following lines:
let hi = `<h1>Hello</h1>
<p>World</p>`;
document.write( hi );
29
Chapter 2 Introduction to TypeScript and ES6
8. Let’s refresh the browser, and we’ll get the output shown in
Figure 2-15.
9. There are other things we can do with the templates. Modify the
file by adding the following lines:
console.log( message );
As you can see, we have included a call to a function between ${}. In fact, everything
that is enclosed by these symbols will be evaluated as an expression. The output is
shown in Figure 2-16.
Knowing about the different data types is a big step, but we need a way to call a set of
statements without code repetition. It’s time to learn about functions.
Functions
Functions are a fundamental component of any programming language. In the case of
JavaScript, the functions have a predominant importance, since they are used not only to
describe procedures but, for example, to imitate classes.
30
Chapter 2 Introduction to TypeScript and ES6
In the case of TypeScript, we have proper classes, but even so within a class, a
method is nothing but a function so its importance is still huge.
tsc -init
function calculateDiscount(price) {
return price * 0.50;
}
console.log( calculateDiscount( 25 ) );
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width = device-width, initial-
scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie = edge">
<title>Functions</title>
</head>
<body>
<script src="functions.js"></script>
</body>
</html>
tsc -w
31
Chapter 2 Introduction to TypeScript and ES6
We have not defined a type for the price parameter, and therefore
it has an implicit type of any.
32
Chapter 2 Introduction to TypeScript and ES6
33
Chapter 2 Introduction to TypeScript and ES6
Now the error will be gone. Let’s try adding a new call to the function
and sending it to the console:
10. If we refresh the browser, we will see that the function works as
expected (Figure 2-21).
34
Chapter 2 Introduction to TypeScript and ES6
35
Chapter 2 Introduction to TypeScript and ES6
Arrow Functions
To understand arrow functions, let’s analyze some previous concepts.
JavaScript has first-class functions. That is, functions can be passed as any other
parameter.
For example:
setTimeout(function() {
console.log("Hi");
}, 3000);
setTimeout(() => {
console.log("Hi");
}, 3000);
This can be further shortened by taking into account that the body of the anonymous
function contains a single line:
Let’s see more examples. Let’s add a new directory called arrow-functions and an
arrow-functions.ts file inside it. Also, add the corresponding index.html file.
Define a simple function and assign it to a variable:
We see that since it is a single line of code, the curly braces and the reserved word
return can be ignored.
We have exemplified the first advantage of arrow functions.
They allow a more compact notation.
But there is an even more important use of arrow functions.
36
Chapter 2 Introduction to TypeScript and ES6
let car = {
speed: 10
Accelerate: function() {
this.speed + = 10;
console.log( this.speed );
}
}
car.accelerate();
When compiling the file and viewing the output in the browser, we get the output
shown in Figure 2-23.
let car2 = {
speed: 10
Accelerate: function() {
setTimeout( function() {
this.speed + = 10;
console.log( this.speed );
}, 3000)
}
}
car2.accelerate();
37
Chapter 2 Introduction to TypeScript and ES6
What is the output now? If we refresh the browser and wait for the three seconds of
the timeout, we will see the output in Figure 2-24.
The output in this case is 110, which is incorrect. What has happened? Well, the
difference in the output has to do with the context.
What happens is that the value of this in a function depends on the way the call is
made. In the case of the first object, when the call car.accelerate() is made, this points
to the car object; this is the call context.
But in this case:
setTimeout( function() {
this.speed + = 10;
console.log( this.speed );
}, 3000)
the call context is not the object car2, but the global object where we previously
defined the variable speed with let speed = 100, and therefore the output is 100.
We can solve this by using an arrow function:
let car3 = {
speed: 10
Accelerate: function() {
setTimeout( () => {
this.speed + = 10;
console.log( this.speed );
}, 3000)
}
}
38
Chapter 2 Introduction to TypeScript and ES6
car3.accelerate();
We see that the value obtained is correct. And this brings us to the second advantage
of the arrow functions. The arrow functions correctly capture the context of this.
In case you are wondering, yes, we could have solved the problem without resorting
to an arrow function, in the following way:
let car4 = {
speed: 10
Accelerate: function() {
let _this = this;
setTimeout( function() {
_this.speed + = 10;
console.log( _this.speed );
}, 3000)
}
}
car4.accelerate();
It was necessary to capture the reference to this before calling the anonymous
function. Arrow functions make this unnecessary.
39
Chapter 2 Introduction to TypeScript and ES6
Another powerful feature of the language is the ability to assign object properties and
array elements to variables. This process is called destructuring, and we’ll learn about it
in the next section.
let rect = {
x: 5,
y: 10,
width: 20,
height: 25
};
We can access the values of the properties of the object and assign them to variables,
in a single statement as follows:
Note that the name of the variables corresponds to the name of the properties of the
object. If you want to rename some variables, this can be done in the following way.
40
Chapter 2 Introduction to TypeScript and ES6
console.log( x, Y, w, h );
console.log( x, Y );
console.log( remaining );
let person = {
firstName: "George",
lastName: "Martin",
41
Chapter 2 Introduction to TypeScript and ES6
prefix: "Dr"
}
console.log( sayHi(person) );
Destructuring can also be used with arrays. Add the following line:
let [p, q] = [ 3, 4 ];
console.log( p, q );
If we send again p and q to the console, we will check that their values have been
exchanged, as shown in Figure 2-29.
42
Chapter 2 Introduction to TypeScript and ES6
To finish this section, I provide you with the complete code of the file:
let rect = {
x: 5,
and: 10,
width: 20,
height: 25
};
console.log( x, Y );
console.log( remaining );
let person = {
firstName: "George",
lastName: "Martin",
prefix: "Dr"
}
43
Chapter 2 Introduction to TypeScript and ES6
console.log( sayHi(person) );
let [p, q] = [ 3, 4 ];
console.log( p, q );
console.log( p, q );
It’s time to move to a concept that can have a significant impact on the quality of the
product, especially in complex applications where many developers are involved. This
concept is known as interfaces.
Interfaces
Interfaces play a very important role not only in TypeScript but in many programming
languages. A useful way to think about an interface is like a contract. This contract
allows us to define a type of data and give it a name. All the variables that are declared
as belonging to that type must implement the interface, that is, they must present the
structure that has been defined in the interface.
Let’s see an example. Our new directory will be called interfaces. In it, we will have
the file interfaces.ts and the file index.html.
Define the following interface:
interface employee {
firstName: string,
lastName: string,
birthDate: Date
}
That is our contract. Any variable of type employee must provide values for
firstName, lastName, and birthDate.
44
Chapter 2 Introduction to TypeScript and ES6
Something very interesting is that by defining the parameter emp as employee type,
we will have the help of our IDE (Figure 2-30).
If we now try to declare an employee variable that does not have any of the
properties of the interface, an error will occur as shown in Figure 2-31.
45
Chapter 2 Introduction to TypeScript and ES6
Correct that:
Now let’s call the function and present the result by console:
As always, let’s compile the project and observe the result in the browser
(Figure 2-32).
Classes
TypeScript is an object-oriented language. For those of you who have worked on object-
oriented programming (OOP), the concepts you will see here will be absolutely natural
and easy to understand.
If you have not worked in OOP before, you will need a little more work. But don’t
worry, we will go step by step.
46
Chapter 2 Introduction to TypeScript and ES6
What Is a Class?
We can think of a class as a plane for the construction of an object. The class contains
the description of all the properties and behaviors that an object must possess to be
considered as belonging to the class.
For example, let’s think of a Person class. A person, in general terms, will have a
name, a surname, and a certain age. We are talking about the attributes of people in
general. The objects created from this description will have specific values for those
attributes. A person, in addition, can have a behavior that is to say hello.
In the OOP language, we call attributes properties and behaviors methods.
But let’s go directly to the code.
First, our new directory will be called classes. We initialize it as a project of typescript
and create a file classes.ts and the index.html that will invoke the compiled file.
In our file classes.ts, we dump the following content:
class Person {
firstName: string;
lastName: string;
birthDate: Date;
Our class is called Person. It is a convention that the first letter of the name of a class
is capitalized.
Then, we have the list of attributes that a person must possess:
firstName: string;
lastName: string;
birthDate: Date;
47
Chapter 2 Introduction to TypeScript and ES6
Two of them are of type string, and one of them is of type date. We have simply
specified the name of the properties and their type, but we have not explicitly defined
the type of access they allow. The type of access, both for properties and methods, can be
defined as public, private, or protected.
Having not specified the access type of the preceding properties, they are implicitly
considered to be of type public.
But what do these types of access mean? Soon we will see it.
Now let’s move on to our first method:
This method has a special name. It is the constructor of the class, a method that is
invoked at the moment of creation of an object of the Person type. Creating an object is
known as instantiate.
As we can see, it is a function like the ones we have seen in the corresponding
section; therefore, it can have mandatory parameters, with default values, and optional
ones. The only difference is that this function is defined in the context of a class.
Consider the line:
this.firstName = firstName;
In this line, we are assigning the value of the firstName parameter to the firstName
property.
The word this will point to the instance of the object that is obtained during its
creation. That is, if we create an object called person1 of type Person, this will be a
synonym of person1.
Let’s instantiate our first object:
Now, we have an object named person1. It does not really do much, but it will allow
us to experiment a bit. Let’s send an output to the console:
console.log( person1.firstName );
48
Chapter 2 Introduction to TypeScript and ES6
Now let’s compile the project with tsc -w, to be aware of the changes. We can see the
output in Figure 2-33.
Good. We can perfectly access the value of the firstName property of the person1
object from outside the class, since as we mentioned previously, the access type of this
and the other properties is of type public.
But this can have unintended consequences. Let’s add the following two lines:
person1.firstName = "Brandon";
console.log( person1.firstName );
We have been able to change the value of a property of the object from outside
it. Why is this a bad idea? Because it violates the principles of encapsulation and
information hiding.
Without going into too many details, the principle of information hiding tells us that
the user of a class must have access only to what he needs. You should not know details
of how an object belonging to that class structures its data and implements its methods.
You should only know which methods to invoke to obtain a certain behavior.
49
Chapter 2 Introduction to TypeScript and ES6
50
Chapter 2 Introduction to TypeScript and ES6
Now that the properties are private, we cannot read or change their value directly.
Then, how do we do it? Well, through special methods called accessors.
getFirstName(): string {
return this.firstName;
}
getLastName(): string {
return this.lastName;
}
getBirthDate(): Date {
return this.birthDate;
}
We have three methods that allow us to read the values of the properties. It seems
too much effort for something so simple, but think that the logic of such methods could
be much more complex, for example, only returning a value when an authorized user
requests it.
Now we can replace the line:
console.log( person1.firstName );
by
console.log( person1.getFirstName() );
51
Chapter 2 Introduction to TypeScript and ES6
However, although not very common, there may be cases in which the first and last
names change, so we are going to add two methods that allow us to do exactly that:
person1.firstName = "Brandon";
by
person1.setFirstName("Brandon");
console.log( person1.getFirstName() );
Now all the errors have disappeared. Again, the logic of these new methods could be
much more complex, with checks being carried out before modifying the values.
Just to be sure, here is the complete code of our file so far:
class Person {
private firstName: string;
private lastName: string;
private birthDate: Date;
getFirstName(): string {
return this.firstName;
}
52
Chapter 2 Introduction to TypeScript and ES6
getLastName(): string {
return this.lastName;
}
getBirthDate(): Date {
return this.birthDate;
}
console.log( person1.getFirstName() );
person1.setFirstName("Brandon");
console.log( person1.getFirstName() );
You can find the code in this file of the GitHub repo:
Class Person
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-02/01-classes-01.ts
Inheritance
As in most object-oriented languages, we can define classes that inherit from others. For
example, think of an Employee class that inherits from the Person class. To express this
relationship, we use the keyword extends:
53
Chapter 2 Introduction to TypeScript and ES6
These few lines have allowed us to define an Employee class that extends the Person
class. Therefore, it inherits all its methods and properties, including the constructor. We
can therefore write:
But the idea of declaring a class that inherits from another is precisely to extend the
functionality. And we have not done any of that yet. Let’s modify the definition of our
class in the following way:
getDepartment(): string {
return this.department;
}
}
We have a new property of type string to store the employee’s department. We are
initializing this variable with an empty string. We see that in the definition of our class
the constructor is absent. This is because, as we mentioned, the new class inherits the
properties and methods of the parent class.
But it would be perfectly possible to overwrite some or all of the methods of the
parent class, for example, the constructor:
constructor(
firstName: string,
lastName: string,
birthDate: Date,
department: string ) {
54
Chapter 2 Introduction to TypeScript and ES6
The constructor receives the three parameters expected by the parent class, plus a
new one corresponding to the department. On the line:
the constructor of the parent class (Person) is invoked. Then we have the line where the
value is assigned to the department:
this.department = department;
This is because the constructor expects a fourth parameter, department, that we are
not assigning.
But suppose that we have a situation in which we want to model an employee can
enter without having yet a department assigned. To reflect this situation, we can define
this fourth parameter as optional. We have already seen how to do that, using a question
sign next to the parameter name. Our modified constructor will be as follows:
constructor(
firstName: string,
lastName: string,
birthDate: Date,
department?: string ) {
55
Chapter 2 Introduction to TypeScript and ES6
It is a simple and clean code. For security, here is the complete code of our file:
class Person {
private firstName: string;
private lastName: string;
private birthDate: Date;
getFirstName(): string {
return this.firstName;
}
getLastName(): string {
return this.lastName;
}
getBirthDate(): Date {
return this.birthDate;
}
console.log( person1.getFirstName() );
person1.setFirstName("Brandon");
console.log( person1.getFirstName() );
56
Chapter 2 Introduction to TypeScript and ES6
constructor(
firstName: string,
lastName: string,
birthDate: Date,
department?: string ) {
getDepartment(): string {
return this.department;
}
}
sayFullName() {
return `${ this.firstName } ${ this.lastName }`;
}
Nothing bad happens to the naked eye. However, our IDE will reveal the errors
shown in Figure 2-38.
57
Chapter 2 Introduction to TypeScript and ES6
Private properties can only be accessed by methods of the class itself. If we wanted to
rewrite the method correctly, it would be like this:
sayFullName() {
return `${ this.getFirstName() } ${ this.getLastName() }`;
}
class Vehicle {
protected brand: string = "";
protected color: string;
protected wheels: number;
getColor(): string {
return this.color;
}
}
The brand property has a default value; therefore, it is not mandatory to assign a
value to it in the constructor. All properties are protected.
Now let’s define a class that extends from it:
Since the brand property is protected, we can assign it a value in the constructor.
But let’s change the type of brand from protected to private now, and we’ll get the error
shown in Figure 2-39.
Alright, let’s change the brand property from private to protected again to get rid of
the error.
With our classes defined, we can perfectly create instances of both:
59
Chapter 2 Introduction to TypeScript and ES6
However, we can question whether it is really logical to be able to instantiate, that is,
to create objects of the generic Vehicle class. Sometimes, it is convenient or necessary
to be able to define classes that can be extended, but not instantiated directly. We thus
arrive at the concept of abstract classes.
Abstract Classes
So far, the full content of classes2.ts is as follows:
class Vehicle {
protected brand: string = "";
protected color: string;
protected wheels: number;
getColor(): string {
return this.color;
}
}
60
Chapter 2 Introduction to TypeScript and ES6
We confirm that now it is not possible to instantiate objects of the Vehicle class
(Figure 2-40). Let’s eliminate the conflicting line.
interface employee {
firstName: string,
lastName: string,
birthDate: Date
}
At that time, we used the interfaces as a type in the definition of a variable, for
example:
61
Chapter 2 Introduction to TypeScript and ES6
interface Animal {
name: string;
family: string;
}
interface Animal {
name: string;
family: string;
makeSound(): string;
}
Pay attention to the following: we have the name of the method, makeSound,
followed by the type of return value, then a semicolon. The method has no body. And
that is a difference with an abstract class. The interfaces can only declare methods, but
not implementations of them.
Now we declare a class that implements this interface. The interfaces are
implemented, they are not extended:
However, our class will give us the error, as shown in Figure 2-41.
62
Chapter 2 Introduction to TypeScript and ES6
This is so because as we said an interface is a contract, and if the Dog class wants to
implement the Animal interface, it must comply with the contract. Let’s fix that:
makeSound(): string {
return 'Woof';
}
}
Now the Dog class correctly implements the interface. We can begin to understand
the power of interfaces. A programmer can define the interfaces, and in this way, it will
be guaranteed that the classes that implement it will be consistent.
In many applications, we’ll have to wait for a server response. That means that
we have to deal with asynchronous processes without degrading the user experience.
Fortunately, promises come to our rescue and are the subject of our next section.
63
Chapter 2 Introduction to TypeScript and ES6
Promises
Promises were introduced to deal with asynchronous processes. But to understand its
usefulness, let’s first see another way of handling asynchronous processes: callbacks.
64
Chapter 2 Introduction to TypeScript and ES6
})
The two arguments, resolve and reject, can be called as desired, but it is
a convention to call them that.
resolve and reject are in fact two functions. When the asynchronous
process finishes, it will be called resolve(). If an error occurs, reject will
be called.
5. Let’s see the full version of the promise:
65
Chapter 2 Introduction to TypeScript and ES6
asyncTask2.then(
function() {
console.log("Promise resolved");
},
function() {
console.error("Something went wrong");
}
)
66
Chapter 2 Introduction to TypeScript and ES6
So far, it may not be too clear what the advantage of the promises on
callbacks is. One of them is that promises can be chained. Suppose we
have three asynchronous methods. The second method must be called
when the first one has finished, and the third when the second one has
done it. Let’s see how we can solve this by using chaining of promises.
8. Let’s create a new file named promises2.ts with the following code:
67
Chapter 2 Introduction to TypeScript and ES6
});
return promise;
};
firstMethod()
.then(secondMethod)
.then(thirdMethod)
.then(
(res: any) => console.log( res )
)
firstMethod()
.then(secondMethod)
.then(thirdMethod)
.then(
(res: any) => console.log( res )
)
firstMethod()
.then(
(res: any) => console.log( res )
)
In this case, we would have obtained the output shown in Figure 2-45.
68
Chapter 2 Introduction to TypeScript and ES6
However, instead, the received output is sent directly to the second method:
firstMethod()
.then(secondMethod)
Then again we subscribe to the promise using then. This output is sent in turn to the
third method, and we subscribe again.
When the third exit has been obtained, we can finally use it. Run the file index.html
in the browser and see how each method is completed until the last, where we can have
the complete data (Figure 2-46).
As we will see when we start defining our classes in Angular, those classes make
extensive use of a kind of annotation called decorators. We’ll learn about it in the next
section.
69
Chapter 2 Introduction to TypeScript and ES6
Decorators
Decorators are annotations that can be added to a class, method, interface, property, or
parameter declaration.
The decorators’ purpose is to add functionality to existing code without having to
modify the code itself.
We see an example.
Our new directory will have the name decorators and inside it a decorators.ts file.
Let’s write the following code:
@log
class Test {
constructor() {}
}
,"experimentalDecorators": true,
70
Chapter 2 Introduction to TypeScript and ES6
In addition to class decorators, we can have decorators of methods. Modify the file as
follows:
console.log('target:', target);
console.log('propertyKey:', propertyKey);
console.log('descriptor:', descriptor);
// post
console.log('The return value is: ' + result);
71
Chapter 2 Introduction to TypeScript and ES6
return descriptor;
}
@log
class Test {
constructor() {}
@authorize
hello( name: string ): string {
return "Hello " + name;
}
}
Summary
This chapter may have been a bit extensive, but I hope it was entertaining and useful. We
have seen the bases of TypeScript: data types, let and const, functions, arrow functions,
classes, and interfaces. Although to be honest we have only touched the surface, what we
have seen is more than enough to develop the applications of the book.
If you have been able to perform the exercises, then you can proceed with the next
chapter. New and exciting sections are coming. Cheer up!
72
Chapter 2 Introduction to TypeScript and ES6
Answers
1. npm install -g typescript
3. interface x { }
4. class X { }
5. super
73
CHAPTER 3
RestApp Part 1
In this chapter, we’ll learn how to generate a new project using angular-cli. We’ll create
new components and a service to communicate with a rest API. As for the purely
aesthetic aspect, we’ll include bootstrap in our application through CDNs.
ng new rest
The process may take a few minutes depending on our connection, giving us the
output shown in Figure 3-1.
75
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_3
Chapter 3 RestApp Part 1
When the process has finished, we will have a new directory called rest. Let’s open it
with our IDE (Figure 3-2).
76
Chapter 3 RestApp Part 1
The number of directories can seem overwhelming, but our work will be limited
mostly to the src directory.
But let’s take a look at the structure that angular-cli has given us. In the terminal, go
to the rest directory and type the following:
ng serve -o
ng serve boots the embedded server of Angular, making our application available at
http://localhost:4200, but if we use the flag -o, our browser by default will open this
address in a new tab (Figure 3-3).
77
Chapter 3 RestApp Part 1
app.component.css
app.component.html
app.component.spec.ts
app.component.ts
app.module.ts
78
Chapter 3 RestApp Part 1
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'rest';
}
This file defines the structure of a basic component. At Angular, we structure our
applications using components. Think of components as reusable blocks that define
a part of our user interface: for example, a navigation bar with the main menu, a
sidebar, etc.
The components are responsible for defining both their own internal logic and their
appearance.
But at its root, the components are not something that we have not seen before.
Classes:
an import of the Component class that is defined in the module @angular/core is being
done here.
79
Chapter 3 RestApp Part 1
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
<body>
<app-root> </app-root>
</body>
styleUrls: This indicates the path of a CSS file where specific styles
are defined for the component.
Now let’s go to the app.component.html file. We are going to replace all the HTML
code that is there by the following:
<h1>{{title}}</h1>
80
Chapter 3 RestApp Part 1
app.module.ts
Every Angular application is structured in modules. When we generate a new Angular
project, a root module is created by us, which is in charge of launching our application.
While this module can be called in any way, it is a convention to call it AppModule. Let’s
analyze it for a moment:
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
In the declarations array, we have all the components that belong to the module.
That is why the components we create should be included in this section.
The imports array contains the modules that are necessary for the application.
When we generate a new module that depends directly on the AppModule, we must add
it here.
81
Chapter 3 RestApp Part 1
We have an application with user information (I’m using my own picture to avoid
copyright issues). The user data comes from a rest API, so we will use a service to
communicate with this API.
When clicking the details of a user, we will obtain a new screen with the information
shown in Figure 3-6.
82
Chapter 3 RestApp Part 1
Bootstrap 4
Before starting to work fully on the code, let’s give a little style to our application.
Bootstrap is a library of frontend components, beautifully stylized, that allow us to
build applications that will be perfectly visualized from any device. It is not the only
one, of course, and in the future I will include sections to work with Material Design, for
example.
We will go to the Bootstrap (https://getbootstrap.com/) page and then to the
Download section (Figure 3-7).
83
Chapter 3 RestApp Part 1
In the Download section, we have different ways to include bootstrap. Let’s start with
the simplest, then we will see a cleaner and tidier way (Figure 3-8).
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.
min.css"
integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN
5t9UJ0Z"
crossorigin="anonymous">
Next, we will include the jQuery, popper, and bootstrap libraries first, in that order:
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+
OrCXaRkfj"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/
popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7n
iu735Sk7lN"
84
Chapter 3 RestApp Part 1
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/
bootstrap.min.js"
integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/
KUEfYiJOMMV+rV"
crossorigin="anonymous"></script>
ng g c components/navbar
Since the components directory does not exist, angular-cli will create it for us.
The navbar.component.html code is extremely simple:
<p>
navbar works!
</p>
85
Chapter 3 RestApp Part 1
<app-navbar> </app-navbar>
And we’ll see the one shown in Figure 3-10 in the browser.
Navbar Component
Very good. On the bootstrap site, let’s go to the documentation section and look for the
Navbar component (Figure 3-11).
86
Chapter 3 RestApp Part 1
87
Chapter 3 RestApp Part 1
</div>
</li>
<li class="nav-item">
<a class="nav-link disabled" href="#" tabindex="-1"
aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search"
placeholder="Search"
aria-label="Search">
<button class="btn btn-outline-success my-2 my-sm-0"
type="submit">
Search
</button>
</form>
</div>
</nav>
We are going to make several changes. First, let’s replace the following line:
with
88
Chapter 3 RestApp Part 1
This will give us a nice dark color in our navigation bar (Figure 3-13).
We are getting rid of the dropdown and the disabled link. Then we must add another
two. Here is the complete code of the file up to this point:
data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
89
Chapter 3 RestApp Part 1
90
Chapter 3 RestApp Part 1
It’s enough for now. We will use the terminal to create three new components: about,
contact, and home.
We could have created the components manually, but the advantage of doing so
using the angular-cli is that the tool will be responsible for making the corresponding
import in our app.module.ts file. If we open it, you will see that the following lines have
been added:
And in the declarations section, the necessary lines will have been added:
declarations: [
AppComponent,
NavbarComponent,
AboutComponent,
ContactComponent,
HomeComponent
],
Routing
To move forward, let’s now establish the navigation of our application. To do this, we will
create a new file called app.routes.ts, at the same level as the app.module.ts file.
If we are working with VS Code, and we have installed the corresponding packages,
we can take advantage of the Angular 10 Snippets package, and simply by starting to type
ng-, we will be shown a series of suggestions (Figure 3-14).
91
Chapter 3 RestApp Part 1
The option that we are interested in is ng-router-appmodule, which will give us the
following skeleton. It is important to note that this skeleton would include references
to two components, FeatureComponent and PageNotFoundComponent, that we don’t
have. We will not use these components, and we’ll be changing the paths accordingly:
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
92
Chapter 3 RestApp Part 1
The constant routes is an array of routes that basically have two elements: a path and
the component that will respond to that path.
We need to make a small change. Simply by convention, we change the constant
routes by uppercase:
imports: [RouterModule.forRoot(ROUTES)],
We have a path that directs to the HomeComponent. Define two paths that lead us to
about and contact:
93
Chapter 3 RestApp Part 1
The last route of our array has the path: **. This path is used when a certain
route has not been found. Let’s change the component that will be addressed by our
HomeComponent:
@NgModule({
imports: [RouterModule.forRoot(ROUTES)],
exports: [RouterModule]
})
export class AppRoutingModule {}
94
Chapter 3 RestApp Part 1
We need to address the AppModule to add our new module to the imports array:
imports: [
BrowserModule,
AppRoutingModule
],
The most important is that we have removed the href and replaced it with:
[routerLink]="['/home']"
We are just passing a route, “home,” but we could also pass parameters as follows:
[routerLink]="['/path', routeParam]"
95
Chapter 3 RestApp Part 1
which the class passed as a parameter is located, in this case “active,” when the selected
route corresponds to the one assigned to routerLink.
We must proceed in the same way for the rest of the menu items.
Again, here is the complete navbar code up to this point:
96
Chapter 3 RestApp Part 1
</form>
</div>
</nav>
<app-navbar></app-navbar>
<router-outlet></router-outlet>
This line represents a placeholder that Angular dynamically fills with the output of
the component being called.
Now the links should work as expected.
Services
A service is responsible for communicating with endpoints that provide data. Our
services will be responsible for sending get, post, put, or patch requests to an API
endpoint.
Processes that involve communication with an API are inherently asynchronous.
Therefore, we must have means to notify us of the moment when the data is available.
We can create a new service with the following command:
ng g s services/reqres
Since the services folder does not exist, angular-cli will create it for us. Let’s execute
the command.
97
Chapter 3 RestApp Part 1
A service folder has been created, and in it we have a file named reqres.service.ts:
@Injectable({
providedIn: 'root'
})
export class ReqresService {
constructor() {}
}
That is the basic structure of a service. Now, unlike what happens with the
components, angular-cli does not add the necessary imports or declarations in app.
module.ts so we must do it ourselves.
In app.module.ts, we have the following array:
imports: [
BrowserModule,
AppRoutingModule
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
If we are using an IDE as VS Code, the necessary import statement will have been
automatically added. If this were not the case, we must add it:
98
Chapter 3 RestApp Part 1
API
In order to have data to work with, we need someone to provide it. In a real scenario,
this function would be fulfilled by an external API. However, for our purposes, we will
simulate a communication with an external server using the in-memory Web API
module, which is a third-party module. Here (https://github.com/angular/angular/
tree/main/packages/misc/angular-in-memory-web-api) you can find the repo.
The function of this module is to intercept requests made using the HttpClient,
effectively simulating communication with a server.
To install this module, we use the command:
After the installation is complete, we will do two imports in the AppModule. The
newly installed module and a class that we will create in a few moments:
99
Chapter 3 RestApp Part 1
Important We are importing a service we have not yet defined, so the compiler
should throw an error. We’ll fix it later.
Then we’ll add the following to the Imports array of the AppModule:
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
Again, we will see a compile error because the service is not defined.
Just to be safe, here is the full Imports array:
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false }
)
]
After adding the InMemoryDataService, you may get a compiler error because we
haven’t defined the class yet. This error will go away.
Our next step is to define the InMemoryDataService class. For this, we will use the
following command:
ng g s services/InMemoryData
@Injectable({
providedIn: 'root',
})
export class InMemoryDataService implements InMemoryDbService {
100
Chapter 3 RestApp Part 1
createDb() {
const users = [
{ id: 11, first_name: 'Eve', last_name: 'Holt',
avatar: 'assets/img/user.jpg' },
{ id: 12, first_name: 'Charles', last_name: 'Morris',
avatar: 'assets/img/user.jpg' },
{ id: 13, first_name: 'Tracy', last_name: 'Ramos',
avatar: 'assets/img/user.jpg' },
{ id: 14, first_name: 'Eve', last_name: 'Holt',
avatar: 'assets/img/user.jpg' },
{ id: 15, first_name: 'Charles', last_name: 'Morris',
avatar: 'assets/img/user.jpg' },
{ id: 16, first_name: 'Tracy', last_name: 'Ramos',
avatar: 'assets/img/user.jpg' },
{ id: 17, first_name: 'Eve', last_name: 'Holt',
avatar: 'assets/img/user.jpg' },
{ id: 18, first_name: 'Charles', last_name: 'Morris',
avatar: 'assets/img/user.jpg' },
{ id: 19, first_name: 'Tracy', last_name: 'Ramos',
avatar: 'assets/img/user.jpg' },
{ id: 20, first_name: 'Eve', last_name: 'Holt',
avatar: 'assets/img/user.jpg' }
];
return {users};
}
As you can see, we are using an image stored in our local system in order for this to
work. We need to create an img folder inside the assets folder and put inside this new
folder an image called user.jpg. You can use any image you like, or you can download my
profile picture from here (https://github.com/Apress/The-Beginning-Angular-by-
Victor-Hugo-Garcia/blob/main/images/user.jpg).
101
Chapter 3 RestApp Part 1
A detail to keep in mind regarding properties declarations. What we did earlier in the
constructor is equivalent to the following:
constructor( ) { }
We are now going to define a very rudimentary function that returns a list of users:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
The body of the function contains a single line of code. The http variable makes a get
request to the API endpoint to obtain data that contains a list of users.
All HttpClient methods return an Observable.
102
Chapter 3 RestApp Part 1
@Injectable({
providedIn: 'root'
})
export class ReqresService {
private url = 'api/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
return this.http.get<User>(url);
}
}
103
Chapter 3 RestApp Part 1
Now, let’s add a method that gets a list of users using the service:
getUsers() {
this.reqresService.getUsers().subscribe(
(res: User[]) => {
console.log(res);
},
(err) => {
console.error(err);
}
);
}
Within the function, we subscribe to the service with subscribe. This method takes
two parameters, both arrow functions.
The first is the one that will be executed when the service has published data (in this
case, represented by the variable res, although it could be called in any way), while the
second will be executed when a problem has occurred.
At this time, the function is not being called. Let’s modify the constructor as follows:
If we use the developer tools, we can see the output in the console (Figure 3-15).
104
Chapter 3 RestApp Part 1
We are going to define a variable that will help us to contain our users:
getUsers() {
this.reqresService.getUsers().subscribe(
(res: User[]) => {
this.users = res;
},
(err) => {
105
Chapter 3 RestApp Part 1
console.error(err);
}
);
}
<ul>
<li *ngFor="let user of users">
<h1>{{ user.first_name}} {{ user.last_name }}</h1>
</li>
</ul>
We have a for loop in our li element. Therefore, this element will be repeated for
every element in the loop. In this cycle, we define a user variable that will take the value
of each element of the users property for each cycle of the loop.
Then we simply generate an h1 tag showing the user’s first and last name. The output
in the browser is shown in Figure 3-16.
It looks pretty bad, but at least we know that the data is being received correctly.
Now let’s give it a little flair. Let’s copy the following into the home.component.
css file:
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
106
Chapter 3 RestApp Part 1
ul li {
background: rgb(238, 238, 238);
padding: 2em;
border-radius: 4px;
margin-bottom: 7px;
display: grid;
grid-template-columns: 60px auto;
}
ul li p {
font-weight: bold;
margin-left: 20px;
}
ul li img {
border-radius: 50%;
width: 100%;
}
An important aspect to note is that when working in the style sheet that is associated
with the home component, the styles that we include will only affect the elements found
in the template of our component.
Therefore, any unordered list that we define outside the component will not be
affected by the styles determined here.
If we want to define global styles, we must do it in the styles.css file.
Now let’s add the users image to our list at home.component.html:
<ul>
<li *ngFor="let user of users">
<img [src]="user.avatar">
<p>{{ user.first_name }} {{ user.last_name }}</p>
</li>
</ul>
107
Chapter 3 RestApp Part 1
Better. We have our list of users in a nice list. We now need a way to view the details
of an individual user. Let’s do it with a button:
<ul>
<li *ngFor="let user of users">
<img [src]="user.avatar">
<p>{{ user.first_name }} {{ user.last_name }}</p>
<div style="padding-top: 5px; width: 18rem;">
<button class="btn btn-outline-primary"
(click)="userDetails(user.id)">
Details
</button>
</div>
</li>
</ul>
(click)="userDetails(user.id)"
What we do here is tell Angular that it must be aware of the click event on the button
element, and in response to this event, it must call a userDetails function that receives
108
Chapter 3 RestApp Part 1
the user’s id as a parameter. However, this function is not yet defined, so if we click the
button, we will get an error. Let’s now add this function to home.component.ts:
We will simply send the value of the received id to the console. Let’s see the result
(Figure 3-18).
If we now click the button, we’ll see that the function is called, and it shows the user
id (Figure 3-19).
109
Chapter 3 RestApp Part 1
Not bad, but maybe the buttons are too big in relation to the photos. Let’s
remedy that:
<ul>
<li *ngFor="let user of users">
<img [src]="user.avatar">
<p>{{ user.first_name }} {{ user.last_name }}</p>
<div style="padding-top: 5px; width: 18rem;">
<button class="btn btn-outline-primary btn-sm"
(click)="userDetails(user.id)">
Details
</button>
</div>
</li>
</ul>
What we have done is add the btn-sm class to the button (Figure 3-20).
110
Chapter 3 RestApp Part 1
return this.http.get<User>(url);
}
It receives as a parameter an id, which will always be of the numeric type, and it will
make the corresponding request, which in this case is also of the get type.
Let’s take a little pause here to consider a bit of refactoring. The two functions
present in our service are currently seen as follows:
At this point, I include for security the complete code of our service:
111
Chapter 3 RestApp Part 1
@Injectable({
providedIn: 'root'
})
export class ReqresService {
private url = 'api/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
return this.http.get<User>(url);
}
}
ng g c components/user/user-detail --flat
The --flat flag tells angular-cli that we don’t want to create a new directory. The
reason for proceeding here is that we will group all the components related to a user in
this user directory.
The result will be as shown in Figure 3-21.
Our UserDetail component will be where the information of an individual user will
be displayed.
This is a new route for our application, and therefore we must make modifications to
our app.routes.ts file. So far, the path arrangement is as follows:
We need a new route that takes us to the details of the user, and this route will receive
the user’s id as a parameter:
Note the form of the path, ‘user/:id’. We have a ‘:id’ that works as a placeholder that
will later be replaced by a value.
If our IDE has not done it for us, we must import the corresponding component:
Now it is time to put our attention precisely on the UserDetail component. There
we need access to the id parameter. For that, we need to inject a variable of the type
ActivatedRoute. We’ll also inject the service:
113
Chapter 3 RestApp Part 1
Perfect. We also need a variable that contains the user that the service will return:
user: User = {
id: 0,
first_name: '',
last_name: '',
avatar: ''
};
constructor(
private activatedRoute: ActivatedRoute,
private reqresService: ReqresService
) {
this.activatedRoute.params.subscribe((params) => {
reqresService.getUser(params ['id'])
.subscribe((res: User) => this.user = res);
});
}
114
Chapter 3 RestApp Part 1
We have defined a route that expects a parameter and the logic that processes said
parameter. But now we need to program the logic that leads from our HomeComponent
to UserDetailComponent. At the moment, we have a userDetails function defined as
follows:
Now we can work on the component template. To do this, we are going to use a nice
Bootstrap component called Card (Figure 3-22).
115
Chapter 3 RestApp Part 1
Let’s copy the first example that appears in the Bootstrap documentation:
116
Chapter 3 RestApp Part 1
<h5 class="card-title">{{user.first_name}}</h5>
<p class="card-text">Some quick example text to build on the card
title and make up the bulk of the card’s content.</p>
<a href="#" class="btn btn-primary">User List</a>
</div>
</div>
If we now click the Details button, we will see something like Figure 3-23.
It works, but it looks too close to the edges. Let’s fix that:
117
Chapter 3 RestApp Part 1
<div class="card-body">
<h5 class="card-title">{{user.first_name}}</h5>
<p class="card-text">Some quick example text to build on the
card title and make up the bulk of the card’s content.</p>
<a href="#" class="btn btn-primary">User List</a>
</div>
</div>
</div>
We put all the content inside a div with the container and m-5 classes. In bootstrap,
containers are the basic elements of the layout and can be fixed or fluid. When using the
container class, we will obtain an element with a fixed width, but whose value for the
max-width property will change with each breakpoint. Then we add a margin at the top
to make everything look neater (Figure 3-24).
Let’s generate a one-paragraph text and use it in our card, as shown in Figure 3-26.
119
Chapter 3 RestApp Part 1
We already have a piece of text that looks more real, but now the problem is that the
design is very vertical.
Let’s change the layout of our user details page:
<div class="col-8">
<p class="card-text">Lorem ipsum pain sit amet,
consectetur
adipiscing elit. Maecenas convallis sed elit ac sagittis. Integer facilisis
scelerisque erat quis finibus. Nunc euismod molesie eros, id semper
lacus euismod
tempus. Nulla at just id felis venenatis varius. Morbi eu rutrum nibh,
at tempor
quam.
Aenean interdum eu sapien ac sodales. Aenean ut gravida eros, id
tincidunt massa.
Vivamus accumsan pain nec malaise sagittis. Sed lobortis sit amet Ligula eu
molestie.
Nulla facilisi. Nam elit dui, rutrum nec massa nec,
volutpat gravida ante. Nulla tempus hate et interdum tincidunt.
Aliquam lacinia
ulcers pretium. Phasellus convallis consequat lacus sed suscipit.
Fusce annoy
lobortis lorem nec convallis.</p>
<a href="#" class="btn btn-primary">User List</a>
</div>
</div>
</div>
</div>
</div>
The main change is that inside the card-body, we have added a row and inside it two
columns. This way:
<div class="row">
<div class="col-4">
...
</div>
<div class="col-8">
...
</div>
</div>
121
Chapter 3 RestApp Part 1
Bootstrap uses a 12-column grid system, of which we are distributing 4 to the left and
8 to the right. The result should be as shown in Figure 3-27.
Better. If we now click the User List button, we will return home, but that happens
simply because it points to a nonexistent route, #, which activates the default route of
our router:
Now yes, it works as it should. However, there is another problem that we must take
care of. If we now click any user to see their info, and depending on the connection, it
may be that in the console we see something like Figure 3-28.
122
Chapter 3 RestApp Part 1
The problem lies in the availability of the user’s image. Later, we will use a custom
pipe to deal with situations where the user’s image is missing, among others. But for the
moment, let’s modify the line where the image is shown as follows:
We have added:
*ngIf="user.avatar"
The ngIf directive allows us to display a DOM element only when the expression
passed to it evaluates to true – in this case, when the user’s image exists.
For a similar reason, we will add a condition that will show the entire div only if we
have a user:
We are going to end this section with the file that has the complete code for our
user screen:
user-detail.component.html (https://github.com/Apress/The-Beginning-Angular-
by-Victor-Hugo-Garcia/blob/main/Chapter-03/07-user-detail.component.html)
user-detail.component.ts (https://github.com/Apress/The-Beginning-Angular-
by-Victor-Hugo-Garcia/blob/main/Chapter-03/08-user-detail.component.ts)
Summary
In this chapter, we learned how to generate a new project using angular-cli. We created
new components and a service to communicate with a rest API. As for the purely
aesthetic aspect, we saw how to include bootstrap in our application, although to tell the
truth we did it in a way that may not be suitable for all cases, such as through CDNs. In
the next chapter, we will learn how to include bootstrap using npm, the node package
manager, and we will continue to add functionality to our application.
123
CHAPTER 4
RestApp Part 2
In the previous chapter, we included the fantastic Bootstrap framework in our
application, in the fastest way possible, that is, using CDNs. However, we have an
alternative, and it is to use the node package manager to install bootstrap. In this chapter,
we’ll learn how to use npm to install bootstrap. We’ll also build a simple contact form,
the logic to perform CRUD operations on our users, a loading component to improve the
user experience, and a pipe to handle the case of missing user profile images. Then you
can decide which of these two alternatives is the most convenient.
Let’s start.
Bootstrap
Currently, our index.html looks like this:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rest</title>
<base href="/">
<link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.
min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/
iJTQUOhcWr7x9JvoRxT2MZw1T"
125
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_4
Chapter 4 RestApp Part 2
crossorigin="anonymous">
</head>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abt
TE1Pi6jizo"
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/
popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF8
6dIHNDz0W1"
crossorigin="anonymous"></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/
bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/
nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
<app-root></app-root>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rest</title>
<base href="/">
126
Chapter 4 RestApp Part 2
<body>
<app-root> </app-root>
</body>
</html>
Now open a new instance of your terminal, or use the terminal of your IDE, and type
the following command:
127
Chapter 4 RestApp Part 2
In this file, there are two changes, styles and scripts (Figure 4-4).
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
128
Chapter 4 RestApp Part 2
"scripts": [
"node_modules/popper.js/dist/umd/popper.min.js",
"node_modules/jquery/dist/jquery.slim.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
],
Now, we probably do not notice any changes in the way our application is viewed
yet. We must stop the Angular development server from the terminal where it is running
with Ctrl+C and then restart it with ng serve. At this time, we will have recovered the
styles of our application.
AboutComponent
For our About page, we simply use the lorem ipsum generator to give us two paragraphs
of text. In addition, we enclose these paragraphs with a container. In about.component.
html, add:
<div class="container">
<p>Lorem ipsum pain sit amet, consectetur adipiscing elit. Nulla
mollis metus
vel velit cursus porta. Ut id enim mollis orci tempus euismod. Praesent
tristique
augue sit amet pain ullamcorper auctor. Pellentesque at nulla ut
nulla dapibus
dignissim
nec non nisi. Curabitur bibendum velit a erat iaculis, blandit
congue sapien
ornare. In thirsty fritilla elit. Sed quis aliquet orci. Mauris
massa augue,
consectetur ut cursus sit amet, dictum in eros. Nam mauris hate,
condimentum vel
pulvinar nec, egestas tincidunt nulla. Quisque a massa facilisis,
lacinia purus et, dapibus ante.</p>
129
Chapter 4 RestApp Part 2
id. Sed sodales nisl ac venenatis porta. Nullam posuere quis quam in
porta. Nunc
unfortunate pulvinar urn, vitae lobortis ex annoying non. Nullam
vestibulum,
lectus in
luctus
fringilla, dui neque pretium diam, vel ullamcorper lectus
felis vitae
magna. Fusce vitae ligula faucibus, mattis erat eleifend, pretium
felis. In hac
habitasse
platea dictumst. Cras vitae sapien faucibus, posuere erat sed, congue
erat. Aliquam
ornare ullamcorper just eget mollis. Aliquam eu bibendum enim, vel
faucibus
diam. Maecenas felis nibh, tempus pretium est in, suscipit semper neque.
Fusce felis
arcu, venenatis ac mauris sed, vehicula egestas tortor. Aenean eu enim
ulcorcorper,
condimentum lectus sit amet, lobortis erat. Vestibulum rhoncus
magna mi,
vel posuere orci vehicula sit amet.</p>
</div>
130
Chapter 4 RestApp Part 2
Forms: ContactComponent
As the first media, in app.module.ts add the following import:
These modules will allow us to easily build forms. But to be able to use them, we
must add them in the imports array:
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false, delay: 1500 }
),
ReactiveFormsModule,
FormsModule
],
@Component({
selector: 'app-contact',
templateUrl: './contact.component.html',
styleUrls: ['./contact.component.css']
})
export class ContactComponent implements OnInit {
constructor() {}
ngOnInit() {
}
131
Chapter 4 RestApp Part 2
messageForm: FormGroup;
submitted = false;
success = false;
The first one will represent our entire form, and the following ones will serve to
indicate if the form has been sent and if the validation has been successful.
Then we must inject a variable of type FormBuilder in the constructor:
Finally, we need a method that will be executed when the form is sent:
onSubmit() {
this.submitted = true;
if (this.messageForm.invalid) {
return;
}
this.success = true;
}
For now, our function simply changes the value of the property submitted to true.
Then, if the validation is not successful, the method returns; otherwise, the success
variable takes the value true.
Now let’s define our form. In contact.component.html, add:
132
Chapter 4 RestApp Part 2
<div class="form-group">
<label for="name">Name:</label>
<input type="text" formControlName="name" class="form-control">
<div *ngIf="submitted && messageForm.controls['name'].errors"
class="error">
<div *ngIf="messageForm.controls['name'].
errors['required']">
Your name is required
</div>
</div>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea formControlName="message" class="form-control">
</textarea>
<div *ngIf="submitted && messageForm.controls
['message'].errors"
class="error">
<div *ngIf="messageForm.controls['message'].errors
['required']">
A message is required
</div>
</div>
</div>
133
Chapter 4 RestApp Part 2
.error {
margin-top: -10px;
color: network;
padding: .5em;
display: inline-block;
font-size: .9em;
margin-bottom: 10px;
}
If we try to send the form without completing the required fields, we will obtain the
errors shown in Figure 4-7.
134
Chapter 4 RestApp Part 2
Let’s make a small aesthetic change. Let’s add a small margin to the container:
With this, we have finished most of the aesthetics, so now we can concentrate on
improving the app reliability.
Error Handling
So far, we have been quite optimistic when it comes to the availability of our data. But
when we are working with a remote server, there is a possibility that the data does not
arrive for different reasons. Since our ReqRes service is in charge of communicating with
the server, that is where we should include error handling.
Our method of getting all users currently looks like this:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url);
}
It returns an Observable, that is, a data stream that will be activated when someone
has subscribed to it. There is a library, RxJs, that provides a series of very useful operators
135
Chapter 4 RestApp Part 2
to manipulate the flow of data from an Observable before it is sent to subscribers. One of
them is the pipe operator, which allows us to pass the data through different operators
that are chained together to manipulate the output. Another operator is catchError,
which fires when the output of an Observable fails.
Let’s add the following amount to our service:
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.url)
.pipe(
catchError(this.handleError<User[]>('getUsers', []))
);
}
We are using the pipe operator to chain the output through other operators, in this
case catchError. When the Observable fails, catchError will catch that failure and send
the error to the function we define within the operator – in this case, a handleError
function that we have not defined yet, but we will do it next. Before we have to change
the import:
by
136
Chapter 4 RestApp Part 2
This method receives two parameters, the operation that failed and optionally a
result that will be returned instead of the expected output.
The operation has not been affected, but working with remote data, our service will
be ready to deal with the problems that may arise.
Now let’s rewrite the method that a user gets from its id:
return this.http.get<User>(url)
.pipe(
catchError(this.handleError<User>(`getUser id=${id}`))
);
}
Update a User
One of the advantages of using the in-memory Web API module is that we can simulate
CRUD operations on the data collections that we define. We are going to test this by
adding the functionality to edit a user’s name.
Just to be sure, at this point, our user-detail.component.ts should look like this:
@Component({
selector: 'app-user-detail',
templateUrl: './user-detail.component.html',
styleUrls: ['./user-detail.component.css']
})
export class UserDetailComponent implements OnInit {
user: User = {
id: 0,
first_name: '',
last_name: '',
137
Chapter 4 RestApp Part 2
avatar: ''
};
constructor(
private activatedRoute: ActivatedRoute,
private reqresService: ReqresService
) {
this.activatedRoute.params.subscribe((params) => {
reqresService.getUser(params ['id'])
.subscribe((res: User) => this.user = res);
});
}
ngOnInit() {
}
Now let’s modify the view of the user detail (user-detail.component.html) as follows:
138
Chapter 4 RestApp Part 2
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/blob/
main/Chapter-04/01-user-detail.component.html
139
Chapter 4 RestApp Part 2
We have the possibility to modify the name of the user. The Save button for the
moment calls a nonexistent method in our component. Now we will write it:
save(): void {
this.reqresService.updateUser(this.user)
.subscribe(() => this.router.navigate( ['users'] ));
}
And if our IDE has not done it for us, add the corresponding import:
Now let’s go back to our service. In reqres.service.ts, first change the import:
to
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
140
Chapter 4 RestApp Part 2
httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
If we now modify the user’s name and click Save, we will see how the data is updated
(Figures 4-9 and 4-10), and the application returns to the list of users.
141
Chapter 4 RestApp Part 2
142
Chapter 4 RestApp Part 2
This will give us the button at the top. Notice that we are referencing an addUser
method that does not exist yet. We will define it now:
addUser(): void {
this.router.navigate( ['add'] );
}
Both the route and the component that will be activated when that route is invoked
do not yet exist. Let’s start first by creating the new component:
ng g c components/user/user-add --flat
In the section where we designed the contact page, we saw how to define a form, so
we will not comment too much and just copy the code that will replace the content of
our user-add.component.html:
<div class="form-group">
<label for="first_name">First Name:</label>
<input type="text" formControlName="first_name" class="form-
control">
<div *ngIf="submitted && userForm.controls['first_
name'].errors"
class="error">
<div *ngIf="userForm.controls['first_name'].
errors['required']">
Your first name is required
</div>
</div>
</div>
143
Chapter 4 RestApp Part 2
<div class="form-group">
<label for="last_name">Last Name:</label>
<input type="text" formControlName="last_name" class="form-
control">
<div *ngIf="submitted && userForm.controls['last_name'].errors"
class="error">
<div *ngIf="userForm.controls['last_name'].
errors['required']">
Your last name is required
</div>
</div>
</div>
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/blob/
main/Chapter-04/02-user-add.component.html
We have a simple form with two inputs. Submitting the form invokes an onSubmit
method that of course we haven’t created yet.
We will now start with the code for our class. First, let’s add the necessary imports:
userForm: FormGroup;
submitted = false;
constructor(
private formBuilder: FormBuilder,
144
Chapter 4 RestApp Part 2
Then add a small method to access the controls of the form in a shortened way:
onSubmit(): void {
this.submitted = true;
if (this.userForm.invalid) {
return;
}
We verify that the form is valid, and if so, we proceed to call the addUser method of
our service, which of course does not exist yet.
Let’s go to our service and write the method, which at this point will be very
familiar to us:
145
Chapter 4 RestApp Part 2
.pipe(
catchError(this.handleError<User>('addUser'))
);
}
If everything went well, we can add a new user (Figures 4-11 and 4-12) and see it in
the list.
We have not included the functionality to upload an image, since we are simulating
data from a server in memory, so we simply encode the avatar.
Deleting a User
First, we will add a button in our home component that allows us to delete a user. We will
do it under the button that calls the detail view:
146
Chapter 4 RestApp Part 2
deleteUser(user: any) {
this.users = this.users.filter( u => u !== user );
this.reqresService.deleteUser(user).subscribe();
}
What we should note here is that although we are calling a deleteUser method of our
service that will be in charge of the deletion, it is the responsibility of the component to
update its own list of users.
Now in our ReqresService, let’s add the method in charge of the elimination:
147
Chapter 4 RestApp Part 2
Loading
By default, our API returns the data with a delay of 500 ms to simulate latency, but in a
real case, this time could be much longer. Let’s simulate a slower response by altering
the settings in our AppModule as follows:
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, { dataEncapsulation: false, delay: 1500 }
),
We have introduced a delay of a second and a half, so it is very likely that we will end
up with a blank screen (Figure 4-14).
From an interface point of view, it is not a good idea to display an idle screen when a
process is in progress. It would be convenient to show a load indicator, to give feedback
to our user.
We are going to do just that, for which we will use a great icon set such as Font
Awesome.
Let’s open the console and write the following command:
We are using npm to install the package, just like we did with Bootstrap.
As you remember, we must modify the angular.json file, adding the following line:
"node_modules/@fortawesome/fontawesome-free/css/all.css"
"node_modules/bootstrap/dist/css/bootstrap.min.css",
We will need to stop the angular-cli live server, pressing Ctrl+C, and restart it for the
changes to be registered.
148
Chapter 4 RestApp Part 2
To verify that Font Awesome has been installed successfully, let’s modify the home.
component.html as follows:
The --style none flag tells angular-cli not to create a style file for the component.
Let’s replace the content of loading.component.html with:
<app-loading> </app-loading>
150
Chapter 4 RestApp Part 2
Of course, the idea is that the component is only displayed while the data is being
received. To do this, we will add a loading property in our HomeComponent.
In home.component.ts, add:
getUsers() {
this.loading = true;
this.reqresService.getUsers().subscribe(
(res: User[]) => {
this.users = res;
this.loading = false;
},
(err) => {
console.error(err);
}
);
}
loading takes the value true when the function is entered and changes to false when the
data is available. Now, to manipulate the visibility of our component, we modify the line:
151
Chapter 4 RestApp Part 2
<app-loading *ngIf="loading"></app-loading>
It will only be displayed as long as the data has not been received. Similarly, the rest
of our page should behave in reverse. We will enclose the rest of the code in a container:
In this way, we will have a load indicator while the data is received.
Pipes
Every nontrivial application will at some point receive data that it must present to the
user, and it will be difficult for that data to arrive in the most appropriate way to display
it. We will need to format the data in a way that makes sense for our application. And it is
also to be expected that these formats will be repeated in different parts.
152
Chapter 4 RestApp Part 2
To do this, Angular has a tool called “pipes.” Pipes are elements that allow us to
transform the way data is presented in the user interface. Angular has a large number of
default pipes, the list of which can be found in
https://angular.io/api?type=pipe
The syntax of a pipe is basically the following:
data | pipe:params
That is, the data is sent to the pipe using the | and also the pipe can receive
parameters that are entered with “:”.
But in addition to the default pipes, Angular gives us the possibility to define our own
pipes. We even have a command to generate the skeleton necessary to build it.
The pipe that we will build will help us deal with cases in which a user does not have
a profile image.
Instead of getting an error in the console, indicating that the resource is not
available, we will show an alternative image. You can download the image from here
(https://drive.google.com/open?id=1yEuXM4iOnZj7dgNCE2DmUsoCx9buU9QO).
In our project, we have an assets directory. Let’s create there a new directory called
img and copy the image into it (Figure 4-16).
ng g p pipes/noimage
This should create a pipes directory at the same height as the components directory.
Inside this folder, we will have a file noimage.pipe.ts with the following content:
@Pipe({
name: 'noimage'
})
153
Chapter 4 RestApp Part 2
The value argument represents the data that is sent to the pipe to be transformed. We
also have an optional argument args.
Let’s modify the pipe like this:
@Pipe({
name: 'noimage'
})
export class NoimagePipe implements PipeTransform {
return image;
}
What this pipe does is check if an image exists. If not, a string corresponding to the
address of our default image is returned.
Now in user-detail.component.html, we can replace the line where the user’s image
is included with the following:
We can verify that our pipe works by intentionally sending a null value through it:
154
Chapter 4 RestApp Part 2
Having verified that it works, let’s return the line to its previous value:
Now we can also use our pipe in the user list. In home.component.html, add:
And in case that there is no profile image, the list will look as shown in Figure 4-18.
155
Chapter 4 RestApp Part 2
Summary
In this chapter, we learned how to use npm to install Bootstrap. We also built a simple
contact form, the logic to perform CRUD operations on our users, a loading component
to improve the user experience, and a pipe to handle the case of missing user profile
images. All this knowledge is a valuable piece that we can incorporate into projects of
any complexity. And we have barely scratched the surface. In the next chapter, we will
begin building a much more complex application.
156
CHAPTER 5
AuthApp
In practically all the applications that we develop, we must provide the registration and
user login functionality.
This involves dealing with two concepts: authentication and authorization.
Authentication means recognizing if the user who tries to enter the application is
who they claim to be. Authorization refers to restricting the user’s access to certain parts
of the application, according to their level of permission.
And it is also increasingly common – ubiquitous I would say – that both the login and
the registration of users are done through social networks.
This functionality of the applications involves not a little time and effort if it is done
from scratch. And everything becomes more tedious if we take into account that the
code will be practically the same for each of these applications.
Therefore, having a way to standardize the authentication and authorization of users
is a great advantage.
Fortunately, we have platforms that offer just that. And among them, Auth0
(https://auth0.com/) is probably the most widespread.
Auth0
According to its website, Auth0 provides a universal platform for authentication and
authorization for applications, web, mobile, and legacy.
The use of the platform has a cost, but it has a free plan for up to 7000 active users
with unlimited logins.
Creating an Account
Let’s start creating an account by clicking SIGN UP (Figure 5-1).
157
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_5
Chapter 5 AuthApp
By clicking SIGN UP, we will reach the screen shown in Figure 5-2.
We can register with our email and then choose a password or through one of the
social networks.
158
Chapter 5 AuthApp
The following screen will suggest a domain name that will be used as endpoints of
the API of the clients of our applications. We can leave the one shown by default or enter
our own (Figure 5-3).
The next step asks us for certain information about the type of account. We can leave
the options as shown in Figure 5-4.
159
Chapter 5 AuthApp
We click NEW APPLICATION, which leads to the next screen (Figure 5-6).
160
Chapter 5 AuthApp
Let’s enter a name for our application. It is not something so important, we can
change it later. We select CREATE, and we will see the following screen. We are asked
what kind of technology we will use for the application, in order to show us a quick guide
corresponding to the selected technology – in our case, Angular (Figure 5-7).
161
Chapter 5 AuthApp
The next screen will show us the quick guide. From there, we have the possibility to
download an example application, but in this case we will build it ourselves (Figure 5-8).
162
Chapter 5 AuthApp
As with bootstrap, jQuery, and popper, once the library is installed, we need to add
the corresponding reference in the scripts array of the angular.json file:
"scripts": [
163
Chapter 5 AuthApp
"node_modules/popper.js/dist/umd/popper.min.js",
"node_modules/jquery/dist/jquery.slim.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js",
"node_modules/auth0-js/dist/auth0.js"
]
Personally, however, the installation using npm and the reference in the angular.json
file is the alternative that I recommend.
AuthService
Our next step will be to create a service called simply “auth”:
ng g s services/auth
Every time we go back to auth0.com, we will find our dashboard (Figure 5-9).
164
Chapter 5 AuthApp
In the menu on the left, we can access the Applications option (Figure 5-10).
Here, we will see a list of all the applications we have created (Figure 5-11).
165
Chapter 5 AuthApp
In this case, we can see our newly created authapp application. By clicking it, we
access its details (Figure 5-12).
166
Chapter 5 AuthApp
In the quickstart tab, we will always be presented with the technology choice option
we saw at the time of creating our application. By selecting Angular, we will observe
custom information for the framework.
In the section Create an Authentication Service, the following code is shown to be
used in the service that we just created with angular-cli:
// src/app/auth/auth.service.ts
@Injectable()
export class AuthService {
Let’s copy it to our auth.service.ts file, but with a small change. The line
redirectUri: 'http://localhost:3000/callback',
must be replaced by
redirectUri: 'http://localhost:4200/callback',
As you may have guessed, this is because our server is running on port 4200.
You may also have seen that we do not have a route or a callback component yet, but
we will take care of that later.
Following the documentation of the quickstart, we complete our service with the
following methods. In auth.service.ts, add:
If you get the error “Could not find a declaration file for module ‘auth0-js’”, then you
need to type:
at the terminal.
Although we have created our service, it will not be available until we add the
corresponding reference in app.module.ts:
providers: [
AuthService
],
169
Chapter 5 AuthApp
At this point, we are now in a position to check that everything is going well so far.
But before that, let’s install bootstrap, jQuery, and popper. We have already seen how to
do it, so I will skip that step and assume that the libraries have been installed correctly.
For this, we will have to add a couple of buttons that deal with the login and logout.
These buttons will be in a navbar component, which we will create next.
NavbarComponent
Let’s type:
ng g c components/navbar
170
Chapter 5 AuthApp
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
</ul>
<span class="navbar-text">
Navbar text with an inline element
</span>
</div>
</nav>
we are going to replace the text of the last span with a couple of buttons:
<button
class="btn btn-primary btn-margin"
*ngIf="!auth.isAuthenticated()" (click)="login()">
Log In
</button>
<button
class="btn btn-primary btn-margin"
*ngIf="auth.isAuthenticated()" (click)="logout()">
Log Out
</button>
Note that we are referring to a property, auth, that we have not yet defined. We are
also invoking two methods, login and logout, which do not yet exist.
Let’s replace the contents of navbar.component.ts with the following:
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
171
Chapter 5 AuthApp
ngOnInit() {
}
login() {
this.auth.login();
}
logout() {
this.auth.logout();
}
Before we can run the application in the browser, we create some other components:
home, about, and private, all within the components directory.
Routing
Our next step is to create the routes file, app.routes.ts, and modify the navbar to add
navigation to the new components. This is something that we have seen before.
The code of app.routes.ts will be as follows:
172
Chapter 5 AuthApp
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
imports: [
BrowserModule,
AppRoutingModule
],
173
Chapter 5 AuthApp
<button
class="btn btn-primary btn-margin" *ngIf="auth.isAuthenticated()"
(click)="logout()">
Log Out
</button>
</div>
</nav>
<app-navbar> </app-navbar>
<div class="container">
<router-outlet> </router-outlet>
</div>
Excellent. Now if we can open our application in the browser, we should see
something like Figure 5-13.
Do not click the login button yet. Let’s return to the dashboard of our application on
the site of auth0 and visit the Settings tab (Figure 5-14).
Auth0 Settings
There are two pieces of information that we must complete as shown in Figure 5-15.
175
Chapter 5 AuthApp
Very well. Now if we click the Login button, we will see the screen shown in
Figure 5-16.
176
Chapter 5 AuthApp
Before trying to log in, however, we need to take a few more steps.
In our service, we have the following line:
redirectUri: 'http://localhost:4200/callback',
When a user logs in with Auth0, they are taken out of the application. Once the user
is authenticated, it is returned to the application.
You can define any route to which the user returns. However, the auth0
documentation recommends creating a dedicated path.
177
Chapter 5 AuthApp
We are going to create a new component called callback with the following content
in its template:
<div class="loading">
Loading
</div>
That’s it. Instead of using a text, “Loading,” we could use some indicator, for example,
with Font Awesome or a vector graphic, but for now it’s enough.
Finally, we must modify the code of app.component.ts in the following way:
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'authapp';
ngOnInit() {
if (this.auth.isAuthenticated()) {
// this.auth.renewTokens ();
}
}
}
178
Chapter 5 AuthApp
This happens because we are using auth0 with development keys, which allow us to
test the application quickly. But using these development keys has important limitations
that are explained here:
Auth0 Devkeys
https://auth0.com/docs/authenticate/identity-providers/social-identity-
providers/devkeys
If we want to use the social login, we must register our application with the chosen
social provider, as explained here (https://auth0.com/docs/authenticate/identity-
providers#social).
For example, in the case of Google, you must follow the steps shown here (https://
marketplace.auth0.com/integrations/google-social-connection).
For now, let’s leave the commented line. Alternatively, we can register with an email
account (Figure 5-17).
179
Chapter 5 AuthApp
Important If we use this method, registration and login through user and
password, the line this.auth.renewTokens(); must be uncommented.
But if we initiate a session, for example, with Google or another social provider,
we will have to comment it out unless we have registered the application in the social
provider.
If we now perform the login, we will see that the Login button is hidden and the
Logout button is displayed (Figure 5-19).
We can navigate through our application, and the Logout button will also perform
its work.
180
Chapter 5 AuthApp
Very good, we are much closer. However, there is an important detail that some of
you may have already noticed.
So far, we have followed the quickstart documentation to the letter. We can log in and
browse the application. But what happens if after having logged in we refresh the page?
We will see how the Logout button disappears and the Login button appears again.
What is happening?
Local Storage
The localLogin function at this time has the following form, in auth.service.ts:
As we can see, the data is being stored in variables in memory, so when you reload
the application the values of these variables are lost.
Let’s replace the code of the function in the following way:
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
}
What we are doing is saving the value of the variables in the local storage.
We must also modify the isAuthenticated and logout methods as follows. In auth.
service.ts, add:
181
Chapter 5 AuthApp
After logging in, we can see how the variables are stored in the localStorage using the
Google developer tools (Figure 5-20).
182
Chapter 5 AuthApp
With these modifications made, our application will keep the session status started
even when we refresh the page.
It is debatable if it is a good practice to keep variables in localStorage. A more secure
alternative would be to perform the session management in the backend, for example,
through a service developed in NodeJS.
In successive editions, we will see how to achieve this, but for the moment, we have
made great progress, although there are still more things to see.
Securing Routes
Our application is working correctly. You can start and end a session. However, there is a
page of our application, private, which is displayed whether the user has been identified
or not (Figure 5-21).
to
183
Chapter 5 AuthApp
We can now see that the link is not visible if the user has not been identified
(Figure 5-22).
However, this does not give us robust protection, since if the user knows the route,
they can still visit it by typing the address in the browser (Figure 5-23).
We need a way to prevent the user from accessing routes for which it is not
authorized, and fortunately we have a simple way to do it.
AuthGuard
Let’s use the following command:
ng g guard services/auth`
With this command, we are generating a route guard. It is a mechanism that allows
us to protect certain routes from unauthorized viewing. The command will give us the
following content:
184
Chapter 5 AuthApp
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
return true;
}
}
to
{
path: 'private',
component: PrivateComponent,
canActivate: [ AuthGuard ]
},
That is all. However, since the canActivate method always returns true, no change
will be seen.
We can verify that by changing the return value true to false:
canActivate(
185
Chapter 5 AuthApp
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
return false;
}
Now the user cannot visit the route. However, a method that does not have decision-
making capacity is not useful. Let’s fix that:
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
if ( this.auth.isAuthenticated()) {
return true;
}
return false;
}
}
With these changes, the route will now only be accessible when the user is identified.
Note The route guards are only for the UI. They do not provide protection against
access to an API. We must always force authentication and authorization in
our APIs.
However, the route guards method does have utility when it comes to preventing
non-authorized navigation.
To make sure everything is working correctly, I will provide the code for all the
components and services.
186
Chapter 5 AuthApp
CallbackComponent
callback.component.html
<div class="loading">
Loading
</div>
callback.component.ts
@Component({
selector: 'app-callback',
templateUrl: './callback.component.html',
styleUrls: ['./callback.component.css']
})
export class CallbackComponent implements OnInit {
constructor() {}
ngOnInit() {
}
File
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-05/02-callback.component.ts
NavbarComponent
navbar.component.html
187
Chapter 5 AuthApp
<button
class="btn btn-primary btn-margin"
*ngIf="auth.isAuthenticated()"
(click)="logout ()">
Log Out
</button>
</div>
</nav>
File
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-05/03-navbar.component-v2.html
188
Chapter 5 AuthApp
navbar.component.ts
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.css']
})
export class NavbarComponent implements OnInit {
ngOnInit() {
}
login() {
this.auth.login();
}
logout() {
this.auth.logout();
}
File
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-05/04-navbar.component.ts
AuthService
auth.service.ts
// src/app/auth/auth.service.ts
189
Chapter 5 AuthApp
@Injectable()
export class AuthService {
190
Chapter 5 AuthApp
this.router.navigate(['/home']);
console.log(err);
alert(`Error: ${err.error}. Check the console for further details.`);
}
});
}
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
}
191
Chapter 5 AuthApp
File
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-05/05-auth.service.ts
AuthGuard
auth.guard.ts
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor( private auth: AuthService) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
if ( this.auth.isAuthenticated() ) {
return true;
}
return false;
}
}
File (https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-
Garcia/blob/main/Chapter-05/06-auth.guard.ts)
192
Chapter 5 AuthApp
In the list of Applications, we will select our application **authapp** (Figure 5-25).
193
Chapter 5 AuthApp
Then, in the Quickstart tab, we will again be asked to select the type of technology
(Figure 5-26).
As always, select Angular. We will access the guide that has helped us to configure
our service. At the bottom of the screen, we can see links to new tutorials (Figure 5-27).
194
Chapter 5 AuthApp
Select User Profile. There we can see the steps to recover and display the user profile.
Let’s start.
We will make our first modification in our AuthService. There we have the following
lines that initialize our auth object:
scope: 'openid'
by
This will allow us to have access to the profile of the user who is logged in.
Next, let’s add the following property:
userProfile: any;
195
Chapter 5 AuthApp
This method allows us to capture the profile information and save it in the
userProfile property. But, where does this information come from? The answer is from
the social provider that the user has used to log in.
Now, address to our PrivateComponent, at private.component.ts, and modify it as
follows:
@Component({
selector: 'app-private',
templateUrl: './private.component.html',
styleUrls: ['./private.component.css']
})
export class PrivateComponent implements OnInit {
profile: any;
196
Chapter 5 AuthApp
ngOnInit() {
if (this.auth.userProfile) {
this.profile = this.auth.userProfile;
} else {
this.auth.getProfile((err: any, profile: any) => {
this.profile = profile;
});
}
}
Let’s follow the instructions in the quick guide and change the private.component.
html code:
197
Chapter 5 AuthApp
We can see all the information about the profile provided by the provider, in this case
Google. It works, but it looks pretty bad.
Let’s change the content to the following:
198
Chapter 5 AuthApp
</div>
</div>
</div>
</div>
</div>
In this way, we have concluded with the basics of Auth0. There are still many items
to explore about this tool, but we have acquired the necessary knowledge to add robust
authentication and authorization functionality to our application.
Just to be sure, here is our final AuthService:
autn.service.ts
// src/app/auth/auth.service.ts
@Injectable()
export class AuthService {
userProfile: any;
199
Chapter 5 AuthApp
domain: '[your-domain]',
responseType: 'token id_token',
redirectUri: 'http://localhost:4200/callback',
scope: 'openid profile'
});
200
Chapter 5 AuthApp
localStorage.setItem('access_token', authResult.accessToken);
localStorage.setItem('id_token', authResult.idToken);
localStorage.setItem('expires_at', expiresAt);
}
201
Chapter 5 AuthApp
File
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-05/07-auth.service.ts
Exercise
As part of your practice, you can add sign-in with auth0 to the rest application in
Chapter 4.
Summary
In this chapter, we have seen how to use auth0 to add authentication and authorization
to our application. Although auth0 has a cost, it is well worth it if we take into account
the robustness and security, as well as saving time by avoiding writing large amounts of
code. In addition, we have obtained other tools such as sandbox, administration board,
etc. We have thus acquired a fantastic tool for our arsenal as developers. And we have
not seen anything yet. In the next chapters, we will develop applications with complex
layouts, which interact with APIs to perform CRUD operations.
202
CHAPTER 6
MongoDB
MongoDB is a scalable and flexible NoSQL document database. Instead of saving
the data in the form of records, MongoDB saves them in documents in BSON format,
which is similar to JSON, which allows them to be more easily integrated into different
applications.
First of all, we will download the installers from this link: www.mongodb.com/try/
download/community (Figure 6-1).
203
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_6
Chapter 6 Blog App Part 1
This will install the MongoDB service, but we will need a GUI tool in order to explore
our documents. So, we also need to download MongoDB Compass, which is the GUI for
MongoDB.
Once downloaded and installed, we can run the MongoDB Compass. The first time
the screen will look similar to Figure 6-2.
204
Chapter 6 Blog App Part 1
In this screen, we can establish a new connection, leaving in this case the default
values. Note that the server will be listening on port 27017. Another point to keep in
mind is that for this example we will not use any type of database authentication, which
of course is not in any way advisable in a real environment.
If you wish, however, you can choose a user-based authentication and password
in the Authentication tab visible in the Advanced Connection Options. You can
access these tabs by clicking Advanced Connection Options in the previous screen
(Figure 6-3).
205
Chapter 6 Blog App Part 1
At this point, if we try to connect, we could get the error shown in Figure 6-4.
206
Chapter 6 Blog App Part 1
This is because we need to make sure that the MongoDB service is running. Now, the
method to start a service differs from operating system to operating system. In Windows,
you can access the services by typing Services and then looking for MongoDB Server
(Figure 6-5).
207
Chapter 6 Blog App Part 1
208
Chapter 6 Blog App Part 1
We will not use this interface to create a new database, although we could do it. We
will simply leave the server running. We will have to start it every time our application
exchanges data with the backend.
209
Chapter 6 Blog App Part 1
This will take us to a grid with a good number of templates that exemplify the
bootstrap capabilities (Figure 6-8).
We have the appropriate template for our blog, as well as the one we will use for
the login.
210
Chapter 6 Blog App Part 1
All these examples can be downloaded so that we can use them (Figure 6-9).
211
Chapter 6 Blog App Part 1
ng new frontend
At the same level as the frontend directory, let’s add another new one called
backend, which will contain the code of Node.js corresponding to our APIs.
212
Chapter 6 Blog App Part 1
Let’s open the frontend folder directory in our IDE. We will see something like
Figure 6-12.
Installing Bootstrap
Next, let’s add bootstrap to our application. We have already seen how to do this in
previous chapters, so you can refer to them if necessary.
213
Chapter 6 Blog App Part 1
In addition, we must install jQuery and popper.js, which has also been covered
previously.
Now let’s open a console inside the frontend directory and run the Angular server:
ng serve -o
We will see the screen to which we must already be accustomed (Figure 6-13).
Login Component
We will now work on the component that we will use for the login. First, generate it with
the command line:
ng g c components/login
<div class="main">
<form class="form-signin">
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="email" id="inputEmail" class="form-control"
placeholder="Email address" required autofocus>
214
Chapter 6 Blog App Part 1
The previous code is the one that is present in the bootstrap sign-in example, which
we downloaded earlier, with some modifications.
We have to add the date variable inside login.component.ts:
.main {
text-align: center! important;
display: -ms-flexbox;
display: flex;
-ms-flex-align: center;
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
}
.form-signin {
width: 100%;
max-width: 330px;
padding: 15px;
215
Chapter 6 Blog App Part 1
margin: auto;
}
.form-signin .checkbox {
font-weight: 400;
}
.form-signin .form-control {
position: relative;
box-sizing: border-box;
height: auto;
padding: 10px;
font-size: 16px;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
<app-login> </app-login>
216
Chapter 6 Blog App Part 1
To be sure that everything is correct, I will give you the links to the source code of the
login component:
login.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/01-login.component.html
login.component.css
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/02-login.component.css
login.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/03-login.component-v1.ts
217
Chapter 6 Blog App Part 1
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
In order for our module to be usable, we must add it in the imports array of app.
module.ts:
imports: [
BrowserModule,
AppRoutingModule
],
Now we can replace the content of our app.component.html with the following:
<router-outlet> </router-outlet>
User Model
Our login screen is already displayed, but still it is not functional. The idea is that we will
send a username and password through it, and that user and password will be validated
in the server, which will return a response to see if the user can be admitted to the
application or not.
Now we will concentrate on creating a user model, a simple class that is responsible
for saving the user and password that will be sent.
218
Chapter 6 Blog App Part 1
Create a directory called models inside the app folder, and within this models
directory, create a user.model.ts file with the following content:
As we mentioned, it’s a very simple class, with two properties: username and
password. We could have reduced it even more in the following way:
constructor() {}
}
Login Service
We need a service that makes requests to the backend (which we do not yet have),
sending the user and password to be validated. We use:
ng g s services/login
219
Chapter 6 Blog App Part 1
@Injectable({
providedIn: 'root'
})
export class LoginService {
constructor() {}
}
As a first step, add a login method, which will receive an object of type User, and a
logout method. We will not develop the logic of these methods yet:
login(user: User) {
logout() {}
For now, this is enough. Now we will make the necessary changes in our login
component to be able to use the service.
220
Chapter 6 Blog App Part 1
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {
date = new Date();
loginForm: FormGroup;
submitted = false;
loading = false;
user: User = new User('', '');
error: string = '';
constructor(
private formBuilder: FormBuilder,
private loginService: LoginService
) {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
}
ngOnInit() {}
onSubmit() {
this.submitted = true;
this.loading = true;
221
Chapter 6 Blog App Part 1
console.log( this.user );
}
}
There are many changes, but we are going to analyze them one by one. First, we have
the following new import:
loginForm: FormGroup;
submitted = false;
loading = false;
user: User = new User('', '');
error: string = '';
loginForm will be the variable by which we identify the group of controls in our form. In
more complex forms, we would have more than one group of controls, but in this case, it
is not necessary since we have only two.
The variable submitted is a flag that is set to true when the form is submitted, and
loading will be true while waiting for the response from the server. Then we have the
model, and error will show any error returned by the API.
constructor(
private formBuilder: FormBuilder,
private loginService: LoginService
) {}
222
Chapter 6 Blog App Part 1
ngOnInit() {
this.loginForm = this.FormBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
}
We must declare two properties in our constructor, one for our FormBuilder and one
for our LoginService.
Then, in the OnInit function, we instantiate our FormBuilder, passing it as a
parameter two fields, which will be mandatory.
Then we have a getter:
The purpose of this function is to allow us to reference the controls of the form by
means of the identifier f.
Finally, the onSubmit function is executed when the button of the form is clicked.
There, a validation is carried out on the required fields, and if this fails, the process will
stop. Otherwise, in this case we will simply show the values per console.
It is important to make the following changes in our app.module.ts. First, add the
following import:
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
AppRoutingModule
]
<div class="main">
<div *ngIf="error" class="alert alert-danger">{{error}}</div>
223
Chapter 6 Blog App Part 1
<div class="form-group">
<button [disabled]="loading"
class="btn btn-lg btn-primary btn-block">
<span *ngIf="loading"
class="spinner-border spinner-border-sm mr-1">
</span>
Sign in
</button>
</div>
</form>
</div>
224
Chapter 6 Blog App Part 1
formControlName="username"
We are linking the text box with the username control of our FormGroup. If instead of
using the Reactive Forms approach, we have used a Template approach, we would have
something like this:
[(ngModel)] ="username"
But if we try to log in without entering the username or password, we will see the
corresponding errors (Figure 6-16).
225
Chapter 6 Blog App Part 1
If, on the contrary, we enter a username and password, we will obtain the expected
console output (Figure 6-17).
getUserName() {
return this.username;
}
getPassword() {
return this.password;
}
Now we can complete the definition of the login method in login.service.ts. But
before that, we need to declare a private property in our constructor method:
login(user: User) {
return this.http.post('/api/user/login', {
username : user.getUserName(),
226
Chapter 6 Blog App Part 1
password : user.getPassword()
});
}
Of course, we have not written the API yet, but we will do it soon. The login method
will return an observable, to which we can subscribe from our login component. Here is
the onSubmit method of login.component.ts:
onSubmit() {
this.submitted = true;
this.loading = true;
However, we must also add a new import in the imports array of our app.module.ts:
HttpClientModule
227
Chapter 6 Blog App Part 1
If everything is correct, we will see the login screen. If we enter a username and
password, we will get the error shown in Figure 6-19.
However, this is an expected error, since the api/user/login path does not exist. But it
tells us that the request is being made.
To finish with this first part, I will provide links to the code of our components,
models, services, and modules, in order to make sure that everything is working
correctly.
228
Chapter 6 Blog App Part 1
Application Code
LoginComponent
login.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/04-login.component-v2.ts
login.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/05-login.component-v2.html
User Model
user.model.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/06-user.model.ts
LoginService
login.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/07-login.service.ts
Routes
app.routes.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/08-app.routes.ts
AppModule
app.module.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-06/09-app.module.ts
Summary
In this chapter, we have laid the foundations of our application. By far, this will be
the most complex application built so far by us, but it will undoubtedly help us to put
together all the concepts seen so far. In the next chapter, we will work with the backend
by writing the API to authenticate the user. It will be developed in NodeJS and will
communicate with a database in MongoDB.
229
CHAPTER 7
NodeJS
NodeJS is an execution environment for JavaScript oriented to asynchronous events. It
allows us to build scalable network applications, which support multiple simultaneous
connections. It uses separate execution threads for this.
NodeJS undoubtedly deserves a book by itself, but we will see here enough to give
life to a simple but robust API.
To make things even easier, we will use Express.
Express
Express is a framework that works on the Node execution environment. It facilitates the
creation of applications, shortening the development time.
MongoDB
We already talked about MongoDB in the previous chapter. In this, we will connect to a
MongoDB database using Mongoose.
231
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_7
Chapter 7 Blog App Part 2
Mongoose
Mongoose is an Object Document Mapper written in JavaScript, which allows us to
define and manipulate in a simple way dynamic objects in a MongoDB database,
mapping those objects to documents.
These tools will allow us to build a robust and efficient API, which will serve as an
excellent backend for our application.
npm init
When executing this command, we will be asked a series of questions. We can simply
leave the default values by pressing enter on each of the options.
As a result of the process, we will obtain a file called package.json with the following
content:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
232
Chapter 7 Blog App Part 2
At the end, we will have a new directory, node_modules, with all the framework
packages (Figure 7-1).
At the same level as the directory, we create a file called index.js with the following
content:
The code is simple. We are defining a path: /api/user/login, which will receive a GET
request, and that will simply return a text message.
At the same time, we are indicating that the requests will be heard on port 3000. We
could have used another port, as long as it was unoccupied.
Now, from the console, and always standing inside the backend directory, let’s run
the command:
node index.js
We’ll see a message indicating that the app is listening on port 3000 (Figure 7-2).
233
Chapter 7 Blog App Part 2
This package will allow you to extract parameters from the requests received by the
server. Then let’s run:
As we mentioned before, this package will allow you to easily interact with the
MongoDB database.
234
Chapter 7 Blog App Part 2
If we wish, we can save this connection as one of our favorites (Figure 7-5).
235
Chapter 7 Blog App Part 2
We will need to define the name of the database and the name of one collection.
It is tempting to assimilate the concept of collection to a table, but a collection is a set
of documents. Documents inside a collection can have a different number of fields
(Figure 7-7).
Go ahead and type blog as the database name and user as the collection, then click
Create Database.
236
Chapter 7 Blog App Part 2
Note that we have not had to specify any type of structure for the collection, but we
don’t need to. We can see the newly added database (Figure 7-8).
Click the name of the database; we’ll see the only collection (Figure 7-9).
Click the collection. Then select the option Insert Document from the ADD DATA
list (Figure 7-10).
237
Chapter 7 Blog App Part 2
We’ll be presented with a pop-up window. Replace the content with the following:
{
"name": "Administrator",
"username": "Admin",
"password": "1234"
}
Then click Insert. We can see the newly added document (Figure 7-11).
We have in this way a user against whom we can test the login, but we still have to
take some steps to connect the frontend with the backend.
238
Chapter 7 Blog App Part 2
Let’s go to the frontend directory and inside this to the src directory. Let’s add a file
called proxy.json with the following content:
{
"/api/*": {
"target": "http://localhost:3000",
"secure": "false"
}
}
Now, let’s open the angular.json file. In it, we will find the following section:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
},
"development": {
"browserTarget": "frontend:build:development"
}
},
"defaultConfiguration": "development"
},
Replace it with:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
},
"development": {
"browserTarget": "frontend:build:development",
"proxyConfig": "src/proxy.json"
}
},
239
Chapter 7 Blog App Part 2
"defaultConfiguration": "development"
},
"proxyConfig": "src/proxy.json"
This step is necessary because we are hosting our client application and the server
within the same parent directory.
If we had already executed:
ng serve -o
We must stop the Angular server with Ctrl+C and restart it.
Let’s go back to the backend folder again and create a directory called models there.
Let’s add a file named user.js with the following content:
// create a schema
const userSchema = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
name: { type: String }
}, { collection: 'user' });
module.exports = User;
240
Chapter 7 Blog App Part 2
mongoose.set('strictQuery', true);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
})
});
});
241
Chapter 7 Blog App Part 2
We are requiring the body-parser module to obtain the parameters that are sent
in the requests to the server. Then we require mongoose because we will work with a
MongoDB database. We also define a constant that will reference the url of our database,
and finally we require our user model to interact with the collection of the same name.
We will not analyze the rest of the code in detail, but what it does is search the user
collection for a user whose username and password match the one received.
If we had previously started the server with:
node index.js
we will need to halt the execution and start the server again.
If now we try to log in, and assuming that we enter as user Admin and password
1234, we will see the output by console (Figure 7-12).
It is to be expected, since in our login component we are simply sending the result
received from the server to the console. What we should do is redirect the user to the
main page, which we do not have yet.
Home Component
Go ahead and create a component called home; we have already done this several times.
Let’s replace the contents of home.component.html with the following. It’s a lot of
content, so it’s probably better to use this file:
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/01-home.component-v1.html
<div class="container">
<header class="blog-header py-3">
242
Chapter 7 Blog App Part 2
243
Chapter 7 Blog App Part 2
244
Chapter 7 Blog App Part 2
245
Chapter 7 Blog App Part 2
<title>Placeholder</title>
<rect width="100%" height="100%" fill="# 55595c"> </rect>
<text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="blog-post">
<h2 class="blog-post-title">Sample blog post</h2>
<p class="blog-post-goal">January 1, 2014 by <a
href="#">Mark</a> </p>
246
Chapter 7 Blog App Part 2
</blockquote>
<p>Etiam carries
<em>sem embarrassed magna</em> mollis euismod.
Cras mattis consectetur purus sit amet fermentum.
Aenean lacinia bibendum nulla sed consectetur.</p>
<h2>Heading</h2>
<p>Vivamus sagittis lacus vel augue
laoreet rutrum faucibus pain auctor. Duis mollis,
est non commodo luctus, nisi erat porttitor ligula,
eget lacinia hate sem nec elit. Morbi leo risus,
porta ac consectetur ac,
vestibulum at eros.</p>
<h3>Sub-heading</h3>
<p>Cum sociis natoque penatibus
et magnis dis parturient montes, nascetur ridiculus mus.</p>
<pre> <code>Example code block</code> </pre>
<p>Aenean lacinia bibendum nulla sed consectetur.
Etiam carries sem embarrassment magna mollis euismod.
Fusce dapibus, tellus ac cursus commodo, tortor
mauris condimentum nibh, ut fermentum massa.</p>
<h3>Sub-heading</h3>
<p>Cum sociis natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus. Aenean lacinia
bibendum nulla sed consectetur. Etiam carries sem embarrassment
magna mollis euismod. Fusce dapibus, tellus ac cursus commodo,
tortor mauris condimentum nibh, ut fermentum massa just sit
amet risus.</p>
<ul>
<li>Praesent commodo cursus magna, vel
scelerisque nisl consectetur et.</li>
<li>Donec id elit non my gravida at eget metus.</li>
<li>Nulla vitae elit libero, a pharetra augue.</li>
</ul>
<p>Donec ullamcorper nulla non metus auctor fringilla.
Nulla vitae elit libero, to pharetra augue.</p>
247
Chapter 7 Blog App Part 2
<ol>
<li>Vestibulum id ligula porta felis euismod
semper.</li>
<li>Cum sociis natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus.</li>
<li>Maecenas sed diam eget risus varius blandit
sit amet non magna.</li>
</ol>
<p>Cras mattis consectetur purus sit amet fermentum.
Sed posuere consectetur est at lobortis.</p>
</div>
<! - /.blog-post ->
<div class="blog-post">
<h2 class="blog-post-title">Another blog post</h2>
<p class="blog-post-goal">
December 23, 2013 by <a href="#">Jacob</a>
</p>
248
Chapter 7 Blog App Part 2
</div>
<! - /.blog-post ->
<div class="blog-post">
<h2 class="blog-post-title">New feature</h2>
<p class="blog-post-goal">
December 14, 2013 by <a href="#">Chris</a>
</p>
<nav class="blog-pagination">
<a class="btn btn-outline-primary" href="#">Older</a>
<a class="btn btn-outline-secondary disabled"
href="#" tabindex="-1" aria-disabled="true">Newer</a>
</nav>
</div>
<! - /.blog-main ->
249
Chapter 7 Blog App Part 2
<div class="p-4">
<h4 class="font-italic">Archives</h4>
<ol class="list-unstyled mb-0">
<li> <a href="#">March 2014</a> </li>
<li> <a href="#">February 2014</a> </li>
<li> <a href="#">January 2014</a> </li>
<li> <a href="#">December 2013</a> </li>
<li> <a href="#">November 2013</a> </li>
<li> <a href="#">October 2013</a> </li>
<li> <a href="#">September 2013</a> </li>
<li> <a href="#">August 2013</a> </li>
<li> <a href="#">July 2013</a> </li>
<li> <a href="#">June 2013</a> </li>
<li> <a href="#">May 2013</a> </li>
<li> <a href="#">April 2013</a> </li>
</ol>
</div>
<div class="p-4">
<h4 class="font-italic">Elsewhere</h4>
<ol class="list-unstyled">
<li> <a href="#">GitHub</a> </li>
<li> <a href="#">Twitter</a> </li>
<li> <a href="#">Facebook</a> </li>
</ol>
</div>
</aside>
<! - /.blog-sidebar ->
</div>
250
Chapter 7 Blog App Part 2
</main>
<! - /.container ->
<footer class="blog-footer">
<p>Blog template built for
<a href="https://getbootstrap.com/">Bootstrap</a> by
<a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>
<a href="#">Back to top</a>
</p>
</footer>
Again, it’s a very large portion of code, so it’s probably best that you use the following
file: https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/01-home.component-v1.html.
Now add the following content to home.component.css:
/* stylelint-disable selector-list-comma-newline-after */
.blog-header {
line-height: 1;
border-bottom: 1px solid #e5e5e5;
}
.blog-header-logo {
font-family: "Playfair Display", Georgia, "Times New Roman", serif;
font-size: 2.25rem;
}
.blog-header-logo: hover {
text-decoration: none;
}
h1,
h2,
h3,
h4,
251
Chapter 7 Blog App Part 2
h5,
h6 {
font-family: "Playfair Display", Georgia, "Times New Roman", serif;
}
.display-4 {
font-size: 2.5rem;
}
.nav-scroller {
position: relative;
z-index: 2;
height: 2.75rem;
overflow-y: hidden;
}
.nav-scroller .nav {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
padding-bottom: 1rem;
margin-top: -1px;
overflow-x: auto;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.nav-scroller .nav-link {
padding-top: .75rem;
padding-bottom: .75rem;
252
Chapter 7 Blog App Part 2
font-size: .875rem;
}
.card-img-right {
height: 100%;
border-radius: 0 3px 3px 0;
}
.flex-auto {
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.h-250 {
height: 250px;
}
/*
* Blog name and description
*/
.blog-title {
margin-bottom: 0;
font-size: 2rem;
font-weight: 400;
}
.blog-description {
font-size: 1.1rem;
color: #999;
}
253
Chapter 7 Blog App Part 2
/* Pagination */
.blog-pagination {
margin-bottom: 4rem;
}
.blog-pagination>.btn {
border-radius: 2rem;
}
/*
* Blog posts
*/
.blog-post {
margin-bottom: 4rem;
}
.blog-post-title {
margin-bottom: .25rem;
font-size: 2.5rem;
}
.blog-post-meta {
margin-bottom: 1.25rem;
color: #999;
}
/*
* Footer
*/
.blog-footer {
254
Chapter 7 Blog App Part 2
padding: 2.5rem 0;
color: #999;
text-align: center;
background-color: #f9f9f9;
border-top: .05rem solid #e5e5e5;
}
.blog-footer p: last-child {
margin-bottom: 0;
}
The page and the route are ready. But to be able to perform the redirection, we have
to add a new import in our login.component.ts:
And we will modify the constructor to create a private variable of type Router:
constructor(
private formBuilder: FormBuilder,
private loginService: LoginService,
private router: Router
) {}
Now we will rewrite the call to the login service within the onSubmit method:
255
Chapter 7 Blog App Part 2
If everything went well, when we log in we will see the screen shown in Figure 7-13.
At this point, we have fulfilled the goal of initially showing a login screen to the
user and then redirecting it to the home once they have entered their username and
password.
256
Chapter 7 Blog App Part 2
However, as you may have noticed, that is not the desired behavior. What we should
do is allow our users to see the home and require the login for any other functionality
other than viewing content, for example, create, edit, and delete posts.
In addition, at this time, the application has no memory that a user has logged in.
Fortunately, we have seen how to deal with these concepts when we work on the
chapter related to Auth0, so get down to work!
by
by
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private router: Router
) {}
this.loginService.login
by
this.authService.login
257
Chapter 7 Blog App Part 2
setCurrentUser(user: User) {
localStorage.setItem('currentUser', user.getUserName ());
}
isAuthenticated() {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
return true;
}
return false;
}
logout() {
localStorage.removeItem('currentUser');
}
Now let’s go to our login.component.ts and modify the call to the login method of the
service as follows:
258
Chapter 7 Blog App Part 2
this.authService.setCurrentUser(this.user);
After logging in, using the Application tab of the Chrome developer console, we can
see that the logged user’s data has been saved in our local storage (Figure 7-14).
Okay, but what happens if the user goes back to the home page? Then you will be
presented with the login screen again.
We are going to correct that. In the constructor of our login component, let’s add the
following verification:
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private router: Router
) {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
259
Chapter 7 Blog App Part 2
if (this.authService.isAuthenticated()) {
this.router.navigate(['/home']);
}
}
<button *ngIf="!this.authService.isAuthenticated()"
class="btn btn-sm btn-outline-secondary">Login</button>
<button *ngIf="this.authService.isAuthenticated()"
class="btn btn-sm btn-outline-secondary">Logout</button>
Buttons are displayed when appropriate, but still do not perform any function.
Let’s start with the Logout button.
<button *ngIf="this.authService.isAuthenticated()"
(click)="logout()" class="btn btn-sm btn-outline-secondary">
Logout</button>
Now let’s write the logout method of our home component. The purpose of this
function is very simple: call the authentication method of the AuthService, and redirect
to the login page.
For this, we need to first import the Router:
260
Chapter 7 Blog App Part 2
constructor(
public authService: AuthService,
public router: Router
) {}
logout() {
this.authService.logout();
this.router.navigate(['']);
}
If we now click the Logout button, we will be redirected to the login, at the same time
that the user will be removed from the local storage.
We can visit the home without having logged in, but now we will see the Login
button instead of Logout (Figure 7-15).
<button *ngIf="!this.authService.isAuthenticated()"
(click)="this.router.navigate([''])"
class="btn btn-sm btn-outline-secondary">Login</button>
This is simply to direct to the login page, which we have set in our app.routes.ts to
answer the route:
([''])
261
Chapter 7 Blog App Part 2
Source Code
LoginComponent
login.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/04-login.component.html
login.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/05-login.component.ts
login.component.css
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/06-login.component.css
HomeComponent
home.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/07-home.component-v3.html
home.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/08-home.component.ts
home.component.css
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/09-home.component-v2.css
AuthService
auth.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/10-auth.service.ts
Summary
In this chapter, we built our home component, which will be the central screen of our
application. In addition, we established the mechanisms to persist the user’s session.
In the next chapter, we will add the necessary logic to show the posts that are stored
in the database.
262
CHAPTER 7
NodeJS
NodeJS is an execution environment for JavaScript oriented to asynchronous events. It
allows us to build scalable network applications, which support multiple simultaneous
connections. It uses separate execution threads for this.
NodeJS undoubtedly deserves a book by itself, but we will see here enough to give
life to a simple but robust API.
To make things even easier, we will use Express.
Express
Express is a framework that works on the Node execution environment. It facilitates the
creation of applications, shortening the development time.
MongoDB
We already talked about MongoDB in the previous chapter. In this, we will connect to a
MongoDB database using Mongoose.
231
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_7
Chapter 7 Blog App Part 2
Mongoose
Mongoose is an Object Document Mapper written in JavaScript, which allows us to
define and manipulate in a simple way dynamic objects in a MongoDB database,
mapping those objects to documents.
These tools will allow us to build a robust and efficient API, which will serve as an
excellent backend for our application.
npm init
When executing this command, we will be asked a series of questions. We can simply
leave the default values by pressing enter on each of the options.
As a result of the process, we will obtain a file called package.json with the following
content:
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
232
Chapter 7 Blog App Part 2
At the end, we will have a new directory, node_modules, with all the framework
packages (Figure 7-1).
At the same level as the directory, we create a file called index.js with the following
content:
The code is simple. We are defining a path: /api/user/login, which will receive a GET
request, and that will simply return a text message.
At the same time, we are indicating that the requests will be heard on port 3000. We
could have used another port, as long as it was unoccupied.
Now, from the console, and always standing inside the backend directory, let’s run
the command:
node index.js
We’ll see a message indicating that the app is listening on port 3000 (Figure 7-2).
233
Chapter 7 Blog App Part 2
This package will allow you to extract parameters from the requests received by the
server. Then let’s run:
As we mentioned before, this package will allow you to easily interact with the
MongoDB database.
234
Chapter 7 Blog App Part 2
If we wish, we can save this connection as one of our favorites (Figure 7-5).
235
Chapter 7 Blog App Part 2
We will need to define the name of the database and the name of one collection.
It is tempting to assimilate the concept of collection to a table, but a collection is a set
of documents. Documents inside a collection can have a different number of fields
(Figure 7-7).
Go ahead and type blog as the database name and user as the collection, then click
Create Database.
236
Chapter 7 Blog App Part 2
Note that we have not had to specify any type of structure for the collection, but we
don’t need to. We can see the newly added database (Figure 7-8).
Click the name of the database; we’ll see the only collection (Figure 7-9).
Click the collection. Then select the option Insert Document from the ADD DATA
list (Figure 7-10).
237
Chapter 7 Blog App Part 2
We’ll be presented with a pop-up window. Replace the content with the following:
{
"name": "Administrator",
"username": "Admin",
"password": "1234"
}
Then click Insert. We can see the newly added document (Figure 7-11).
We have in this way a user against whom we can test the login, but we still have to
take some steps to connect the frontend with the backend.
238
Chapter 7 Blog App Part 2
Let’s go to the frontend directory and inside this to the src directory. Let’s add a file
called proxy.json with the following content:
{
"/api/*": {
"target": "http://localhost:3000",
"secure": "false"
}
}
Now, let’s open the angular.json file. In it, we will find the following section:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
},
"development": {
"browserTarget": "frontend:build:development"
}
},
"defaultConfiguration": "development"
},
Replace it with:
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
},
"development": {
"browserTarget": "frontend:build:development",
"proxyConfig": "src/proxy.json"
}
},
239
Chapter 7 Blog App Part 2
"defaultConfiguration": "development"
},
"proxyConfig": "src/proxy.json"
This step is necessary because we are hosting our client application and the server
within the same parent directory.
If we had already executed:
ng serve -o
We must stop the Angular server with Ctrl+C and restart it.
Let’s go back to the backend folder again and create a directory called models there.
Let’s add a file named user.js with the following content:
// create a schema
const userSchema = new Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true },
name: { type: String }
}, { collection: 'user' });
module.exports = User;
240
Chapter 7 Blog App Part 2
mongoose.set('strictQuery', true);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
})
});
});
241
Chapter 7 Blog App Part 2
We are requiring the body-parser module to obtain the parameters that are sent
in the requests to the server. Then we require mongoose because we will work with a
MongoDB database. We also define a constant that will reference the url of our database,
and finally we require our user model to interact with the collection of the same name.
We will not analyze the rest of the code in detail, but what it does is search the user
collection for a user whose username and password match the one received.
If we had previously started the server with:
node index.js
we will need to halt the execution and start the server again.
If now we try to log in, and assuming that we enter as user Admin and password
1234, we will see the output by console (Figure 7-12).
It is to be expected, since in our login component we are simply sending the result
received from the server to the console. What we should do is redirect the user to the
main page, which we do not have yet.
Home Component
Go ahead and create a component called home; we have already done this several times.
Let’s replace the contents of home.component.html with the following. It’s a lot of
content, so it’s probably better to use this file:
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/01-home.component-v1.html
<div class="container">
<header class="blog-header py-3">
242
Chapter 7 Blog App Part 2
243
Chapter 7 Blog App Part 2
244
Chapter 7 Blog App Part 2
245
Chapter 7 Blog App Part 2
<title>Placeholder</title>
<rect width="100%" height="100%" fill="# 55595c"> </rect>
<text x="50%" y="50%" fill="#eceeef" dy=".3em">Thumbnail</text>
</svg>
</div>
</div>
</div>
</div>
</div>
<div class="blog-post">
<h2 class="blog-post-title">Sample blog post</h2>
<p class="blog-post-goal">January 1, 2014 by <a
href="#">Mark</a> </p>
246
Chapter 7 Blog App Part 2
</blockquote>
<p>Etiam carries
<em>sem embarrassed magna</em> mollis euismod.
Cras mattis consectetur purus sit amet fermentum.
Aenean lacinia bibendum nulla sed consectetur.</p>
<h2>Heading</h2>
<p>Vivamus sagittis lacus vel augue
laoreet rutrum faucibus pain auctor. Duis mollis,
est non commodo luctus, nisi erat porttitor ligula,
eget lacinia hate sem nec elit. Morbi leo risus,
porta ac consectetur ac,
vestibulum at eros.</p>
<h3>Sub-heading</h3>
<p>Cum sociis natoque penatibus
et magnis dis parturient montes, nascetur ridiculus mus.</p>
<pre> <code>Example code block</code> </pre>
<p>Aenean lacinia bibendum nulla sed consectetur.
Etiam carries sem embarrassment magna mollis euismod.
Fusce dapibus, tellus ac cursus commodo, tortor
mauris condimentum nibh, ut fermentum massa.</p>
<h3>Sub-heading</h3>
<p>Cum sociis natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus. Aenean lacinia
bibendum nulla sed consectetur. Etiam carries sem embarrassment
magna mollis euismod. Fusce dapibus, tellus ac cursus commodo,
tortor mauris condimentum nibh, ut fermentum massa just sit
amet risus.</p>
<ul>
<li>Praesent commodo cursus magna, vel
scelerisque nisl consectetur et.</li>
<li>Donec id elit non my gravida at eget metus.</li>
<li>Nulla vitae elit libero, a pharetra augue.</li>
</ul>
<p>Donec ullamcorper nulla non metus auctor fringilla.
Nulla vitae elit libero, to pharetra augue.</p>
247
Chapter 7 Blog App Part 2
<ol>
<li>Vestibulum id ligula porta felis euismod
semper.</li>
<li>Cum sociis natoque penatibus et magnis dis
parturient montes, nascetur ridiculus mus.</li>
<li>Maecenas sed diam eget risus varius blandit
sit amet non magna.</li>
</ol>
<p>Cras mattis consectetur purus sit amet fermentum.
Sed posuere consectetur est at lobortis.</p>
</div>
<! - /.blog-post ->
<div class="blog-post">
<h2 class="blog-post-title">Another blog post</h2>
<p class="blog-post-goal">
December 23, 2013 by <a href="#">Jacob</a>
</p>
248
Chapter 7 Blog App Part 2
</div>
<! - /.blog-post ->
<div class="blog-post">
<h2 class="blog-post-title">New feature</h2>
<p class="blog-post-goal">
December 14, 2013 by <a href="#">Chris</a>
</p>
<nav class="blog-pagination">
<a class="btn btn-outline-primary" href="#">Older</a>
<a class="btn btn-outline-secondary disabled"
href="#" tabindex="-1" aria-disabled="true">Newer</a>
</nav>
</div>
<! - /.blog-main ->
249
Chapter 7 Blog App Part 2
<div class="p-4">
<h4 class="font-italic">Archives</h4>
<ol class="list-unstyled mb-0">
<li> <a href="#">March 2014</a> </li>
<li> <a href="#">February 2014</a> </li>
<li> <a href="#">January 2014</a> </li>
<li> <a href="#">December 2013</a> </li>
<li> <a href="#">November 2013</a> </li>
<li> <a href="#">October 2013</a> </li>
<li> <a href="#">September 2013</a> </li>
<li> <a href="#">August 2013</a> </li>
<li> <a href="#">July 2013</a> </li>
<li> <a href="#">June 2013</a> </li>
<li> <a href="#">May 2013</a> </li>
<li> <a href="#">April 2013</a> </li>
</ol>
</div>
<div class="p-4">
<h4 class="font-italic">Elsewhere</h4>
<ol class="list-unstyled">
<li> <a href="#">GitHub</a> </li>
<li> <a href="#">Twitter</a> </li>
<li> <a href="#">Facebook</a> </li>
</ol>
</div>
</aside>
<! - /.blog-sidebar ->
</div>
250
Chapter 7 Blog App Part 2
</main>
<! - /.container ->
<footer class="blog-footer">
<p>Blog template built for
<a href="https://getbootstrap.com/">Bootstrap</a> by
<a href="https://twitter.com/mdo">@mdo</a>.</p>
<p>
<a href="#">Back to top</a>
</p>
</footer>
Again, it’s a very large portion of code, so it’s probably best that you use the following
file: https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/01-home.component-v1.html.
Now add the following content to home.component.css:
/* stylelint-disable selector-list-comma-newline-after */
.blog-header {
line-height: 1;
border-bottom: 1px solid #e5e5e5;
}
.blog-header-logo {
font-family: "Playfair Display", Georgia, "Times New Roman", serif;
font-size: 2.25rem;
}
.blog-header-logo: hover {
text-decoration: none;
}
h1,
h2,
h3,
h4,
251
Chapter 7 Blog App Part 2
h5,
h6 {
font-family: "Playfair Display", Georgia, "Times New Roman", serif;
}
.display-4 {
font-size: 2.5rem;
}
.nav-scroller {
position: relative;
z-index: 2;
height: 2.75rem;
overflow-y: hidden;
}
.nav-scroller .nav {
display: -ms-flexbox;
display: flex;
-ms-flex-wrap: nowrap;
flex-wrap: nowrap;
padding-bottom: 1rem;
margin-top: -1px;
overflow-x: auto;
text-align: center;
white-space: nowrap;
-webkit-overflow-scrolling: touch;
}
.nav-scroller .nav-link {
padding-top: .75rem;
padding-bottom: .75rem;
252
Chapter 7 Blog App Part 2
font-size: .875rem;
}
.card-img-right {
height: 100%;
border-radius: 0 3px 3px 0;
}
.flex-auto {
-ms-flex: 0 0 auto;
flex: 0 0 auto;
}
.h-250 {
height: 250px;
}
/*
* Blog name and description
*/
.blog-title {
margin-bottom: 0;
font-size: 2rem;
font-weight: 400;
}
.blog-description {
font-size: 1.1rem;
color: #999;
}
253
Chapter 7 Blog App Part 2
/* Pagination */
.blog-pagination {
margin-bottom: 4rem;
}
.blog-pagination>.btn {
border-radius: 2rem;
}
/*
* Blog posts
*/
.blog-post {
margin-bottom: 4rem;
}
.blog-post-title {
margin-bottom: .25rem;
font-size: 2.5rem;
}
.blog-post-meta {
margin-bottom: 1.25rem;
color: #999;
}
/*
* Footer
*/
.blog-footer {
254
Chapter 7 Blog App Part 2
padding: 2.5rem 0;
color: #999;
text-align: center;
background-color: #f9f9f9;
border-top: .05rem solid #e5e5e5;
}
.blog-footer p: last-child {
margin-bottom: 0;
}
The page and the route are ready. But to be able to perform the redirection, we have
to add a new import in our login.component.ts:
And we will modify the constructor to create a private variable of type Router:
constructor(
private formBuilder: FormBuilder,
private loginService: LoginService,
private router: Router
) {}
Now we will rewrite the call to the login service within the onSubmit method:
255
Chapter 7 Blog App Part 2
If everything went well, when we log in we will see the screen shown in Figure 7-13.
At this point, we have fulfilled the goal of initially showing a login screen to the
user and then redirecting it to the home once they have entered their username and
password.
256
Chapter 7 Blog App Part 2
However, as you may have noticed, that is not the desired behavior. What we should
do is allow our users to see the home and require the login for any other functionality
other than viewing content, for example, create, edit, and delete posts.
In addition, at this time, the application has no memory that a user has logged in.
Fortunately, we have seen how to deal with these concepts when we work on the
chapter related to Auth0, so get down to work!
by
by
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private router: Router
) {}
this.loginService.login
by
this.authService.login
257
Chapter 7 Blog App Part 2
setCurrentUser(user: User) {
localStorage.setItem('currentUser', user.getUserName ());
}
isAuthenticated() {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
return true;
}
return false;
}
logout() {
localStorage.removeItem('currentUser');
}
Now let’s go to our login.component.ts and modify the call to the login method of the
service as follows:
258
Chapter 7 Blog App Part 2
this.authService.setCurrentUser(this.user);
After logging in, using the Application tab of the Chrome developer console, we can
see that the logged user’s data has been saved in our local storage (Figure 7-14).
Okay, but what happens if the user goes back to the home page? Then you will be
presented with the login screen again.
We are going to correct that. In the constructor of our login component, let’s add the
following verification:
constructor(
private formBuilder: FormBuilder,
private authService: AuthService,
private router: Router
) {
this.loginForm = this.formBuilder.group({
username: ['', Validators.required],
password: ['', Validators.required]
});
259
Chapter 7 Blog App Part 2
if (this.authService.isAuthenticated()) {
this.router.navigate(['/home']);
}
}
<button *ngIf="!this.authService.isAuthenticated()"
class="btn btn-sm btn-outline-secondary">Login</button>
<button *ngIf="this.authService.isAuthenticated()"
class="btn btn-sm btn-outline-secondary">Logout</button>
Buttons are displayed when appropriate, but still do not perform any function.
Let’s start with the Logout button.
<button *ngIf="this.authService.isAuthenticated()"
(click)="logout()" class="btn btn-sm btn-outline-secondary">
Logout</button>
Now let’s write the logout method of our home component. The purpose of this
function is very simple: call the authentication method of the AuthService, and redirect
to the login page.
For this, we need to first import the Router:
260
Chapter 7 Blog App Part 2
constructor(
public authService: AuthService,
public router: Router
) {}
logout() {
this.authService.logout();
this.router.navigate(['']);
}
If we now click the Logout button, we will be redirected to the login, at the same time
that the user will be removed from the local storage.
We can visit the home without having logged in, but now we will see the Login
button instead of Logout (Figure 7-15).
<button *ngIf="!this.authService.isAuthenticated()"
(click)="this.router.navigate([''])"
class="btn btn-sm btn-outline-secondary">Login</button>
This is simply to direct to the login page, which we have set in our app.routes.ts to
answer the route:
([''])
261
Chapter 7 Blog App Part 2
Source Code
LoginComponent
login.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/04-login.component.html
login.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/05-login.component.ts
login.component.css
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/06-login.component.css
HomeComponent
home.component.html
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/07-home.component-v3.html
home.component.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/08-home.component.ts
home.component.css
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/09-home.component-v2.css
AuthService
auth.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-07/10-auth.service.ts
Summary
In this chapter, we built our home component, which will be the central screen of our
application. In addition, we established the mechanisms to persist the user’s session.
In the next chapter, we will add the necessary logic to show the posts that are stored
in the database.
262
CHAPTER 8
getUserName() {
return this.username;
}
getPassword() {
return this.password;
}
}
263
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_8
Chapter 8 Blog App Part 3: Showing Posts
In this class, we have two fields, username and password. However, in order to
establish a relationship between users and the posts they publish, our user needs to have
an id that uniquely identifies them.
When an object is inserted in a collection, MongoDB is responsible for assigning an
id, as we can see after a user has logged in (Figure 8-1).
We can see that the ids are shown as hexadecimal text strings. Strictly speaking, in
the context of MongoDB, an _id is of type ObjectId, but we do not need to delve into too
many details.
With this in mind, let’s modify our user class in the following way:
getUserName() {
return this.username;
}
getPassword() {
return this.password;
}
264
Chapter 8 Blog App Part 3: Showing Posts
setId(id: string ) {
this.id = id;
}
getId() {
return this.id;
}
}
this.user.setId(result['data'][0]._id);
setCurrentUser(user: User) {
const loggedInUser = {
id: user.getId(),
username: user.getUserName()
265
Chapter 8 Blog App Part 3: Showing Posts
};
localStorage.setItem('currentUser', JSON.stringify(loggedInUser));
}
isAuthenticated() {
const currentUser =
JSON.parse(localStorage.getItem('currentUser') as string);
if (currentUser) {
return true;
}
return false;
}
We use the JSON.stringify method to convert the loggedInUser object into a JSON
text string.
Conversely, the JSON parse method allows you to convert the JSON text string into
an object.
Post Component
From the command line, create a new component called post:
ng g c components/post
This component will represent a single post of our application. Let’s start with the
template. The code will be extracted from home.component.html. Modify the post.
component.html code:
266
Chapter 8 Blog App Part 3: Showing Posts
267
Chapter 8 Blog App Part 3: Showing Posts
<div class="blog-post">
<app-post> </app-post>
</div>
{
"title": "Sample Post",
268
Chapter 8 Blog App Part 3: Showing Posts
The author_id field is of the ObjectId type since it will contain references to a user
in the **user** collection. The text string that is passed as a parameter must therefore
correspond to the id of a document in the user collection.
At this moment, we have a single user identified as **admin**, and the corresponding
id is the one we keep in the local storage after the user has logged in (Figure 8-2).
269
Chapter 8 Blog App Part 3: Showing Posts
Now we are going to define a Post model. Let’s create a file called post.js with the
following code:
// create a schema
270
Chapter 8 Blog App Part 3: Showing Posts
module.exports = Post;
The line
is what determines that there is a relationship between the “Post” scheme and the “User”
scheme. This allows retrieving data from related documents when a query is made.
Our next step will be to add the endpoint to recover the posts in our index.js:
For this to work, we need to import the model. Let’s add the line:
after
271
Chapter 8 Blog App Part 3: Showing Posts
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
})
});
});
272
Chapter 8 Blog App Part 3: Showing Posts
Important Remember, in order for the new endpoint to work, you need to stop
the server and restart it.
That’s all from the side of our backend. Now we can return to our frontend and work
on a new service to recover the posts.
Post Service
Create a new service called post:
ng g s services/post
273
Chapter 8 Blog App Part 3: Showing Posts
@Injectable({
providedIn: 'root'
})
export class PostService {
getAllPost() {
return this.http.post('/api/post/getAllPost', {});
}
}
This service will be used by our home component, since that is where we will show
the posts.
First of all, we need to import the service:
Then, let’s define a property to contain the posts received from the API:
constructor(
private authService: AuthService,
private router: Router,
private postService: PostService
) {}
getPosts() {
this.postService.getAllPost().subscribe({
next: (result: any) => {
this.posts = result['data'];
console.log( this.posts );
}
});
}
274
Chapter 8 Blog App Part 3: Showing Posts
ngOnInit() {
this.getPosts();
}
Post Component
It is time to work on our Post Component.
Modify post.component.ts as follows:
@Component({
selector: 'app-post',
templateUrl: './post.component.html',
styleUrls: ['./post.component.css']
})
export class PostComponent implements OnInit {
@Input() post: any = {};
constructor() {}
ngOnInit() {
}
We have already seen the use of @Input to receive data from a parent component.
Now let’s modify the post.component.html file as follows:
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-goal">
January 1, 2014 by <a href="#">Mark</a>
</p>
275
Chapter 8 Blog App Part 3: Showing Posts
There is nothing new there, a loop to display posts, where each of them is passed as a
parameter to the child component.
The output will be like Figure 8-4.
276
Chapter 8 Blog App Part 3: Showing Posts
It doesn’t look bad, but one problem is that we are showing the full text of the post.
Let’s remedy that:
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-goal">January 1, 2014 by <a href="#">Mark</a> </p>
We are using the pipe **slice** to limit the size of the text displayed to 300 characters.
Now, the home will look like Figure 8-5.
277
Chapter 8 Blog App Part 3: Showing Posts
Much better. Of course, we could use a custom pipe so that the cut always occurs at
the end of a complete word, but for now it is enough.
Let’s give it a final touch including a link to read the full post:
<h2 class="blog-post-title">{{post.title}}</h2>
<p class="blog-post-goal">January 1, 2014 by <a href="#">Mark</a> </p>
278
Chapter 8 Blog App Part 3: Showing Posts
We have many things to improve, but for the moment, we have fulfilled our goal.
As always, to ensure that everything works correctly, I will provide the gists of each
component and service.
279
Chapter 8 Blog App Part 3: Showing Posts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/06-index-v2.js
280
Chapter 8 Blog App Part 3: Showing Posts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/15-user.model.ts
Auth Service
auth.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/16-auth.service.ts
Post Service
post.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/17-post.service.ts
AppModule
app.module.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/18-app.module.ts
Routes
app.routes.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-08/19-app.routes.ts
Summary
In this chapter, we have seen how to recover the posts stored in a collection of our
database.
We modified the home to present the posts thanks to a new component. If we
analyze our application until now, it presents a series of problems that many of you
will surely have noticed. One of them, and not unimportant, is that of security. User
passwords are saved as plain text in the user collection. This is not only bad practice, but
inadmissible. In later chapters, we will solve this and other issues, but for now we will
continue advancing until the application performs all CRUD operations on the posts.
281
CHAPTER 9
Dashboard Component
Let’s create a new component called dashboard:
ng g c components/dashboard
<div class="container-fluid">
<div class="row">
<nav class="col-md-2 d-none d-md-block bg-light sidebar">
<div class="sidebar-sticky">
283
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_9
Chapter 9 Blog App Part 4: Filtering Posts
284
Chapter 9 Blog App Part 4: Filtering Posts
</button>
</div>
</div>
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
posts: any[] = [];
ngOnInit() {
this.getPosts();
}
getPosts() {
this.postService.getAllPost().subscribe({
next: (result: any) => {
this.posts = result['data'];
285
Chapter 9 Blog App Part 4: Filtering Posts
console.log( this.posts );
}
});
}
The route is visible even when we have not logged in, and this is something that we
must change.
286
Chapter 9 Blog App Part 4: Filtering Posts
In the chapter where we worked with Auth0, we saw the concept of a route guardian,
and that is the resource we will use to protect this route from unauthorized access.
Let’s execute the following command:
ng g g services/auth
If you see a prompt, just leave CanActivate selected. This will create a file called auth.
guard.ts, inside the services directory. Let’s replace the contents of the file as follows:
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor( private auth: AuthService ) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> |
boolean {
if ( this.auth.isAuthenticated() ) {
return true;
}
return false;
}
}
287
Chapter 9 Blog App Part 4: Filtering Posts
And just to be sure, here is the code of the AuthService that is imported in the guard:
@Injectable({
providedIn: 'root'
})
export class AuthService {
login(user: User) {
return this.http.post('/api/user/login', {
username : user.getUserName(),
password : user.getPassword()
});
}
logout() {
localStorage.removeItem('currentUser');
}
setCurrentUser(user: User) {
const loggedInUser = {
id: user.getId(),
username: user.getUserName()
};
localStorage.setItem('currentUser', JSON.stringify(loggedInUser));
}
isAuthenticated() {
const currentUser = JSON.parse(localStorage.getItem('currentUser') as
string);
if (currentUser) {
return true;
}
288
Chapter 9 Blog App Part 4: Filtering Posts
return false;
}
}
Remember that the purpose of a guard is to protect the routes of our application, so
only authorized people can visit them. Think about routes for administering users, for
example, you wouldn’t want every person being able to change other users’ data.
The canActivate method returns true or false to indicate whether or not you have
access to a route. We are in charge of specifying the conditions that will cause a true
value to be returned and therefore authorize access to the route. In this case, it is
sufficient that the user has been identified.
In order for the guard to take effect for our route, we must change the definition of
the route in app.routes.ts in the following way:
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
}
If we now try to access the dashboard route, we will find a blank page.
Instead of returning false if the user is not authenticated, we can redirect it to the
login page. Let’s do that:
289
Chapter 9 Blog App Part 4: Filtering Posts
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
private auth: AuthService,
private router: Router
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean | UrlTree {
if ( this.auth.isAuthenticated() ) {
return true;
}
return this.router.parseUrl('');
}
}
Now before the attempt to access the route without having logged in, we will be
redirected to the login page.
Post Component
The posts are showing on our dashboard, but we need to make some modifications.
For example, the “Continue reading” link does not make sense on our post
management page.
In our Post class, let’s add the following property:
290
Chapter 9 Blog App Part 4: Filtering Posts
In this way, the link will have disappeared in the dashboard (Figure 9-2).
We will now make other modifications, such as showing buttons to edit and delete in
each post of the dashboard.
Let’s start by installing Font Awesome. From a console, let’s execute:
291
Chapter 9 Blog App Part 4: Filtering Posts
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"node_modules/@fortawesome/fontawesome-free/css/all.css"
]
For the changes to take effect, we must stop the angular-cli server by pressing Ctrl+C
and then restart it.
Let’s make the following changes to post.component.html:
<div class="text-right">
<i title="Edit" class="fas fa-edit" aria-hidden="true"
style="cursor: pointer"></i>
<i title="Delete" class="fas fa-trash-alt" aria-hidden="true"
style="cursor: pointer"></i>
</div>
<h2 class="blog-post-title">{{ post.title }}</h2>
<p class="blog-post-meta">January 1, 2014 by <a href="#">Mark</a></p>
292
Chapter 9 Blog App Part 4: Filtering Posts
However, if we now go to home, the buttons to edit and delete posts will be visible.
We are going to solve it by making use of a new property:
And now we can control the visibility of the div that contains the buttons as follows:
293
Chapter 9 Blog App Part 4: Filtering Posts
to
Filtering Posts
Something that surely you will have noticed is that we are showing all the posts in the
dashboard indiscriminately, when what should happen is that only the posts created by
the logged-in user are displayed. We are going to solve it.
Let’s start by adding a new endpoint in the backend. In index.js, add:
So in order for the changes made to index.js to be registered, we will have to stop the
server and restart it again with **node index.js**.
The code is very similar to the getAllPost method. But we will worry about
refactoring later.
294
Chapter 9 Blog App Part 4: Filtering Posts
Now, let’s go back to the frontend and add the following method in post.service.ts:
getPostsByAuthor() {
const currentUser = JSON.parse(
localStorage.getItem('currentUser') as string
);
return this.http.post('/api/post/getPostsByAuthor',
{ author_id: currentUser.id });
}
getPosts() {
this.postService.getPostsByAuthor().subscribe({
next: (result: any) => {
this.posts = result['data'];
console.log( this.posts );
}
});
}
We will not see any changes in the frontend, but that’s fine, since we do not have any
other registered users.
We can use MongoDB Compass to quickly enter a new user (Figures 9-4 and 9-5).
295
Chapter 9 Blog App Part 4: Filtering Posts
Now, we can see the post collection and use the clone function to insert a new
document, replacing the id of the author with the id of the new user (Figures 9-6
and 9-7).
296
Chapter 9 Blog App Part 4: Filtering Posts
If we return to the home, close the session, and reenter with the data of the new user
and we go again to the dashboard, we will see only one post.
Sharpening Details
We are going to make some small changes. First of all, we will replace the link **Sign
Out** of the dashboard with a link that shows the text **Logout** that allows to effectively
close the session.
It’s something we’ve seen before, so we’ll do it quickly. First, let’s replace the line:
by
297
Chapter 9 Blog App Part 4: Filtering Posts
logout() {
this.auth.logout();
this.router.navigate(['']);
}
298
Chapter 9 Blog App Part 4: Filtering Posts
Summary
In this chapter, we modified the logic of the dashboard to show only the posts published
by the user who has logged in. We also made changes to improve the interface. We are
closer to having our application finished. In the next chapter, we will add the necessary
functionality to register posts.
299
CHAPTER 10
AddPost Component
Generate a new component:
ng g c components/addPost
@Component({
selector: 'app-add-post',
templateUrl: './add-post.component.html',
styleUrls: ['./add-post.component.css']
})
export class AddPostComponent implements OnInit {
postForm: FormGroup;
submitted = false;
301
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_10
Chapter 10 Blog App Part 5: Adding Posts
ngOnInit () {}
onSubmit() {
this.submitted = true;
This should already be familiar to us given our experience with the login form.
We import the modules needed to build the form:
postForm: FormGroup;
submitted = false;
postForm will reference our group of form controls. Again, for such a simple form, we
will only have one group.
**submitted** will be our flag to indicate when the form has been sent.
Then we have the constructor:
302
Chapter 10 Blog App Part 5: Adding Posts
We declare a private property that will reference our form and initialize it. We declare
two controls, title and text, which will be mandatory.
For the moment, our onSubmit method does nothing but return if the form is invalid.
Here is the link to the file:
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-10/01-add-post.component.ts
Now let’s replace the content of add-post.component.html as follows:
303
Chapter 10 Blog App Part 5: Adding Posts
<div class="form-group">
<label for="text" class="sr-only">Text</label>
<textarea formControlName="text"
class="form-control" placeholder="Text"
[ngClass]="{'is-invalid': submitted && f['text'].errors}">
</textarea>
<div *ngIf="submitted && f['text'].errors"
class="invalid-feedback">
<div *ngIf="f['text'].errors['required']">
Text is required</div>
</div>
</div>
</div>
</div>
</div>
</div>
304
Chapter 10 Blog App Part 5: Adding Posts
<button type="button"
class="btn btn-sm btn-outline-secondary"
data-toggle="modal"
data-target="#exampleModal">Add Post</button>
305
Chapter 10 Blog App Part 5: Adding Posts
And if we try to send the form without completing the fields, we will get errors
(Figure 10-2).
With the frontend acceptably finished, let’s concentrate now on our API, where we
will include the endpoint in charge of inserting the posts in the database.
At this point, I will make a recommendation.
As you may have noticed, every time we make changes to our API, that is, we modify
our index.js file from the backend, we are forced to stop the server with Ctrl+C and
restart it with node index.js so that the changes are visible.
306
Chapter 10 Blog App Part 5: Adding Posts
This can be tedious, but fortunately we have a way of avoiding it. We can install a
utility called nodemon (https://nodemon.io/), which will monitor the changes made
to our source code and automatically restart the server. In this way, we need only to
start the server once, and then we can work on our code and see the changes reflected
automatically. It is not mandatory to install this tool, but is very convenient.
To install it, we can simply enter the following from a terminal:
The -g flag indicates that we want to install the tool globally. Now, from our backend
directory, it is only a matter of starting the server using the nodemon command instead
of the node command (Figure 10-3).
## createPost method
307
Chapter 10 Blog App Part 5: Adding Posts
Post Model
In our models directory, add a post.model.ts file with the following content:
getTitle() {
return this.title;
}
getText() {
return this.text;
}
}
308
Chapter 10 Blog App Part 5: Adding Posts
ng g s services/addPost
This will give us an add-post.service.ts file. Let’s replace the content with the
following:
@Injectable({
providedIn: 'root'
})
export class AddPostService {
addPost(post: Post) {
const user = JSON.parse(localStorage.getItem('currentUser') as string);
return this.http.post('/api/post/createPost', {
title : post.getTitle(),
text : post.getText(),
author_id: user.id
});
}
}
Our next step will be to modify the add-post.component.ts file to make use of the
service.
We add the necessary import:
309
Chapter 10 Blog App Part 5: Adding Posts
constructor(
private formBuilder: FormBuilder,
private addPostService: AddPostService
) {}
Now we can modify the onSubmit method to make use of the service:
onSubmit() {
this.submitted = true;
310
Chapter 10 Blog App Part 5: Adding Posts
@Component({
selector: 'app-add-post',
templateUrl: './add-post.component.html',
styleUrls: ['./add-post.component.css']
})
export class AddPostComponent implements OnInit {
postForm: FormGroup;
submitted = false;
post: Post = new Post('', '');
constructor(
private formBuilder: FormBuilder,
private addPostService: AddPostService
) {
this.postForm = this.formBuilder.group({
title: ['', Validators.required],
text: ['', Validators.required]
});
}
ngOnInit() {}
onSubmit() {
this.submitted = true;
311
Chapter 10 Blog App Part 5: Adding Posts
With these changes applied, we can try adding a new post (Figure 10-4).
We can use MongoDB Compass to see that a new document has been inserted as
shown in Figure 10-5 (it may be necessary to refresh the interface).
312
Chapter 10 Blog App Part 5: Adding Posts
However, we can notice that the modal window is still in place. If we close and
refresh the page, we will see the new post (Figure 10-6).
Logically, leaving the modal window open is something we should avoid. To do this,
we will use the viewChild decorator, which allows you to use a property to refer to an
element of a component.
In add-post.component.html, we have the definition of the following button:
313
Chapter 10 Blog App Part 5: Adding Posts
This is the button used to close the modal window, the x in the upper-right corner.
We can refer to that button by using the viewChild property. In add-post.component.
ts, let’s modify the first import as follows:
onSubmit() {
this.submitted = true;
314
Chapter 10 Blog App Part 5: Adding Posts
Now, the modal window will be closed after the new post has been added. This is not
the only use of viewChild. It can also be used to refer to a child component from a parent
component, in order to be able to access the child’s properties or methods.
We have solved a problem, but we still have the issue of updating the dashboard
once a new post has been added. For this, we will use the power of the Observables.
Observables
Observables provide a form of message-based communication between a sender and
one or more subscribers.
In response to an event, the sender sends a notification that is received by all
subscribers. Application examples would be to detect when a user has registered
and communicate the subscription to a component responsible for sending an email
message.
First, we add a new service:
ng g s services/common
This generates a new common.service.ts file. Let’s replace the content with the
following:
@Injectable({
providedIn: 'root'
})
export class CommonService {
constructor() {
}
notifyPostAddition(msg: string) {
this.postAdded_Observable.next(msg);
}
}
315
Chapter 10 Blog App Part 5: Adding Posts
Now let’s modify our add-post.component.ts again. Add an import of the new
service:
builder(
private formBuilder: FormBuilder,
private addPostService: AddPostService,
private commonService: CommonService
) {}
onSubmit() {
this.submitted = true;
316
Chapter 10 Blog App Part 5: Adding Posts
The notification is issued, but our Dashboard Component must react to it.
Add the following import to dashboard.component.ts:
constructor(
private postService: PostService,
private auth: AuthService,
private router: Router,
private commonService: CommonService
)
ngOnInit() {
this.getPosts();
this.commonService.postAdded_Observable.subscribe((res) => {
this.getPosts();
});
}
With these modifications, when adding a new post, we will see how the dashboard
screen shows the new post added immediately.
As always, we’re going to end the chapter with the gists that contain the source code
to make sure everything works correctly.
317
Chapter 10 Blog App Part 5: Adding Posts
Summary
We have thus completed the necessary functionality to add posts. Of course, there are
things that could definitely be improved. For example, at this time our posts do not
support more than text format. But we are on the way to completing the basic CRUD
operations. There will be time to make the necessary improvements.
In the next chapter, we will focus on editing posts.
318
CHAPTER 11
setTitle(title: string) {
this.title = title;
}
setText(text: string) {
this.text = text;
}
setId(id: string) {
this.id = id;
}
There are three setters, and just to be sure, here is the complete code of the model:
319
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_11
Chapter 11 Blog App Part 6: Editing Posts
getTitle() {
return this.title;
}
getText() {
return this.text;
}
getId() {
return this.id;
}
setTitle(title: string) {
this.title = title;
}
setText(text: string) {
this.text = text;
}
setId(id: string) {
this.id = id;
}
Common Service
Let’s add two new properties:
320
Chapter 11 Blog App Part 6: Editing Posts
First, add a new observable that we will use to notify when one has been selected to
be edited. Then, we declare a variable that will serve to contain said post.
Of course, we need the import statement:
notifyPostEdit(msg: string) {
this.postToEdit_Observable.next(msg);
}
setPostToEdit(post: any) {
this.postToEdit = new Post(post.title, post.text);
this.postToEdit.setId(post._id);
this.notifyPostEdit('');
}
The first method will issue the notification to which all consumers of the observable
can subscribe.
In the second method, we first create a new post based on the user’s selection.
Remember that in the Post Component the posts that are displayed are not post objects.
They are simply common objects, but they do not fit the interface of our Post model.
That is why we created a new instance of the Post model from the title and text of the
post-selected generic object. Then, we use our new setter to assign to the new post the id
of the post that the user wishes to edit. Finally, the notification is issued.
Let’s clarify this point a little more.
Both in the home and in the dashboard, the posts that are received from the backend
have the following format:
{
_id: " 5d05211736b99b35d4f9d458 ",
title: " Angular ",
text: " It's great ",
author_id: " 5ce9c7811b5fc391a55cbcfb "
}
321
Chapter 11 Blog App Part 6: Editing Posts
And it is that generic object that when selected for editing is used to instantiate an
object of type Post with the following structure:
{
id: "5d05211736b99b35d4f9d458",
author_id: "",
title: "Angular ",
text:" It's great "
}
Some of you may point out that this is cumbersome, and they are right. It would be
much better to have some way of transforming the objects received from the backend to
Post-type objects according to our frontend.
Fortunately, such a way exists, and we can implement it in the service that makes the
requests. But we will leave this optimization for later.
Post Component
In post.component.html, we will modify the line where the edit icon is shown to respond
to the mouse-click event:
(click)="setPostToEdit(post)"
setPostToEdit(post) {
this.commonService.setPostToEdit(post);
}
We simply call the Common Service method. As you should know, before we can use
it we must add the necessary import:
322
Chapter 11 Blog App Part 6: Editing Posts
With these changes, when the user selects a post for editing, the notification will be
issued, but we are not yet reacting to that notification. We are going to change that.
Dashboard Component
First, let’s modify the dashboard.component.html to assign an id to the **Add Post** button:
to
builder(
private postService: PostService,
private auth: AuthService,
private router: Router,
private commonService: CommonService
) {
this.commonService.postToEdit_Observable.subscribe(res => {
this.addBtn.nativeElement.click();
});
}
323
Chapter 11 Blog App Part 6: Editing Posts
In the constructor, we are subscribing to the notification that will be sent when a post
is selected to be edited. In response to that event, the click event of the Add Post button
of the child component AddPost will be invoked.
Finally, we must modify the AddPost Component so that the data of the selected post
is shown in the modal screen.
First, add the constructor:
constructor(
private formBuilder: FormBuilder,
private addPostService: AddPostService,
private commonService: CommonService
) {
this.postForm = this.formBuilder.group({
title: ['', Validators.required],
text: ['', Validators.required]
});
this.commonService.postToEdit_Observable.subscribe(res => {
this.post = this.commonService.postToEdit;
this.postForm = this.FormBuilder.group({
title: [this.post.getTitle(), Validators.required],
text: [this.post.getText(), Validators.required]
});
});
}
this.commonService.postToEdit_Observable.subscribe(res => {
this.post = this.commonService.postToEdit;
this.postForm = this.FormBuilder.group({
title: [this.post.getTitle(), Validators.required],
text: [this.post.getText(), Validators.required]
});
});
324
Chapter 11 Blog App Part 6: Editing Posts
In the body of the method, we subscribe to the observable that notifies the selection
of a post for editing. When the notification is received, the post local variable stores the
post to be modified, which is provided by the service. Then, the control group of the form
is created, passing the title and the text of the post as values.
The next change is in the onSubmit method. Let’s replace the line:
by
this.post.setText(this.f['title'].value);
this.post.setText(this.f['text'].value);
We use the Post model setters to change the title and text according to the
form values.
At this point, if we select a post for editing, we will see that the form effectively shows
the values of the selected post.
However, we have a problem. If we select a post to edit, close the modal window, and
then click the Add Post button, we will see that in the form the values of the last selected
post are shown. Let’s deal with it.
The problem is that we have no way to differentiate when the modal window has
been called by clicking the Add Post button of dashboard.component.html and when it
has been called by clicking any of the edit buttons of the Post Component instances.
First, let’s modify the Common Service as follows. We add a new observable:
a new notification:
notifyPostToAdd(msg: string) {
this.postToAdd_Observable.next(msg);
}
setPostToAdd() {
this.postToEdit = new Post('', '');
this.postToEdit.setId('');
this.notifyPostToAdd();
}
325
Chapter 11 Blog App Part 6: Editing Posts
this.addBtn.nativeElement.click();
by
this.editBtn.nativeElement.click();
resetPost() {
this.commonService.setPostToAdd();
}
This method calls the setPostToAdd method, which, as we have seen, assigns a new
empty Post to the postToEdit variable, and issues the corresponding notification:
That resetPost method will be summoned by clicking the Add Post button. Modify it
as follows:
326
Chapter 11 Blog App Part 6: Editing Posts
Summing up what we have so far, when the user clicks the **Add Post** button of
the Dashboard Component, a Common Service method is invoked that generates a new
empty post and issues a notification.
Now, we must change the AddPost Component to respond to the notification.
AddPost Component
Let’s replace the constructor as follows:
constructor(
private formBuilder: FormBuilder,
private addPostService: AddPostService,
private commonService: CommonService
) {
this.postForm = this.formBuilder.group({
title: ['', Validators.required],
text: ['', Validators.required]
});
this.commonService.postToEdit_Observable.subscribe(res => {
this.post = this.commonService.postToEdit;
this.postForm = this.formBuilder.group({
title: [this.post.getTitle(), Validators.required],
text: [this.post.getText(), Validators.required]
});
});
this.commonService.postToAdd_Observable.subscribe(res => {
this.post = this.commonService.postToEdit;
this.postForm = this.formBuilder.group({
title: [this.post.getTitle(), Validators.required],
text: [this.post.getText(), Validators.required]
});
});
}
327
Chapter 11 Blog App Part 6: Editing Posts
As you may have noticed, we are subscribing to the new notification issued by the
new observable. However, the code that runs on both subscriptions is identical. Let’s
solve that.
Let’s add a new method:
setPostToEdit() {
this.post = this.commonService.postToEdit;
this.postForm = this.formBuilder.group({
title: [this.post.getTitle(), Validators.required],
text: [this.post.getText(), Validators.required]
});
}
constructor(
private formBuilder: FormBuilder,
private addPostService: AddPostService,
private commonService: CommonService
) {
this.commonService.postToEdit_Observable.subscribe(res => {
this.setPostToEdit();
});
this.commonService.postToAdd_Observable.subscribe(res => {
this.setPostToEdit();
});
}
If we now select a post to edit, close the window, and then click the **Add Post**
button, we will see that the fields appear blank, which is the expected behavior.
If now we try to add a new post, we can do it without problems, but if we try to edit a
specific post, the only thing we will get is a duplicate post.
We must modify in our onSubmit method the post submission. Immediately after
the lines:
this.post.setTitle(this.f.title.value);
this.post.setText(this.f.text.value);
328
Chapter 11 Blog App Part 6: Editing Posts
329
Chapter 11 Blog App Part 6: Editing Posts
onSubmit() {
this.submitted = true;
this.post.setText(this.f['title'].value);
this.post.setText(this.f['text'].value);
330
Chapter 11 Blog App Part 6: Editing Posts
However, you may have noticed some problems with our new version of the method.
For starters, let’s analyze the condition that is evaluated:
Probably our IDE warns us that the Post class does not have a getId method. Let’s
add it right now. In post.model.ts, add:
getId() {
return this.id;
}
The next problem is that our AddPost Service does not have an updatePost method.
Let’s add it.
AddPost Service
In add-post.service.ts, add:
updatePost(post: Post) {
console.log(post);
const user = JSON.parse(localStorage.getItem('currentUser') as string);
return this.http.post('/api/post/updatePost', {
id: post.getId(),
title : post.getTitle(),
text : post.getText(),
author_id: user.id
});
}
Magnificent. Everything is complete in the frontend. Now we must deal with adding
the endpoint in our API.
331
Chapter 11 Blog App Part 6: Editing Posts
updatePost
In index.js, let’s add:
With our endpoint running, we can edit posts (Figures 11-1 to 11-3).
332
Chapter 11 Blog App Part 6: Editing Posts
As always, we close the chapter with links to the code to make sure that everything
works correctly.
333
Chapter 11 Blog App Part 6: Editing Posts
334
Chapter 11 Blog App Part 6: Editing Posts
Summary
In this brief chapter, we added the functionality to edit posts, making use again of the
observables. The next chapter, where we will deal with the elimination, will be equally
brief. Once the basic functionality is complete, we will devote ourselves to polishing
details of our application to improve its efficiency and appearance.
335
CHAPTER 12
Common Service
We will start defining a new observable:
notifyPostDelete(msg: string) {
this.postToDelete_Observable.next(msg);
}
337
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_12
Chapter 12 Blog App Part 7: Deleting Posts
Finally, the method that receives the post to be deleted, operates on it, and sends the
notification:
setPostToDelete(post: any) {
this.postToDelete = new Post('', '');
this.postToDelete.setId(post._id);
this.notifyPostDelete('');
}
That’s it on the Common Service side. Nothing that we have not seen previously.
Dashboard Component
Let’s start by modifying the dashboard.component.html file. First, add a modal window
that will show a confirmation message when we click the delete icon that appears next to
the edit icon in each post.
Following the line:
<app-add-post> </app-add-post>
338
Chapter 12 Blog App Part 7: Deleting Posts
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-dismiss="modal">Cancel</button>
<button type="button" (click)="delete ()"
class="btn btn-primary">Delete</button>
</div>
</div>
</div>
</div>
Here is the link to the file with the code snippet: https://github.com/Apress/The-
Beginning-Angular-by-Victor-Hugo-Garcia/blob/main/Chapter-12/01-dashboard.
component.html.
For this window to be displayed, it is necessary to add a button that will not be
displayed but will serve to trigger the event that calls that window.
Next to the following line:
add:
Where will we assign to that variable the post that has been selected to be deleted?
339
Chapter 12 Blog App Part 7: Deleting Posts
When we respond to the notification that the Common Service will send, and to
which we must subscribe to the constructor, the method remains as follows:
builder(
private postService: PostService,
private auth: AuthService,
private router: Router,
private commonService: CommonService
) {
this.commonService.postToEdit_Observable.subscribe(res => {
this.editBtn.nativeElement.click();
});
this.commonService.postToDelete_Observable.subscribe(res => {
this.postToDelete = this.commonService.postToDelete;
this.deleteBtn.nativeElement.click();
});
}
Notice also that on our **Delete** button of the modal confirmation window, we are
defining the following:
(click)="delete()"
We will be listening to the click event in order to trigger an event **delete**, which for
now define as:
delete() {
console.log(this.postToDelete);
}
Post Component
The first change will be made in the post.component.html file. Replace the line showing
the deletion icon with the following:
340
Chapter 12 Blog App Part 7: Deleting Posts
(click)="setPostToDelete(post)"
Logically, the setPostToDelete method does not exist yet. Our next step is to create it
in post.component.ts:
setPostToDelete(post: any) {
this.commonService.setPostToDelete(post);
}
The method receives the selected post and passes it to a setPostToDelete method of
our Common Service, starting the cycle that we commented on at the beginning.
With these changes in position, when clicking the delete icon of a post, we should
see something like Figure 12-1.
When we click Delete, the console will show us something like Figure 12-2.
We have only kept the id of the post to be deleted, which is the only thing we need.
Let’s now write the method that will call the API to delete the post, sending as
parameter the id of the selected post.
341
Chapter 12 Blog App Part 7: Deleting Posts
deletePost(post: Post) {
return this.http.post('/api/post/deletePost', {
id: post.getId()
});
}
builder(
private postService: PostService,
private auth: AuthService,
private router: Router,
private commonService: CommonService,
private addPostService: AddPostService
)
delete() {
this.addPostService.deletePost( this.postToDelete ).subscribe(
(res) => {
this.getPosts();
});
}
With that, we have finished the frontend part; now it is our turn to take care of
our API.
342
Chapter 12 Blog App Part 7: Deleting Posts
index.js
In the index.js file, we create the following method:
And here’s the file with the corresponding code snippet: https://github.com/
Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/blob/main/Chapter-12/02-
index.js.
With these changes, the removal will work. Let’s try it, add a post and try to eliminate
it. It works, but there is a problem. The modal confirmation window continues to be
displayed. Some of you will have already figured out how to solve the problem. That’s
right, we’ll add an id to our **Cancel** button next to a reference in our Dashboard
Component to be able to summon your click method programmatically.
First, in dashboard.component.html we modified the definition of the button
**Cancel**:
343
Chapter 12 Blog App Part 7: Deleting Posts
delete() {
this.addPostService.deletePost( this.postToDelete ).subscribe(
(res) => {
this.getPosts();
this.cancelBtn.nativeElement.click();
});
}
We have completed our removal functionality. As always, I will include the code of all
the modified files.
344
Chapter 12 Blog App Part 7: Deleting Posts
AddPost Service
add-post.service.ts
https://github.com/Apress/The-Beginning-Angular-by-Victor-Hugo-Garcia/
blob/main/Chapter-12/08-add-post.component.ts
Summary
Throughout these last chapters, we have built a functional application that illustrates
concepts such as communication between components and use of services to
communicate with an API hosted on a server. Our blog performs the basic CRUD
operations on a type of entity, and in that sense, it is very simple, but at the same time
with it as a guide, we can build arbitrarily complex applications. There are details to be
polished, and we will take care of them as you add material to this book.
345
CHAPTER 13
Testing
There should be no doubt about how important it is to thoroughly test the applications
we develop. I would even say that the word is not “important,” but “essential.”
And here it is worth making an important clarification. That we test our application
does not imply that it will be free of defects. It is practically impossible, except in the
most trivial cases, to ship a product with no errors. But this does not mean that testing is
useless. On the contrary, testing allows us to
• Find many defects before they become errors (when they have
already been detected by the user)
But if testing is so important, why are we so reluctant to do so? One of the reasons is
that building and running tests is a tedious process.
Well, with Angular, this boredom is greatly reduced, which means that we have fewer
excuses to test our products.
In this chapter, we will see the tools that Angular offers developers to perform unit
tests, and we will build suites with specs to test classes, pipes, and services.
Jasmine
What makes it so simple to write tests with Angular and run them? On the
one hand, it is the fact that Angular comes from the beginning with Jasmine
(https://jasmine.github.io/).
Jasmine is a framework to test JavaScript code. It is not exclusive to Angular; on the
contrary, if we work on the backend using NodeJS, we can use it perfectly. It’s just that
Angular has integrated it so we can use it without extra settings. And to make everything
easier, Angular has also incorporated a tool called Karma.
347
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8_13
Chapter 13 Testing
Karma
Karma is a test runner developed by the Angular team, designed to automate tasks
related to the execution of the developer tests with Jasmine.
We will create a new project:
ng new angular-test
After executing the command, we will obtain the new angular-test directory where
our project is located. We know we can execute it simply by typing:
ng serve -o
on the command line. The flag -o is simply used to indicate that we want to use the
default browser of our operating system. We will see the familiar screen shown in
Figure 13-1.
348
Chapter 13 Testing
We have not yet modified a single line of code, and yet we can run the first tests of
our application.
Let’s stop project execution by pressing Ctrl+C. Always from our angular-test
directory, we execute:
ng test
This will open a new browser window with the output shown in Figure 13-2.
349
Chapter 13 Testing
350
Chapter 13 Testing
We can see there the references to Jasmine and Karma. This configuration, as we
said, comes by default, and we should not modify it unless we want to replace Karma
with some other test runner, for example, Jest (https://jestjs.io/), or we want to
replace Jasmine with another testing framework. However, for the vast majority of cases,
the original configuration will allow us to create and run all the tests we need in an
organized way.
We will also find in our project a karma.conf.js file (Figure 13-4).
351
Chapter 13 Testing
This is the file that we should modify to run another framework other than Jasmine
using Karma. Again, I don’t find too many reasons to do so.
Now we can get into what is the code of our application.
Inside the src directory, we have our app.component.ts file where the
AppComponent class declaration appears. Accompanying this file, we can see another
called app.component.spec.ts. Each time we create a new component, service, or
pipe using angular-cli, the corresponding spec file is created unless explicitly stated
otherwise. The file will have the following content:
352
Chapter 13 Testing
describe('AppComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
});
353
Chapter 13 Testing
describe is a function whose first parameter is a text string that indicates the
name under which the specs will be grouped. In the case of the specs we run for our
AppComponent, the name of the suite is precisely AppComponent.
it is a global Jasmine function that is used to define the specs. Its first parameter is
the description of what that spec expects to obtain as a result.
Then we have expect, which takes a value and performs a check on that value.
Returning to the specs file of our AppComponent, after the declaration of the suite
we have the following:
beforeEach is a function that will be executed before each of the specs runs. In
it we use the TestBed utility, which will allow us to obtain an instance of the module
to be tested outside the normal life cycle of the application. The beforeEach function
is declared as asynchronous because we must obtain the instance before performing
the tests.
Then, we have the specs themselves, which are self-descriptive. For example, let’s
take the following:
What we are checking is if the title property of AppComponent has the value
“angular-test.”
354
Chapter 13 Testing
And this is to be expected, since we have not modified the value of the title property
in our AppComponent. We can make changes, and the window where the test execution
results are displayed will automatically refresh.
If we make the modification, that error will disappear, but we will have another
(Figure 13-6).
355
Chapter 13 Testing
Since we have modified the title, now what we would expect to get is:
356
Chapter 13 Testing
As you can see, it is very simple to make checks on the property values of the
components and the way they are displayed. Now we will proceed to show how we can
perform tests for pipes and services.
Testing a Pipe
We are going to build a pipe that returns a truncated string of characters to a
certain limit:
ng g p pipes/truncate
Two files have been created – one that contains the definition of the pipe and
another with the corresponding suite (truncate.pipe.spec.ts):
describe('TruncatePipe', () => {
it('create an instance', () => {
const pipe = new TruncatePipe();
expect(pipe).toBeTruthy();
});
});
357
Chapter 13 Testing
Our suite has a unique spec that verifies that an instance of the pipe could be
created.
But since we know what the expected behavior of our pipe is, we can add a new spec
that must be overcome by the pipe.
This is the approach we should follow for the tests. Write them first, and then
implement the functionality. Of course, the first time we run the tests they will fail, but by
developing them before the code itself, we will make sure not to write unnecessary code.
We will only implement what is necessary to pass the test.
In addition, it is a way of organizing work, where we can have a group, perhaps more
experienced, writing the definitions of tests to guide the development of functionalities.
Let’s modify the file as follows:
describe('TruncatePipe', () => {
it('create an instance', () => {
const pipe = new TruncatePipe();
expect(pipe).toBeTruthy();
});
});
358
Chapter 13 Testing
But now we can work on our pipe knowing that there is a test to make sure
everything works correctly. Let’s rewrite our pipe as follows:
@Pipe ({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
}
359
Chapter 13 Testing
Of course, we can make our pipe more complex, for example, declaring the limit
parameter as optional and also optionally adding an ellipsis at the end of the sentence.
And for each of these modifications, we will act in the same way – creating the specs first,
seeing them fail, and then working on the functionality.
Let’s add the following spec:
360
Chapter 13 Testing
First of all, the IDE will warn us of an error, because we are passing a third parameter
when our pipe waits for three. But let’s modify it:
@Pipe ({
name: 'truncate'
})
export class TruncatePipe implements PipeTransform {
}
That is to be expected, because we are not adding the ellipsis to the return value.
361
Chapter 13 Testing
describe('TruncatePipe', () => {
it('create an instance', () => {
const pipe = new TruncatePipe();
expect(pipe).toBeTruthy();
});
});
With this, we must have a pretty clear idea of how we can test a pipe. Now let’s take
care of the services.
362
Chapter 13 Testing
Testing a Service
First of all, we will create a service to be tested. This service, which we will call auth, will
have three methods: login, logout, and isAuthenticated.
The function of the first two is to log in and log out the user, and the third will return
true or false depending on whether the user has indicated their credentials or not.
ng g s services/auth
@Injectable({
providedIn: 'root'
})
export class AuthService {
constructor() { }
login() {}
logout() {}
isAuthenticated() {}
In the login method, we should receive a username and password, call an API, etc.
But what interests us is the mechanics of the test.
Now let’s go to our test file and rewrite it as follows:
describe('AuthService', () => {
let service: AuthService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(AuthService);
});
363
Chapter 13 Testing
localStorage.setItem('currentUser', 'admin');
expect (service.isAuthenticated()).toBeTruthy();
});
});
isAuthenticated() {
const currentUser = localStorage.getItem('currentUser');
if (currentUser) {
return true;
}
364
Chapter 13 Testing
return false;
}
Summary
We have seen the tools that Angular offers developers to perform unit tests. We have built
suites with specs to test classes, pipes, and services. Of course, this is a minimal part of
what we can do, but with these firm foundations, we can continue to explore and expand
our skills. When it starts with the issue of testing, it can be tedious, but it has happened to
me that Angular has made it not only simple but entertaining, and I trust that the same
will happen to you.
365
Index
A Angular folder, 75
angular-in-memory-web-api, 99
ActivatedRoute, 113, 114
Angular.json, 128, 148, 163, 164, 239, 292
Adding post
Angular project, 81, 212, 232
add-post.component.html, 303, 304
Angular server, 214, 240
add-post.component.ts, 301, 302
Angular 10 Snippets package, 91
dashboard.component.html, 304, 305
AppComponent class, 79, 80, 352
index.js, 307, 308
app.component.css, 78
login form, 302
app.component.html, 78, 80, 86, 97
modal errors, 306
app.component.spec.ts, 78, 352
modal window, 305, 306
app.component.ts, 78, 79, 178, 352
models, 308–315
App files, 78
nodemon, 307
Application code, 229
observables, 315–317
Applications menu, 165
postForm, 302
AppModule, 81, 95, 99, 100, 148, 229, 281
add-post.component.html,
app.module.ts, 78, 81, 91, 98, 131, 169,
303, 304, 313
173, 217, 218, 223, 227, 229, 281
Advanced Connection Options, 205
app.routes, 172
Angular, 220
app.routes.ts, 91, 94, 113, 143, 172, 185,
angular CLI, 4
217, 229, 255, 261, 281, 286, 289
Chrome, 3
Arrays, 40–44
errors, 11
Arrow functions
IDE, 5–10
advantages, 36, 39
Ionic, 5
anonymous function, 36, 39
NodeJS, 3
car.accelerate(), 38
Postman, 10
object, 37
semantic versioning, 2
output, 37–39
TypeScript, 3, 4
parameter, 36
versions, 1, 2
setTimeout function, 36
website, 1
ts file, 36
Angular application, 81
variable, 36, 38
Angular-cli, 77, 123
version, 36
Angular-cli live server, 148
367
© Victor Hugo Garcia 2023
V. H. Garcia, Getting Started with Angular, https://doi.org/10.1007/978-1-4842-9206-8
INDEX
368
INDEX
369
INDEX
Home.component (cont.) L
home.component.html, 106, 107
Latin text, 119
home.component.ts, 115
Load indicator, 148, 152
Home template, 256
LocalLogin function, 181
HTML tag, 80
LocalStorage, 182, 183, 259
HttpClient, 102
Login button, 175, 176, 180, 181, 261
HttpClientInMemoryWebApiModule, 99
Login component, 214–217, 220, 223, 225,
HttpClientModule, 98, 228
227, 242, 255, 259
LoginService, 223, 257
I Login success, 242
Logout button, 180, 181, 260, 261, 297
IDE
Lorem ipsum generator, 119, 129
packages
Atom, 7–10
installation, 5, 6 M
VS Code, 6, 7 MongoDB, 203–205, 207, 234, 264
Visual Studio Code, 5 Compass, 312
InMemoryDataService, 100 Server, 208
In-memory Web API module, 99, 137 Mongoose, 232
Insert document, 237, 238
Ionic framework, 5 N
isAuthenticated method, 364
Navbar component, 85–97, 170, 171, 187
Navbar links, 88
J ngIf directive, 123
ng-router-appmodule, 92
Jasmine, 347, 348, 350–354
Node index.js, 234
NodeJS, 3, 183, 203, 229, 231, 347
K Nodemon, 307
Noimage, 153, 156
Karma
Non-authorized navigation, 186
Angular-test, 349
Nonexistent method, 140
AppComponent, 350, 352, 354
Nontrivial application, 152
configuration, 351
Jasmine, 353
karma.conf.js file, 351 O
modification, 355 Object-oriented languages, 46, 53
new browser window, 349 Object-oriented programming
package.json, 350, 352 (OOP), 46, 47
370
INDEX
Observables, 103 R
onSubmit method, 144, 314, 325,
ReqRes service, 135
328, 330
reqres.service.ts, 98, 103
Rest, 76
P Rest API, 75
RestApp, 202
PageNotFoundComponent, 92
addUser method, 143
Pipe, 153, 155
component, 143
Pipe operator, 136
functionality, 146
Post
home component, 146
component, 266–268, 275–279
new user, 142
creating collection, 268–273
Robust protection, 184
modifying, user class, 263–266
routerLinkActive, 95
service, 273–275
Postman, 10
Private route, 184 S
Profile image, 125, 153, 155 Services, 97
Project structure, 213 setPostToAdd method, 326
Promises styleUrls, 80
advantage, 67
arguments, 65
asynchronous methods, 67, 69 T
asynchronous process, 64 templateUrl, 80
asyncTask function, 64 Testing
chained calls, 68, 69 Angular, 347
data, 64 Jasmine, 347
errors, 65, 66 Karma (see Karma)
index.html file, 64, 65 pipe, 357–362
output, 66 service, 363–365
promises2.ts, 67, 68 Tutorials, 195
tsc, 68 TypeScript, 13, 14
version, 65 Angular, 3, 4
child Employee class, 57
classes, 47
Q abstract, 60, 61
Quickstart, 168, 194 account, 50
documentation, 181 brand property, 59
tab, 167 classes2.ts, 58, 59
371
INDEX
372
INDEX
compling, 49 evaluation, 29
consequences, 49 exit by console, 29, 30
constructor class, 48 HTML tags, 29
errors, 50, 51, 57 index.html file, 27
GitHub repo, 53 modification, 30
instantiate, 48 tsconfig.json, 28
methods, 52
modification, 50
output, 49 U
parameters, 48 UpdateUser method, 141
person1 object, 48 UserDetail component, 113, 115, 137, 140
properties, 57, 58 userDetails function, 108
template strings User id, 109, 110
app.js file, 28 user.jpg, 101
app.ts, 27 UserProfile property, 196
backtick characters, 29 User’s profile, 193
command, 28 user.ts, 99
concatenation, 29
console output, 30
ECMAScript 5, 28 V, W, X, Y, Z
error, 27 VS Code, 5, 85, 91
373