Paul Aluyege - MongoDB, Express, Angular, and Node - Js Fundamentals-Packt (2019)
Paul Aluyege - MongoDB, Express, Angular, and Node - Js Fundamentals-Packt (2019)
Paul Oluyege
b|
Table of Contents
Preface i
Introduction ..................................................................................................... 2
MEAN Architecture Demystification ............................................................. 3
MEAN Architecture ............................................................................................... 3
Express.js ............................................................................................................... 6
Angular ................................................................................................................... 8
Node.js ................................................................................................................. 10
Getting Started with Node ........................................................................... 11
Exercise 1: Creating Our First Node Program .................................................. 12
Activity 1: Creating an HTTP Server for a Blogging Application .................... 14
Understanding Callbacks, Event Loops, and EventEmitters in Node ..... 15
Callback ................................................................................................................ 15
Event Loops ......................................................................................................... 17
EventEmitter ........................................................................................................ 19
EventEmitter Implementation ......................................................................... 19
The AddListener Method ................................................................................... 20
Trigger Events ...................................................................................................... 20
Removing Listeners ............................................................................................ 20
Some Other Features of EventEmmitter .......................................................... 22
Understanding Buffers, Streams, and the Filesystem in Node ............... 22
Buffer .................................................................................................................... 22
Exercise 2: Creating, Reading, and Writing to a Buffer .................................. 23
Uninitialized Buffers ........................................................................................... 26
d|
Introduction ................................................................................................... 40
Getting Started with RESTful APIs ............................................................... 40
Node RESTful APIs Design Practice ................................................................... 42
Getting Started with MongoDB Atlas ......................................................... 45
Clusters ................................................................................................................ 45
Exercise 4: Creating a Free-Tier Cluster on MongoDB Atlas .......................... 46
The Native MongoDB Driver and Mongoose (Object Document Mapper) .. 50
Using the Native MongoDB Driver .................................................................... 51
Using Mongoose .................................................................................................. 52
Exercise 5: Connecting a Node Application with the MongoDB
Native Driver ....................................................................................................... 53
Exercise 6: Connecting the Node Application with a Third-Party
Driver (Mongoose) .............................................................................................. 54
Creating and Defining a Schema with Mongoose ........................................... 56
Creating Models in Mongoose ........................................................................... 57
Exercise 7: Creating a Database Schema and a Data Model
Using Mongoose .................................................................................................. 58
|e
Introduction ................................................................................................... 76
Getting Started with Angular CLI ................................................................ 76
The Angular CLI ................................................................................................... 76
Installing the Angular CLI ................................................................................... 77
Exercise 10: Creating and Running a New Application on the Angular CLI . 78
Project File Review .............................................................................................. 80
Using Components, Directives, Services, and Making HTTP Requests
in Angular ....................................................................................................... 82
Angular Components .......................................................................................... 82
Exercise 11: Creating Angular Components .................................................... 83
Directives ............................................................................................................ 85
Exercise 12: Implementing a Structural Directive .......................................... 87
Exercise 13: Implementing an Attribute Directive .......................................... 88
Data Binding in Angular ..................................................................................... 89
Services ................................................................................................................ 89
f|
Introduction ................................................................................................. 118
Node Security and Best Practices ............................................................. 118
Securing your Node Applications ................................................................... 118
Node Application Authentication with JWTs ........................................... 126
The Structure of a JWT ..................................................................................... 127
How JWTs Work ................................................................................................ 130
Exercise 17: Creating a Token-Based User Authentication System for
a Node Application .......................................................................................... 131
Activity 11: Securing the RESTful API ............................................................. 139
Node Application Authentication with Passport .................................... 140
Getting Started with Passport ........................................................................ 140
Passport's Features ......................................................................................... 141
Passport Authentication Strategy ................................................................. 141
Authenticating Requests with Passport ........................................................ 144
Exercise 18: Creating a User-Authenticating System in a Node
Application Using Passport's LocalStrategy ................................................. 147
Implementing Facebook and Twitter Strategies ......................................... 157
Creating Routes and Granting Permissions ................................................. 158
Exercise 19: Creating a User-Authenticating System in Node Using
the Facebook Strategy ..................................................................................... 159
Activity 12: Creating a Login Page to Allow Authentication with Twitter
Using Passport Strategies ............................................................................... 164
Summary ...................................................................................................... 165
Introduction ................................................................................................. 168
h|
Introduction ................................................................................................. 202
Angular Animations and the Latest Angular Features ........................... 202
Getting Started with the Animation Module ................................................ 202
Animating an Element ..................................................................................... 205
Animating Route Transitions .......................................................................... 209
Exercise 24: Creating an Animated Route Transition between
Two Pages ......................................................................................................... 210
Activity 15: Animating the Route Transition Between the Blog Post
Page and the View Post Page of the Blogging Application ......................... 212
Other Angular Features .................................................................................. 213
Storing and Defining Constants in Angular .................................................. 215
Angular Authentication Guard ....................................................................... 215
Activity 16: Implementing Router Guard, Constant Storage, and
Updating the Application Functions of the Blogging Application .............. 218
Optimizing Angular Applications .............................................................. 219
Measuring Load Time Performance .............................................................. 220
Exercise 25: Web Application Performance Analysis Using PageSpeed .... 220
Optimizing Angular Application Load Time Performance .......................... 222
Optimizing Angular Application Runtime Performance ............................. 225
Exercise 26: Checking the Change Detection Cycle of an Angular
Application ........................................................................................................ 230
Testing Angular Applications ..................................................................... 232
Getting Started with Unit Testing .................................................................. 233
Unit Testing a Service ...................................................................................... 237
Getting Started with E2E Testing with Protractor ........................................ 240
Interacting with Elements ............................................................................. 241
Exercise 27: Performing an E2E Test for a Default Angular CLI
Application to Find an Element Using By.CSS ............................................... 242
j|
Appendix 249
Index 341
Preface
>
About
This section briefly introduces the author, the coverage of this book, the technical skills you'll
need to get started, and the hardware and software requirements required to complete all of
the included activities and exercises.
ii | Preface
Objectives
• Understand the MEAN architecture
• Create RESTful APIs to complete CRUD tasks
• Build a blogging application with basic features
• Create simple animations using Angular
• Perform unit testing on Angular applications
Audience
MongoDB, Express, Angular, and Node.js Fundamentals is ideal for beginner and
intermediate frontend developers who want to become full-stack developers. You will
need some prior working knowledge of JavaScript and MongoDB as we skim over its
basics and get straight to work.
| iii
Approach
MongoDB, Express, Angular, and Node.js Fundamentals takes a hands-on approach to
teach you how to build professional full-stack web applications. It contains multiple
activities that use real-life business scenarios for you to practice and apply your new
skills in a highly relevant context.
Hardware Requirements
For an optimal student experience, we recommend the following hardware
configuration:
• Processor: Intel Core i5 or equivalent
• Memory: 4 GB RAM
• Storage: 35 GB available space
Software Requirements
You'll also need the following software installed in advance:
• OS: Windows 7 SP1 64-bit, Windows 8.1 64-bit, or Windows 10 64-bit
• Browser: Google Chrome, latest version
• Postman | API Development Environment, latest version
• Visual Studio Code, latest version
• Node.js LTS 8.9.1 installed
• Angular 7 CLI 7.21 installed
• Express.js
• MongoDB Atlas
Installing Postman
Install Postman by following the instructions at this link: https://www.getpostman.
com/downloads/.
Installing Visual Studio Code
Install Visual Studio Code by following the instructions at this link: https://code.
visualstudio.com/download/.
Additional Resources
The code bundle for this course is also hosted on GitHub at https://github.com/
TrainingByPackt/MongoDB-Express-Angular-and-Node.js-Fundamentals.
We also have other code bundles from our rich catalog of books and videos available at
https://github.com/PacktPublishing/. Check them out!
Conventions
Code words in text, database table names, folder names, filenames, file extensions,
pathnames, dummy URLs, user input, and Twitter handles are shown as follows: "We
use var fs = require('fs') to declare fs (a filesystem variable) and assign the filesystem
module to it."
A block of code is set as follows:
interface LoginResponse {
accessToken: string;
accessExpiration: number;
}
|v
this.http.post<LoginResponse>('api/login', {
login: 'foo',
password: 'bar'
}).subscribe(data => {
console.log(data.accessToken, data.accessExpiration);
});
New terms and important words are shown in bold. Words that you see on the screen,
for example, in menus or dialog boxes, appear in the text like this: "Create the Post
Create/Edit form components"
Introduction to the
1
MEAN Stack
Learning Objectives
This chapter presents the fundamentals of the MEAN stack, along with practical exercises that
implement several features that are described in the chapter.
2 | Introduction to the MEAN Stack
Introduction
The MEAN stack is comprised of four technologies: MongoDB, Express.js, Angular.js,
and Node.js, and is one of the best tools available for solving contemporary business
challenges. This technology results in low maintenance costs, fewer resources being
required, and entails less development time. Furthermore, it provides flexibility,
high scalability, and several features that help businesses deliver high-performance
solutions.
For developers or aspiring developers, learning MEAN is essential as it has become an
indispensable tool for web applications. Businesses and developers are increasingly
skipping Ruby, Python, and PHP, and choosing JavaScript for development. Also,
JavaScript boasts the most active developer community.
Other stacks also exist, such as the Linux, Apache, MySQL, and PHP (LAMP) stacks.
However, the flexible simplicity of the MEAN stack is highly desirable. Also, factors such
as Node being super-fast and the fact that MongoDB is a compelling database with
which apps can be developed, tested, and hosted in the cloud with ease, add to the
popularity of the MEAN stack. In comparison, LAMP is MySQL-based and has a
confining structure, and thus is not as popular as the MEAN stack.
This chapter introduces the components, libraries, and framework that make up the
MEAN stack architecture. We will also describe various contemporary commercial
applications that can be built with the MEAN stack. Finally, we will build web servers
with Node.js and implement various features that are provided by the backend runtime
environment.
MEAN Architecture Demystification | 3
MEAN Architecture
The MEAN architecture is comprised of four different JavaScript technologies when
they are stacked successively. The architecture is designed so that each component
performs specific tasks individually as well as in sync with other layers in the stack to
achieve an end goal.
In the MEAN architecture that's shown in the following diagram, the client initiates the
requests, which flow right from the first layer down to the last. Appropriate responses
to those requests then flow right through all the layers back up to the client.
4 | Introduction to the MEAN Stack
The client first interacts with the user interface, which is built using the Angular
frontend framework. Requests made on the interface are passed to Node.js, the server-
side engine. Then, the middleware framework known as Express.js makes a request to
MongoDB, which is the database. Express.js retrieves the response in the form of data
from the database. Finally, this response is then returned by Node.js to the client via the
user display:
Note
For further information on the MEAN stack, refer to the following page:
https://blog.meanjs.org.
There are several advantages to using the various MEAN components, which has made
it a technology stack to reckon with for start-ups and big companies alike. There are
several reasons why the MEAN stack is preferred in web application development. A few
of these reasons are listed here:
• Reduced development time and cost
• Speedy MVP development and scalability
• Increased developer flexibility and efficiency
• Excellent performance
• Speedy data storage and retrieval
• Uniform programming language
• Operating system compatibility
MEAN Architecture Demystification | 5
Owing to its performance benefits, the MEAN stack has been adopted by many
contemporary commercial organizations, such as Uber, PayPal, Netflix, and so on to
build their websites and web applications. Ulbora and Trakit.me are a couple of other
web applications that are built using the MEAN stack.
Note
A few live applications that have been built with the MEAN stack can be viewed
here: https://github.com/linnovate/mean/wiki/Built-with-Mean.
Before going into MEAN application development, we will briefly discuss the individual
technologies that make up the MEAN stack. We will start with MongoDB.
MongoDB
MongoDB is a document-oriented database program. The database, which is classified
as NoSQL, is developed by MongoDB Inc. It's a free and open source database that uses
JSON-like documents with schemas. Described here are some of the major features that
make MongoDB stand out in application development:
Strong consistency: Strong consistency is a default feature in MongoDB, and thus it
takes no additional upgrades to create consistency. It's also highly guaranteed: as long
as a write is successful, you will be able to read it. Automatic failover of the replica
set makes it highly available. Also, flexibility and scalability are not readily available in
traditional databases that only rely on a defined schema. In comparison, MongoDB,
which is NoSQL, relies on the key-to-value pair approach. This enables flexibility and
scalability in MongoDB.
Real-time aggregation: This is a built-in MongoDB feature that allows real-time
statistical analysis and the generation of pre-aggregated reports on request.
Ad hoc queries: These are impromptu queries that are written to extract information
from the database, but only when necessary.
Indexing: Indexing in MongoDB makes it easy to search documents without performing
a collection-wide scan by matching specific queries with documents.
6 | Introduction to the MEAN Stack
Note
For more features and pricing information on different tiers, refer to this link:
https://www.mongodb.com/cloud/atlas.
Despite having several advantages, there are a few limitations of MongoDB that we need
to be aware of:
Limited data size: Documents have a maximum data size of 16 MB.
Limited nesting: The maximum number of documents that can be nested is 100.
Joins not supported: This is one of the main differences between RDBMS and NoSQL.
MongoDB is not designed to have related columns. Rather, it stores de-normalized data
and thus joins are not supported.
However, these limitations cannot overshadow the fact that MongoDB provides high
performance, high availability, and automatic scaling, thus mitigating—if not completely
eliminating—the preceding limitations.
So far, we have introduced the MEAN stack and its database component, MongoDB.
We will now continue with the introduction of the second component, Express.js—a
middleware application framework for developing web applications.
Express.js
Express.js or simply Express is a Node.js web application framework that provides
a strong set of features for web and mobile applications. It's free, open source, fast,
un-opinionated (that is, there is no "right way" to complete a certain task in Express),
and is comprised of a simple web framework that's designed for building web
applications and APIs. The framework has achieved a dominant position in the industry
as the server framework for Node.js. Express possesses rich features and advantages. A
few of these are listed as follows:
• Configuration
• Middleware
• Views and templates
MEAN Architecture Demystification | 7
• Routing
• Session
• Security
The preceding list includes a few of the most dominant features and advantages of
Express, and each of them contribute to the performance benefits afforded by this
framework. In Express, third-party plugins (middleware) can be set up to respond to
HTTP/RESTful requests, and a routing table can also be defined to perform different
HTTP operations. HTML pages are dynamically rendered by Express based on passing
arguments to templates. Express supports a wide range of templating languages,
such as Pug, React, hbs, and h4e. Furthermore, it has detailed documentation to help
developers maximize its uses. The community support is huge: as of now, Express has
gathered over 30,000 GitHub stars, which means that it is well accepted and supported
in the open source community. Also, Express allows for the addition of custom code.
In terms of performance, Express leads other frameworks because of its inherent
properties that make the routing process efficient and faster.
Express presents a thin layer that enables the addition of robust features and
functionalities to your web applications. It organizes web applications into a model
view controller (MVC) architecture and manages everything from routes, to rendering
views, and pre-forming HTTP requests.
However, we do need to be aware of certain limitations while developing apps in
Express. In a real-life situation, when projects become more complex and code base
size increases, refactoring becomes very complex and a lot of manual labor is required.
This is because the absence of organization makes the handling of complex applications
difficult as Express is not designed to manage complexity by itself. Furthermore, the
cost of scaling increases with application complexity.
Note
To explore popular Node.js frameworks that have been built on Express.js, refer to
this link: https://expressjs.com/en/resources/frameworks.html.
The next component after Express in the MEAN stack is a JavaScript frontend
framework known as Angular. We will be introducing Angular features across all of its
existing versions, and then we will talk briefly about its advantages and limitations.
Angular
Angular is an open source TypeScript and JavaScript-based frontend web application
structural framework and is managed and sustained by Google. Being open source, a
community of software developers and corporations provide maintenance and address
issues that are encountered when developing single-page applications. Listed here are
some of the advantages that make Angular stand out:
• Use of Typescript: Angular uses Typescript, which is a better tooling language that
compiles to readable, clean code and standards-based JavaScript, which is highly
scalable.
• Component-based architecture: Angular embraces component-based
architecture to ensure high quality code.
• Platform-agnostic philosophy: A platform, agnostic philosophy simply means that
Angular is unbiased toward the use of different technology tools to solve different
problems.
• High performance: The Angular framework offers dependency injection, end-to-
end tooling, declarative templates, and integrated best practices. These features
help to solve development challenges. Thus, web, mobile, and desktop application
development is made easier using Angular.
• Long-term support (LTS) by Google: Long-term support by Google ensures the
there is a continuous and timely release of versions to address any performance
issues and bugs.
However, note that no framework is perfect and there have been several Angular
versions. The following list shows how Angular has evolved over the years, along with its
different versions:
• Angular.js: This was the first version and was a library written in JavaScript
released in 2009. It was developed with a modular structure that expresses an
application's behavior and manages its data, logic, and rules. Its data binding
(binding data to a DOM element) and architecture was based on model view
controller (MVC), but was built with no support for mobile.
MEAN Architecture Demystification | 9
• Angular 2: This version was written in TypeScript 1.8 and released in 2016. It
was developed to be faster than the previous version and was mobile-oriented.
It offers structured components and has support for offline compilation and
fast change detection. Dynamic loading and asynchronous operation brought
about performance improvements. Furthermore, it provides support for
component directives, decorator directives, and template directives. Data binding
was improved, compared to the previous version, and it offered support for
component-based architecture.
• Angular 4: This was written in TypeScript 2.1 and 2.2 and was released in 2017.
It was developed to be compact and fast. Animation packages and pipes were
introduced, along with directive improvements and simplified HTTP requests.
• Angular 5: This version was released in 2017 and was faster than the previous
version. Features such as watch mode, type checking, improved metadata
structure, and better error messages were introduced. AOT (ahead-of-time)
compilation was set to default.
• Angular 6: This was released in early 2018 and was faster than the previous
versions with features such as ng update, ng add, Angular element, Angular
Material and Components development kit (CDK), RxJs version 6, and improved
animation, and a CLI workplace.
• Angular 7: This version was released in October 2018 with updated features such
as drag and drop, virtual scrolling, and CLI prompts, among many others.
Note
The third version release of Angular was skipped to avoid conflict with the router
package version. For more detailed documentation, please refer to this link:
https://angular.io/. Information on bug fixes in each version can be found here:
https://github.com/angular/angular/blob/master/CHANGELOG.md.
However, you should be aware that no web framework is perfect, and in the case
of Angular, its verbosity and complexity renders it a difficult framework to master.
Furthermore, Angular has a steep learning curve.
In this section, we introduced the third component of the MEAN stack – Angular. We
discussed its features, as well as its limitations. In the next section, we will introduce
our final MEAN component, a server-side technology known as Node.js.
10 | Introduction to the MEAN Stack
Node.js
Node is an open source program that is built on a JavaScript runtime engine (Chrome's
V8) and consists of JavaScript libraries that are implemented in the form of modules.
The cross-platform server environment is lightweight and efficient because it uses an
event-driven, non-blocking I/O model. For example, PayPal employs Node to solve
the problem that arises owing to the boundary between the browser (client side) and
server (server side) by enabling both to be written in JavaScript. This, in turn, helps
PayPal unify their engineering specialties into one team, thus enabling them to focus
on user needs and create solutions to meet these on the technology stack level. Some
other websites and applications built on Node are eBay, GoDaddy, Groupon, Uber,
Netflix, Mozilla, Yahoo, LinkedIn, and so on. The outstanding performance offered by
Node makes it the heart of the MEAN stack. According to a number of benchmarks,
JavaScript technology for the backend outperforms several other backend programming
languages, such as PHP, Python, and Ruby. Listed here are some of the major
advantages of Node:
• Fast code execution: Google Chrome's V8 JavaScript engine makes the Node
library extremely fast at code execution as V8 compiles JavaScript into native
machine code.
• Single-threaded operation: Node is able to handle concurrent tasks because
its architecture is built to use the single-threaded event loop model.
• Highly Scalable: Its microservice architecture makes Node highly scalable, thereby
making it a highly sought after development environment by most start-ups and
established companies alike.
• Asynchronous and event-driven I/O: Programs in Node do not run until an event
occurs.
• Open source (MIT license) and community support: Node is a distributed
development project that is governed by the Node.js Foundation. Also, Node has a
very active and vibrant community with several code files shared by GitHub users.
• Easy web-application deployment: Node allows the automated deployment of
web applications. Also, the deployment of further updates can be reduced to a
single command with Node. Thus, the deployment of Node applications has also
led to the proliferation of Platform-as-a-service (PaaS) providers such as Heroku,
Modulus, and so on.
• Easy scalability: Developers find it easy to scale applications horizontally as well
as vertically.
Getting Started with Node | 11
Note
A detailed introduction to Node, implementing node features, and various activities
will be covered in future chapters.
Note
The code files for this exercise can be found here: http://bit.ly/2TaT32E.
1. Create a JavaScript file and name it hello.js. This can be done using the options in
the File tab.
2. Load the built-in http module by using the "require" directive and passing a
constant variable (http) to it:
const http = require ('http');
3. Declare and initialize the hostname and port as constant variables using the
following code:
const hostname = '127.0.0.1';
const port = 8000;
Getting Started with Node | 13
4. Create the server using the createServer method and pass req and res, which
denote a request to and a response from the server, respectively:
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
});
This created server sends the following response to the browser:
statusCode: 200, the Content-Type header in plain text, and a string,
Hello World.
5. Have the server listen to localhost on port 8000 using the following command:
server.listen(port, hostname, () => {
console.log ('Server running at
http://${hostname}:${port}/');
});
6. Run the server using the following command prompt, as shown in the following
screenshot:
node hello.js
One of the learning objectives of this book is to create the components of a blogging
application with basic features. We will commence its development in this chapter.
Most of this will be done in the activity section of each topic.
Note
The code files for this activity can be found here: http://bit.ly/2TZYqz5.
Note
The solution for this activity can be found on page 250.
Understanding Callbacks, Event Loops, and EventEmitters in Node | 15
Callback
Node is built to be asynchronous in everything that it does. In this view, a callback is
an asynchronous equivalent of a function that is called after a given task is completed.
Alternatively, it can be defined as a function that is passed into another function so that
the latter can call it on the completion of a given task. It allows other programs to keep
running, thereby preventing blocking.
Let's consider a JavaScript global function, setTimeout(), as implemented in the
following snippet:
setTimeout(function () {
console.log("1…2…3…4…5 secs later.");
}, 5000);
setTimeout() accepts a callback function and a delay in milliseconds as first and second
arguments, respectively. The callback function is fired after 5,000 milliseconds has
elapsed, thereby printing "1…2…3…4…5 secs later." to the console.
An interesting thing is that the preceding code can be rewritten and simplified, as
shown here:
var callback = function () {
console.log("1…2…3…4…5 secs later.");
};
setTimeout(callback, 5000)
The readFileSync() method reads a file synchronously (all of the content is read at the
same time). It takes in the file path (this could be in the form of a string, URL, buffer, or
integer) and an optional parameter (either an encoder, which could be a string, null, or
a flag in the form of a string) as an argument. In the case of the preceding snippet, the
synchronous filesystem function takes in a file (fileText.txt) from the directory and
the next line prints the contents of the file as a buffer. Note that if the encoding option
is specified, then this function returns a string. Otherwise, it returns a buffer, just as
we've seen here.
An example of asynchronous reading is as follows:
var callback = function (err, FileData) {
if (err) return console.error(err);
console.log(FileData);
};
fs.readFile('fileText.txt', callback);
In the preceding snippet, the readFile() method asynchronously reads the entire
contents of a file (read serially until all its content is entirely read). It takes in the
file path (this could be in the form of a string, URL, buffer, or integer), an optional
parameter (either an encoder, which could be a string or null, or a flag in the form of a
string), and a callback as arguments. It can be seen from the first line that the callback
function accepts two arguments: err and data (FileData). The err argument is passed
first because, as API calls are made, it becomes difficult to track an error, thus, it's best
to check whether err has a value before you do anything else. If so, stop the execution
of the callback and log the error. This is known as error-first callback.
In addition, if callbacks are not used (as seen in the previous example on synchronous
reading) when dealing with a large file, you will be using massive amounts of memory,
and this leads to a delay before the transfer of data begins (network latency). To
summarize, from the use of the two filesystem functions, that is, the readFileSync(),
which works synchronously, and the readFile(), which works asynchronously, we can
deduce that the latter is safer than the former.
Understanding Callbacks, Event Loops, and EventEmitters in Node | 17
Note
For more information on callback hell, refer to this link:
http://callbackhell.com/.
Event Loops
An event loop is an efficient mechanism in Node that enables single-threaded
execution. Everything that happens in Node is a reaction to an event. So, we can say
that Node is an event-based platform, and that the event loop ensures that Node keeps
running. Node uses event loops similar to a FIFO (first-in first-out) queue to arrange
the tasks it has to do in its free time.
Let's assume that we have three programs running simultaneously. Given that each
program is independent of another at any given time, they won't have to wait for the
other before the output of each program is printed on the console. The following
diagram shows the event loop cycle:
The following diagram shows the event queue and the call stack operations:
Now, we are able to understand the concept of the event loop in Node. Next, we will
explore the EventEmitter, which is one of the most important classes in Node.
EventEmitter
Callbacks are emitted and bound to an event by using a consistent interface, which is
provided by a class known as EventEmitter. In real life, EventEmitter can be likened to
anything that triggers an event for anyone to listen to.
EventEmitter implementation involves the following steps:
1. Importing and loading the event modules by invoking the "require" directive.
2. Creating the emitter class that extends the loaded event module.
3. Creating an instance of the emitter class.
4. Adding a listener to the instance.
5. Triggering the event.
EventEmitter Implementation
You can implement the EventEmitter by creating an EventEmitter instance using the
following code:
var eEmitter = require('events'); // events module from node
class emitter extends eEmitter {} // EventEmitter class extended
var myeEmitter = new emitter(); // EventEmitter instance
20 | Introduction to the MEAN Stack
Trigger Events
To trigger an event, you can use emit(event, arg1), as can be seen here:
EventEmitter.emit(event, arg1, arg2……)
The .emit function takes an unlimited number of arguments and passes them on to the
callback(s) associated with the event.
By putting all of this code together, we have the following snippet:
var eEmitter = require('events');
class emitter extends eEmitter { }
var myEemitter = new emitter();
myEemitter.on('event', () => {
console.log('Hey, an event just occurred!');
});
myEemitter.emit('event');
Removing Listeners
We can also remove a listener from an event by using the removeListener(event,
listener) or removeAllListeners(event) functions. This can be done using the following
code:
EventEmitter. removeAllListeners (event, arg1, arg2……)
EventEmitter works synchronously; therefore, listeners are called in the order in which
they are registered to ensure proper sequencing of events. This also helps avoid race
conditions or logic errors. The setImmediate() or process.nextTick() methods make it
possible for a listener to switch to an asynchronous mode.
For example, let's say we wanted an output such as "Mr. Pick Piper". We could use the
following code:
console.log ("Mr.");
console.log("Pick");
console.log("Piper");
However, if we want an output such as "Mr. Piper Pick" using the preceding
snippet, then we would introduce an event sequencing function, setImmediate(), to help
the listener to switch to an asynchronous mode of operation, such as in the following
code:
console.log ("Mr.");
console.log("Pick");
setImmediate(function(){
console.log("Piper");
});
The output of the preceding function is exactly as expected:
Note
Callbacks, event loops, and event emitters are important concepts in Node.js.
However, a more detailed description is beyond the scope of this book. For more
information on these topics, please refer to this link: https://nodejs.org/.
22 | Introduction to the MEAN Stack
Buffer
This is a chunk of memory where data is stored temporarily. This non-resizable
memory is designed to handle raw binary data, and the integers in it are limited
to values from 0 to 255 (2^8 - 1), with each integer representing one byte.
Let's look at a real-world scenario so that we have a better understanding of
what buffers are and how they work by using the following screenshot from a YouTube
video:
When a YouTube video starts to play, and if the internet is super fast, a gray area
is observed in the playing stream. This area is the buffer zone, where data is being
collected and stored temporarily (usually in the RAM) to allow for continuous
playing, even when the internet is disconnected. The red zone is the playing zone
(data processing zone) whose length is dependent on the video's playing speed and
the amount of data that has been buffered. If the browser is refreshed, the temporary
storage is re-initialized and the process is restarted.
Now that we have seen a real-time application using a buffer, we will now create, read,
and write to buffers in the upcoming exercises.
Note
The code files for this exercise can be found here: http://bit.ly/2SiwAw6.
1. Create a new folder in an appropriate location in your system and rename it Buffer
Operations.
2. Open the newly created Buffer Operations folder from your code editor and
create a buffer.js file. Buffers can be created in the following ways:
You can create an uninitialized buffer by passing the buffer size to
Buffer.alloc(), or you can create an instance of a buffer class; for example, let's
create an uninitiated buffer of 5 bytes using the following code:
var buf1 = Buffer.alloc(5);
var buf2 = new Buffer(5);
console.log(buf1)
console.log(buf2)
The output is as follows:
You can create a buffer using a given array using from() or using an instance
of a buffer; for example, let's initialize a buffer with the contents of the array
[10, 20, 30, 40, 50]:
varbuf3 = new Buffer([10, 20, 30, 40, 50]);
varbuf4 = Buffer.from([ 10, 20, 30, 40, 50]);
console.log(buf3)
console.log(buf4)
Note that the integers that make up the array's contents represent bytes. The
output can be seen as follows:
Finally, you can create a buffer using a given string and, optionally, the encoding
type using from() or using an instance of a buffer. The following code initializes
the buffer to a binary encoding of the first argument, which is a string that's
specified by the second argument, which is an encoding type:
var buf5 = new Buffer("Hi Packt students!", "utf-8");
var buf6 = Buffer.from("Hi Packt students!", "utf-8")
console.log(buf5)
console.log(buf6)
The output can be seen as follows:
The buffer also supports encoding methods such as ascii, ucs2, base64, binary,
and so on.
Understanding Buffers, Streams, and the Filesystem in Node | 25
3. To write into a buffer, we can use the buff.write() method. The output returned
after a buffer has been written to (created) is the number of octets written into it:
buffer.write(string[, offset][, length][, encoding])
Note that the first argument is the string to write to the buffer and the second
argument is the encoding. If the encoding is not set, the default encoding, which
is utf-8, will be used. Write into the buffer that we created in the preceding step
using the following code:
len = buf5.write("Packt student", "utf-8")
console.log (len) //The length becomes 13 after writing into the buffer
The output can be seen as follows:
4. To read from the buffer, the toString() method is commonly used, but keep in
mind that many buffers contain text. This method is implemented as follows:
buffer.toString([encoding][, start][, end]
Here, we will read from the buffer that was written into in the preceding step and
print the output on the command line using the following code:
console.log(buf5.toString("utf-8", 0, 13))
There are a few more methods for buffers, which will be covered in the
following sections.
26 | Introduction to the MEAN Stack
Uninitialized Buffers
You can also create buffers using the allocUnsafe(length) method. The
allocUnsafe(length) method creates an uninitialized buffer of the assigned length.
When compared to the buffer.alloc() method, it is much faster, but old data in the
returned buffer instance needs to be overwritten using either fill() or write().
Let's see how the allocUnsafe(length) method is being used in the following snippet:
var buf = Buffer.allocUnsafe(15);
var buf1 = Buffer.alloc(15);
console.log(buf);
console.log(buf1);
The preceding snippet yields the following output:
Figure 1.14: Output of the buffer created using allocateUnsafe(length) and alloc(length)
Streams
Whenever you talk about reading data from a source and writing it to a destination,
you're referring to streams (Unix pipes). A stream can be likened to an EventEmitter.
There are four types of streams, and they are as follows:
• Readable: Allows you to read data from a source.
• Writable: Allows you to write data to a destination.
• Duplex: Allows you to read data from a source and write data to a destination.
• Transform: Allows you to modify or transform data while data is being
read or written.
Writing to Streams
A stream is said to be writeable if it permits data to be written to a destination,
irrespective of what the destination is. It could be another stream, a file in a filesystem,
a buffer in memory, and so on. Similar to readable streams, various events that are
emitted at various points in writeable streams can also be referred to as instances of
EventEmitter. The write() function is available on the stream instance. It makes writing
data to a stream possible.
Take a look at the following snippet:
const fs = require('fs');
const readableStream = fs.createReadStream('readTextFile.txt');
const writableStream = fs.createWriteStream('writeTextFile.txt');
readableStream.on('data', function (data) {
console.log('Hey!, I am about to write what has been read from the file
readTextFile.txt');
if (writableStream.write(data) === true) {
console.log('Hey!, I am done writing. Open the file writeTextFile.txt to see
what has been written');
}
else
console.log('Writing is not successful');
});
First, we load the filesystem using the require directive. Then, we create two files:
readTextFile.txt and writeTextFile.txt. We then write some text string into
the readTextFile.txt file and leave writeTextFile.txt blank. Using the filesystem
createReadStream() function with the file path or directory as an argument, we create
a readable stream. Thereafter, a writeable stream is created using the filesystem
createWriteStream() function with the file path or directory as an argument.
readableStream.on('data', callback) listens to a data event and attaches a callback,
whereas writableStream.write(data) writes data to a writable stream. Once this snippet
is run, you will realize that the text string read from readTextFile.txt has been written
into the writeTextFile.txt file.
Understanding Buffers, Streams, and the Filesystem in Node | 29
Duplex
Recall from the short description in the previous section that a duplex is a type of
stream that allows you to read data from a source and write data to a destination.
Examples of duplex streams are as follows:
• crypto streams
• TCP socket streams
• zlib streams
Transform
This is another stream method that lets you modify or transform data while data is
being read or written. In essence, transform streams are duplex streams with the
aforementioned functionality. Examples of transform streams are as follows:
• crypto streams
• zlib streams
Filesystems
Node has a module name File System (fs) that works with the file systems on a
computer. This module, fs, performs several methods that can be implemented both
synchronously and asynchronously, but there is there is no guaranteed ordering when
using them.
Reading, creating, updating, deleting, and renaming are some of the operations that can
be performed on any text file that's saved in a directory.
Reading Files
After the required directive has been invoked for the fs module, assuming that we have
a string of text contained in the text file, the file can be read by calling the readFile()
function, as shown here:
var fs = require('fs');
fs.readFile('readFileName.txt', function(err, data) {
if (err) throw err;
console.log('Read!');
});
Creating Files
The methods that are highlighted here are made available by the fs module for creating
new files:
fs.appendFile(): This method appends a given content to a file. The file will be created
in this case, even if it does not exist. This function can also be used to update a file. Its
implementation is as follows:
var text = ' Hey John, Welcome to Packt class.'
var fs = require('fs');
fs.appendFile('newTextFile.txt', 'text'
', function (err) {
if (err) throw err;
console.log('Saved!');
});
Once the preceding code is run, you will see the designated string written into the file.
Understanding Buffers, Streams, and the Filesystem in Node | 31
fs.open(): When this method is called, a given file is opened for writing, and if the file
doesn't exist, an empty file will be created. The implementation is as follows:
var fs = require('fs');
fs.open('TextFile.txt', 'w', function (err, file) {
if (err) throw err;
// let's assume the file doesn't exist
console.log('An empty file created');
});
This method takes a second argument, w, which is referred to as a flag for "writing."
fs.writeFile(): When this method is called on a given file and content exists in it, the
existing content is replaced by new content. In a situation where the file doesn't exist, a
new file containing the given content will be created. This function can also be used to
update the file, and the implementation is as follows:
Var fs = require('fs');
fs.writeFile('textFile.txt', 'Hello content!', function (err) {
if(err)throwerr;
console.log('Saved!');
});
Deleting Files
fs.unlink(): When this method is called on a given file in the filesystem, the file
is deleted. The implementation is as follows:
var fs = require('fs');
fs.unlink('textFile.txt', function (err) {
if (err) throw err;
console.log('File deleted!');
});
32 | Introduction to the MEAN Stack
Renaming Files
fs.rename(): This method takes in three arguments: the old name of the file, the new
name of the file, and a callback. When this method is called on a given file in the
filesystem, the file is renamed. The implementation is as follows:
var fs = require('fs');
fs.rename('textFile.txt', 'renamedtextFile.txt', function (err) {
if (err) throw err;
console.log('File Renamed!');
});
Note
For more information on the Node.js APIs, refer to this link:
https://nodejs.org/api/.
Understanding Buffers, Streams, and the Filesystem in Node | 33
Note
The code files for this exercise can be found here: http://bit.ly/2U1FIH5.
3. Open the read.txt file and write some text; for example, "Welcome to Packt." To
read what has been previously input in the read.txt file, open the read.js file and
type in the following code:
var fs = require('fs')
fs.readFile('read.txt', function(err, data) {
if (err) throw err;
console.log('Read!');});
34 | Introduction to the MEAN Stack
5. Open the write.js file to write into the write.txt file and run the following
command:
var fs = require('fs');
fs.writeFile('write.txt','Welcome to packt' function(err, data) {
if (err) throw err;
console.log('Written!'); });
We use var fs = require('fs') to declare fs (a filesystem variable) and
assign a filesystem module to it. We then use fs.writeFile('write.txt',
'Welcome to Packt' function(err, data) to call the writeFile() function on the
write.txt file, pass the text to be written as the second argument, and attach a
callback as the third argument. Thereafter, we check for if errors using if (err)
throw err. If there are no errors, we print Written! using console.log('Written!').
6. Press Ctrl + ` to open the integrated command-line terminal in Visual Studio and
run the following command:
node write.js
Understanding Buffers, Streams, and the Filesystem in Node | 35
7. Open the write.txt file to view what has been written from the output, as shown
here:
The previous examples and activities have explored the knowledge and implementation
of different built-in modules in Node. The next activity will be a hands-on
implementation of streaming data from one file to another.
Note
The code files for this activity can be found here: http://bit.ly/2EkRW8j.
Note
The solution for this activity can be found on page 251.
Summary
In this chapter, we first described MEAN architecture by briefly expanding on the layers
that comprise the MEAN stack: MongoDB, Express, Angular, and Node. For each of
these components, we discussed their features/advantages and limitations. Thereafter,
we began by describing the Node framework in detail. We first discussed the various
installation options (the LTS and stable versions) that are available. Then, we had a brief
look at the built-in modules that make up Node and also learned about the steps that
are involved in starting a Node application.
In the subsequent topics, we described some important concepts in Node, such
as callbacks, event loops, event emitters, buffers, streams, and the filesystem.
For callbacks, we described their implementation in synchronous and asynchronous
mode. We also looked into how Node employs event loops in sequencing program
operations. Next, we learned how to add event listeners, trigger events, remove
listeners, and implement some other features. We then moved on to discuss buffers.
Specifically, we learned how to create, read, and write to buffers. The next topic
described the various types of streams that are available. We performed an exercise
on writing to and reading from a stream. Finally, we discussed the filesystem and
implemented different operations that can be performed on a file via an activity.
In the next chapter, we will begin developing RESTful APIs to perform create, read,
update, and delete (CRUD) operations.
Developing RESTful
2
APIs to Perform CRUD
Operations
Learning Objectives
• Connect a Node application to a database with the native MongoDB driver and Mongoose
This chapter presents the fundamentals and practical elements that will enable you to develop
and test a RESTful API.
40 | Developing RESTful APIs to Perform CRUD Operations
Introduction
We began this book by describing the MEAN (MongoDB, Express, Angular, and Node)
architecture. Specifically, we described its technology components in terms of their
features/advantages, limitations, and scenarios in which they are best used. We ran a
Node server and implemented various Node features, such as callbacks, event loops,
event emitters, streams, buffers, and the filesystem.
This chapter will revolve around RESTful APIs, which are a major application of Node
that leverage HTTP request types to indicate a desired action. We will also introduce
you to the RESTful API and its designs concepts. Later in this chapter, we will talk about
some operations of MongoDB Atlas, such as creating clusters, schemas, and connecting
the database to applications. This chapter will present the Express framework and its
feature implementation on Node and MongoDB Atlas.
Finally, you'll implement Node, MongoDB Atlas, and Express together, with the goal
of developing a RESTful API to perform CRUD (Create, Read, Update, and Delete)
operations. The chapter activity will wrap up the backend development for our sample
application by creating a RESTful API for our nascent blogging application, which was
developed in the first chapter.
Whenever a customer visits your website after the Geolocation API is implemented
on it, a request is fired to get the location of the GPS-enabled device that was used to
access the website. Then, a response is fed back as a result. This response contains a
location and address in JSON format, which is then retrieved and stored. A sample of
this process is shown in the following code:
In the aforementioned example, the Geolocation API is very robust. Let's assume that
you, as a developer, are tasked with developing an API that may some day go public.
In such a scenario, you have to think of an API design that will provide room for easy
evolution. This is where REST (Representative State Transfer) APIs come into play.
REST is an architectural style that defines a set of restrictions based on HTTP
(Hypertext Transfer Protocol). Using a REST API design is easy because there's no
need to install libraries or additional software. Building APIs that meet your needs and
those of your diverse customers has been simplified by the freedom and flexibility
that exists in REST API designs. RESTful APIs can be implemented with CRUD HTTP
methods such as GET, POST, PUT, and DELETE, which are used for mutation, creation,
and deletion, respectively. Furthermore, the REST stateless protocol and its interaction
with resources through standard operations help systems achieve scalability, fast
performance, and reliability.
42 | Developing RESTful APIs to Perform CRUD Operations
The reuse of components that can be managed and updated (that is, the ability to
handle multiple types of calls, return different data formats, and so on) without
affecting the system as a whole, even while it is running, is the major backbone of the
REST system.
We have seen the basic definition of a REST API, along with a real-world case study. We
have also understood the benefits that are offered by RESTful APIs. Now, we will look
into the Node RESTful API design practices that's employed by established companies in
the industry to develop scalable, secure, and reliable APIs.
3. Send metadata using HTTP headers: When sending payloads, their metadata
should be attached to the HTTP header. For example, information about
authentication and pagination can be passed into the header.
Note
A list of HTTP headers can be found here: https://developer.mozilla.org/en-US/
docs/Web/HTTP/Headers.
4. Use the right framework: It's best to make a good decision on the right use
of technology when embarking on any design and development journey, since
technology frameworks and tools perform best when used in their areas of
strength. For example, it's highly recommended to use Express when it comes
to browser applications, as these applications need to provide templating and
rendering to the user-facing side. On the other hand, when the focus is on
building "strict" RESTful API services that are maintainable and observable, you
can opt for a production framework.
44 | Developing RESTful APIs to Perform CRUD Operations
Note
To read more about HTTP specification and the Representational State
Transfer (REST) architectural style, as described by Roy Fielding, refer to this link:
https://tools.ietf.org/html/draft-griffin-bliss-rest-00. For a full list of HTTP status
codes, refer to this link: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
Getting Started with MongoDB Atlas | 45
So far, we have explained the design concept of a RESTful API by first describing the
relationship between API and REST, and then by listing design practices for Node
RESTful APIs. In the next section, we will get started with MongoDB Atlas, which is a
database-as-a-service offering by MongoDB. Specifically, we will employ the MongoDB
Atlas service to store data and resources that are transferred over the RESTful APIs we
aim to develop.
Clusters
Clusters are a set of MongoDB Atlas server components that can connect to
applications. Three types of cluster are available: shared, dedicated, and production.
The only freely available cluster among these is the shared cluster. In the following
exercise, we will create our first free-tier cluster on MongoDB Atlas.
46 | Developing RESTful APIs to Perform CRUD Operations
2. Click Build a New Cluster and input the project name. Then, click Next, as shown
in the following screenshot:
3. Select any free tier that's available, as shown in the following screenshot, and click
NEXT: CLUSTER TIER:
4. Select M0, a shared cluster (for the purpose of this book), and click Create
Cluster, as shown in the following screenshot:
5. Select the Security tab for the IP whitelist setting, as shown in the following
screenshot:
7. Click Connect on the cluster dashboard and observe the Connect to Cluster0
pop-up page. Then, select the Connect Your Application tab under the Choose a
connection method: label, as shown in the following screenshot:
8. To connect the cluster to a Node application, we can use the connection strings
that are shown in the following screenshot:
Note that MongoDB Atlas has made different connection strings available based
on the language or technology that the application has been developed with. In
this chapter, we are working with an API that was developed with Node, and the
aforementioned procedures are important to successfully connect the cluster to
the Node application.
If you have millions of concurrent requests using the conventional MongoDB method,
then your app will crash. The native MongoDB driver makes provisions for developers to
handle code that helps optimize the request process. Mongoose is an Object Document
Mapper (ODM) that translates MongoDB documents into program objects, thereby
making data access and manipulation in MongoDB easier. Mongoose also employs a
principle called Object Relational Mapping (ORM). This principle is based on having
strict models or schemas. You have to define your schema structure, unlike in the case
of MongoDB, which doesn't need any fixed schema. You can insert or update documents
as per your requirements.
In a situation where you are developing an app for a client that will not have millions
of users or very high read/write concurrency, and you want to develop it quickly, then
Mongoose is an appropriate choice. Development with Mongoose is really fast. You
don't need to create a connection, close it in proper time, optimize the connection,
make promises, and so on. The abstraction layer of Mongoose does all of this for you.
We have briefly discussed the fundamentals of the native MongoDB driver and
Mongoose. Now, we will learn how to use the driver as well as the Mongoose ODM.
Getting Started with MongoDB Atlas | 51
Once the connection has been established, the document collection within the database
is defined and database access and manipulation is made possible:
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
MongoClient.connect(uri, options)
.then((db) => {
accountsDb = db;
collection = accountsDb.db('test');
console.log('Successfully connected to MongoDB');
})
.catch((err) => {
console.log(err);
});
52 | Developing RESTful APIs to Perform CRUD Operations
Using Mongoose
The first step in using Mongoose is installing the Mongoose package by using the npm
install mongoose command.
The Mongoose module is required to allow the properties in it to be referenced from
within the source file that creates the connection. This can be done with the following
command:
var Mongoose = require(Mongoose)
The uri is also declared and assigned the MongoDB Atlas connection string, as seen
with the MongoDB driver. Mongoose uses the connection functions that are shown in
the following snippet. It provides an abstract layer for the purpose of eliminating the
use of collections with the program. Also, function chaining in Mongoose, which is used
by queries, makes code more readable, flexible, and maintainable. An example of this is
shown as follows:
Mongoose.connect(uri, options).then(
() => {
console.log("Database connection established!");
},
err => {
console.log("Error connecting Database instance due to: ", err);
}
);
Now that we've gone through the theory and seen, with the aid of snippets, how
MongoDB can be connected to an application, let's look at some exercises so that
we have a solid understanding of this implementation.
Getting Started with MongoDB Atlas | 53
Note
The code files for this exercise can be found here: http://bit.ly/2GE5pKM.
Before you attempt this exercise, create a folder called Third Party- Mongoose and
install the mongoose module using npn install mongoose on the CLI. Perform the
following steps to complete this exercise:
Note
It is assumed that you have already created a MongoDB Atlas cluster. Therefore,
use the same credentials (username and password) for this exercise. The code file
for this exercise can be found here: https://bit.ly/2CdnFpO.
The schema specifies the attributes of each property, including the data type; for
example, during an insert or update operation, the data type could take either
the required or optional attribute, and the data type values could be set to unique or
not.
For Mongoose, it's best practice for the file to have the same name as that of the model;
therefore, we should create a schema models directory. Defining a schema literally
means defining the structure or organization of the database and involves the following:
• Mapping a schema to a collection
• Defining the shape and properties of documents within a collection
Schemas also define document life cycle hooks called middleware, compound indexes,
document, static model methods, and instance methods. The following snippet shows a
schema being created:
// load Mongoose module
const Mongoose = require("Mongoose");
The .model() function makes a copy of a schema that must be defined before creating
the model. The first argument in .model() is model name, while the second argument
is the name of the defined schema. Mongoose.model() tells Mongoose to use the default
Mongoose connection for the model being created.
Let's look at some exercises so that we have a better understanding of how a database
schema and model is created using Mongoose.
58 | Developing RESTful APIs to Perform CRUD Operations
Note
The code for the model.js file can be found here: http://bit.ly/2SjFDwM.
1. Create a file named model.js inside the model folder using the following code:
touch model.js
2. Load the Mongoose module using the following code:
const Mongoose = require("Mongoose");
3. Declare a schema and assign the Schema class using the following code:
const Schema = Mongoose.Schema;
4. Create a Schema instance and add properties (assuming we're creating for a simple
blog) using the following code:
const BlogSchema = new Schema({
title: {
type: String,
required: true
},
body: String,
createdOn: {
type: Date,
default: Date.now
}
});
Getting Started with MongoDB Atlas | 59
5. Call Mongoose on the model() function to use the default Mongoose connection:
Mongoose.model();
6. Pass in the model name, ArticleModel, as the first argument using the
following code:
Mongoose.model("ArticleModel");
7. Pass in the schema name BlogSchema as the second argument using the following
code:
Mongoose.model("ArticleModel", BlogSchema);
8. Make the model exportable using the following code:
module.exports = Mongoose.model("ArticleModel", BlogSchema);
9. Create an output.js file and import the model using the following code:
const Article = require("../models/model");
if(Article){
console.log('Model Successfully Imported');
}
10. Run the output.js file on the command line using the following code:
node output
You will obtain the following output:
Note
The code files for this activity can be found here: http://bit.ly/2tuWYsN.
1. Create a subfolder named config and create a db.js file inside it.
2. Install and import Mongoose in the db.js file.
3. Assign a MongoDB connection string to uri and declare the options settings.
4. Connect the application to MongoDB Atlas.
5. Create the api/models folders and then create an Article.js file inside models.
6. Declare the schema and assign a schema class in the Article.js file.
Note
You will need to install and import Mongoose in the Article.js file once more.
Note
The solution to this activity can be found on page 254.
Getting Started with Express | 61
The previous sections described how to get started with MongoDB Atlas, a database as
a service offered by MongoDB. We went through in topics and exercises that taught us
about connecting an application to clusters, creating schemas, defining a schema with
native and third-party MongoDB drivers, and creating models.
In the next section, we will learn about how to get started with Express, an open source
web application framework for Node that's used for building web applications and APIs.
We will wrap up the section by understanding the methods for routing HTTP requests,
configuring middleware, error handling, rendering HTML views, and registering a
template engine.
// route definition
app.get('/', function (req, res) {
res.send('Hello World');
});
// Start server
app.listen(4000, function () {
62 | Developing RESTful APIs to Perform CRUD Operations
Routing in Express
Routing involves matching a URL in response to an action by a web API. Particular
patterns of characters in a URL are matched, and some values that are extracted from
the URL are passed as parameters to the route handler, as shown in the following
snippet:
app.get('/', function(req, res){
res.send('Hello World!');
});
In the preceding code, app.get() defines the HTTP GET requests to the site
root (/).
Some of the other methods for defining route handlers for all the other HTTP verbs
provided by the Express application, which are mostly used in exactly the same way, are
as follows: post(), put(), delete(), subscribe(), unsubscribe(), patch(), search(), and
connect().
Middleware functions can be loaded on a particular path for all request methods using
app.all(), which is a multipurpose Express route handler. It is also a special routing
method that can be called in response to any HTTP method, as shown in the following
snippet:
app.all('/about', function(req, res, next)
{
console.log('welcome to about page ...');
next(); // next handler takes control
});
Getting Started with Express | 63
Route handlers can also be used to group a particular part of a site together so that
a common route-prefix is used to access them. This is made possible by the Express
router object. In the next exercise, we will create a route and export the router object.
Note
The code files for this exercise can be found here: http://bit.ly/2BLDeFU.
if(routes){
console.log('Routes Successfully Imported');
}
64 | Developing RESTful APIs to Perform CRUD Operations
Note that we are importing the exported route to verify whether the export has
been successful.
7. Run the test.js file using the following code:
node test
You will obtain the following output:
If the export hadn't been successful, then we would not have obtained the
preceding output.
Before we move on to the next section, you should be aware that Express routing
templates can either be one-to-one or one-to-many, as listed here:
• One-to-one implementation: app.get('/users/:id?', function (req, res, next)
{ //[...]}
• One-to-many implementation: '/:controller/:action/:id
Note
The code files for this activity can be found here: http://bit.ly/2BMvLpZ.
Getting Started with Express | 65
Note
The solution for this activity can be found on page 257.
Middleware in Express
Express is a routing and middleware web framework that has minimal functionality of
its own. Alternatively, an Express application can be said to be a series of middleware
function calls, as listed here:
function myFunMiddleware(request, response, next){
// Do stuff with the request and response
// When done, Call next() to defer to the next middleware
next();
}
66 | Developing RESTful APIs to Perform CRUD Operations
myFunMiddleware() takes three arguments: a request object (req), a response object (res),
and next:
Third-party middleware can also be added by calling use() on the Express application
object. For example, body-parser is a middleware that extracts the body of an incoming
request and exposes it on res.body and parses the JSON, buffer, string, and URL
encoded data that's submitted using a HTTP POST request. This implementation is
shown in the following snippet:
const express = require("express");
const bodyParser = require("body-parser");
It is strictly recommended that err is processed before res to avoid the default error
handler closing the connection and failing the request in a situation where an error is
discovered.
The following snippet shows how a template engine is used in an Express application:
var express = require('express');
var app = express();
//set view engine
app.set("view engine","pug")
//route
app.get('/', function (req, res) {
res.render('home');
The following are some of the template engines that work with Express:
• Haml: This is a markup language that cleanly and simply describes the HTML of
any web document without the use of inline code
• EJS: This is the embedded JavaScript template engine
• Pug: This was formerly known as Jade and it's an Haml-inspired template engine
• hbs: This is an extension of the Mustache.js template engine that works as an
adapter for Handlebars.js
• React: Renders React components on a server
• h4e: This is an adapter for Hogan.js with support for partials and layouts
As highlighted in the preceding list, Express can be used with any template engine. In
the following exercise, we will look at how to use Pug (Jade) templates to create HTML
pages dynamically and implement error handling in Express.
Exercise 9: Using the Pug (Jade) Template Engine and Implementing Error
Handling in the Node Application Using Express
In this exercise, we will use the Pug template to implement error handling in a Node
application using Express. Create a Pug-template folder and install the express module
using the command npm install express. Perform the following steps to complete this
exercise:
Note
The code file for this exercise can be found here: http://bit.ly/2SfZCN9.
Getting Started with Express | 69
// initialize todoData
var todoData = [
{id:1, task:'Take Breakfast'},
{id:2, task:'Read Book'},
{id:3, task:'Take Launch'},
{id:4, task:'Start Meeting'},
];
});
4. Update the index.pug file to list out todoData by using the following code:
doctype html
html
head
title Pug Todo List Page
body
h1 This page is produced by Pug engine to display todo List
p Todo List
ul
each list in TodoList
li=list.task
5. To use custom error handling middleware to handle errors, we use the main
middleware before the route:
app.use(function(err, req, res, next) {
// Do logging and user-friendly error message display
console.error(err);
// Using template engine
res.render('500');
});
6. Run the following command on the command line:
node server
7. Open the browser and type localhost:8000 in the address bar. You will obtain the
following output:
Note
To learn more about Pug syntax rules in detail, refer to this link: https://pugjs.org/
api/getting-started.html.
Getting Started with Express | 71
Note:
The code files for this activity can be found here: http://bit.ly/2NiTZNl.
Note
The solution to this activity can be found on page 259.
Summary
This chapter got us started on the introduction to RESTful APIs and their design
concepts. First, we defined what an API is and then expanded on REST. Thereafter, we
looked into design practices that have been employed by established companies and
described the guiding framework for web standards.
In the subsequent sections, we looked into how MongoDB Atlas is implemented with
Node. We also performed an exercise on creating MongoDB Atlas server components,
known as clusters, and connected them with applications. Later in this section,
we established connections with applications using middleware such as the native
MongoDB driver and Mongoose. We also described how schemas are created and
defined with Mongoose. Finally, we performed an exercise on model creation using the
default Mongoose method.
In the final sections of this chapter, we implemented various features in Express with
Node. These included routing HTTP requests, configuring middleware, error handing,
rendering HTML views, and registering a template engine. Finally, we tested a RESTful
API.
In the next chapter, we will begin frontend development with the Angular CLI.
Beginning Frontend
3
Development with
Angular CLI
Learning Objectives
• Describe the consumption of RESTful API services from the client side
This chapter begins with the development of Angular CLI applications and then goes on to
describe components, directives, services, and making HTTP requests in Angular by walking
through several exercises and activities.
76 | Beginning Frontend Development with Angular CLI
Introduction
In the previous chapter, we learned about the RESTful API and its design concepts,
performed operations on MongoDB Atlas, and completed exercises on feature
implementations in the Express framework for Node applications. The chapter
concluded with activities aimed at implementing Node, MongoDB Atlas, and
Express together to develop a RESTful API to perform CRUD operations.
This chapter will get you started on frontend development with the Angular CLI:
a command-line interface developed by Angular to help developers jump-start frontend
development. Later, you will implement features such as components, which are the
basic building blocks of any Angular application. This chapter will describe the different
directives that are available in Angular. Also, you will learn how to consume API services
and create templates, reactive forms, perform routing, and so on.
Finally, you shall be putting together all the knowledge you gained in this chapter to
develop a robust web application with a solid Angular frontend. The chapter activity will
wrap up the development for our sample application by consuming the RESTful API we
created in the previous chapter from the client side.
You will see the following output by running the preceding command:
Now that Angular CLI has been installed, we will begin creating Angular applications.
78 | Beginning Frontend Development with Angular CLI
Exercise 10: Creating and Running a New Application on the Angular CLI
The Angular CLI has made application development very easy as CLI commands
help generate all the required files to build a basic web app. In this exercise, we will
be creating and running an application using the Angular CLI. Open the CLI in the
chapter-3-exercises directory and perform the following steps to create or generate a
new default Angular application project:
Note
The code files for this exercise can be found here: http://bit.ly/2TdtIFg.
1. Create or generate a new Angular CLI application from the command line using
the following command:
ng new my-new-app
Note that the necessary packages can also be installed as needed, using the ng add
<package-name> command. However, for now, we will only use the default packages
that have been auto generated by Angular CLI, as shown in the following output:
Now that the application has been created, the next thing we need to do is run the
application on the server.
Getting Started with Angular CLI | 79
2. Serve or run the Angular CLI application by running the following command in the
project directory:
cd my-new-app
ng serve -open
Note that ng serve builds the application and starts a web server.
The serve property is the architect target, whereas @angular-devkit/build-
angular:dev-server is the prebuilt Angular application builder. On top of the
Angular builder, we can run the targets of a specific project directly using ng run
so that serving a target is performed by running ng run angular-cli-workspace-
example:serve.
3. Run the following command in the CLI to open the default browser:
ng serve -open
–open or –o is a command to automatically open default browsers on http://
localhost:4200/ as soon as the application starts running, as shown in the
following screenshot:
Note
The Angular QuickStart method is deprecated. Therefore, the officially
recommended method for getting started with Angular is using the Angular CLI. If
you want to know more about Angular QuickStart, refer to this link: https://github.
com/angular/quickstart.
80 | Beginning Frontend Development with Angular CLI
Angular Components
Everything in Angular is developed as a component; classes interact with different files
that are embedded in components, which form a browser display. It can also be referred
to as a kind of a directive with configuration that's suitable for an application structure
that is component-based.
The architecture of an Angular application is a tree of components originating from one
root component configured in the bootstrap property on your root NgModule (in the app.
module.ts file).
Let's look at the file structure of an Angular root component that was created by default
in the process of bootstrapping a new Angular application:
• app.component.css: This is the style sheet for the component
• app.component.html: This is the HTML template file for the component
• app.component.spec.ts: This is the testing file for the component
• app.component.ts: This is the TypeScript class for the component
• app.module.ts: This is the TypeScript module file for the application
The details of these component files will be discussed later in this section. Now that we
have discussed Angular components briefly, we will perform an exercise on creating
them.
Using Components, Directives, Services, and Making HTTP Requests in Angular | 83
Note
The code files for this exercise can be found here: http://bit.ly/2VbHfui.
1. Import the component from the Angular core library into the hello-world.
component.ts file:
import { Component, OnInit } from '@angular/core';
2. Insert a component decorator path in the hello-world.component.ts file that
specifies the metadata of the component class using the following command:
@Component({
selector: 'app-hello-world',
templateUrl: './hello-world.component.html',
styleUrls: ['./hello-world.component.css']
})
The metadata consists of objects with different key-value pairs, such as the
following:
templateUrl: './app.component.html', selector: 'app-root', and a styleUrl:'[]./
app/component.css'
These objects are the major building blocks that need to be defined to successfully
create and present the component to the DOM view. The template that describes
a view or renders a component can be associated with components either directly
with inline code, template: '<div> {{title}} </div>', or with a reference:
templateUrl: './hello-world.component.html'.
84 | Beginning Frontend Development with Angular CLI
3. Create an exportable component class and assign a string 'Hello World' to title
using the following code:
export class HelloWorldComponent implements OnInit {
title='Hello World'
constructor() { }
ngOnInit() {
}
}
This class holds all the variables and methods being used by the components. The
component can either be referred to from another component or reused by other
components.
4. Update the app.module.ts file using the following code:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
@NgModule({
declarations: [
AppComponent,
HelloWorldComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
5. Update the component template using the following code:
{{title}}
6. Update the root component template by attaching the hello world component
element tag:
<app-hello-world></app-hello-world>
Using Components, Directives, Services, and Making HTTP Requests in Angular | 85
7. Serve the application to view the output by running the following command on the
CLI:
'ng serve -o'
You should obtain an output similar to the following:
As can be seen in the preceding output, we have successfully created our first
Angular component.
Note
Many decorators are allowed in Angular. To get more information about different
component directive options and their descriptions, refer to this link: https://
angular.io/api/core/Component.
So far, we have learned how to start an Angular CLI project and learned how to create
the building blocks of an Angular application, which are components. Next, we'll look
into another concept in Angular, known as a directive.
Directives
A directive is a concept in Angular that allows you to attach attributes and behaviors
to a DOM element. This concept is used to extend the power of HTML by giving it a
new syntax. Each directive has a name, which can either be predefined in Angular, such
as ng-repeat, or a name that's generated by the developer. Each directive determines
where it can be used: in an element, attribute, class, or comment. We will focus more on
predefined directives here. There are three types of directives: component, structural,
and attribute. Let's look at each of these in detail.
86 | Beginning Frontend Development with Angular CLI
Component directive: This is the most commonly used directive, known as a directive
in a template. In this type of directive, you can use other directives, custom or built-in,
in the @Component annotation, as shown here:
@Component({
selector: "my-app"
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
directives: [custom_directive_here]
})
use this directive in your view as:
<my-app></my-app>
From the previous snippet, the objects we have inside the @Components decorators are
known as component directives and can be explained further. selector: "string"
identifies and instantiates a directive in a template. directives: [custom_directive_
here] defines a set of directive providers in the format, as follows:
• Inputs: This defines a set of data-bound input properties: inputs: ['string'],
• Outputs: This defines a set of event-bound output properties: outputs:
['string'],
Other directives are providers, exportAs, queries, jit, and host. Even though we have
already made use of component directives in previous sections, we will be covering
some other directives in detail in the following sections and exercises.
Structural directive: Structural directives are responsible for providing a DOM
structure and for reshaping the HTML layout by adding or manipulating elements. Most
structural directives are easily recognized by the * sign that precedes the directive.
Examples of structural directives are *ngIf, *ngFor, and *ngSwitch (this includes a set of
cooperating directives: NgSwitch, NgSwitchCase, and NgSwitchDefault). You can create a
custom structural directive as well.
Attribute directive: This is a directive that attaches behavior to the DOM element.
Let's quickly run a few exercises to cement our understanding of how structural and
attribute directives are implemented.
Using Components, Directives, Services, and Making HTTP Requests in Angular | 87
Note
The code files for this exercise can be found here: http://bit.ly/2IBQKSu.
Note
The code files for this exercise can be found here: http://bit.ly/2GExPnW.
public myStyle={
"color":"green"
}
4. Update the root component HTML file with the following command:
<app-attribute-dir></app-attribute-dir>
5. Run the application using the following command:
ng serve -o
Using Components, Directives, Services, and Making HTTP Requests in Angular | 89
In the preceding output, observe that the H1 element is displayed in green and that
the H2 element is hidden.
Services
Services are just JavaScript functions and properties that can be reused by injecting
them across application components. Services can be created either manually or by
using the Angular CLI.
90 | Beginning Frontend Development with Angular CLI
In the case of services, we have used the following pattern in different examples,
when we had to inject services into constructors inside component classes. The steps
involved are as follows:
• Define a service class
• Register with Injector
• Declare services as a dependency in components
Note
Many features of dependency injection in Angular, such as nested service
dependencies and multiple service instances, can be read about and understood
in the Angular documentation: https://angular.io/guide/dependency-injection-in-
action.
Note
The code files for this exercise can be found here: http://bit.ly/2V95rxF.
1. Create a service file, cars.service.ts, in the app directory using the following
command:
touch cars.service.ts
2. Import Injectable and create an injectable decoration with or without metadata
using the following command:
import { Injectable } from '@angular/core';
@Injectable()
Using Components, Directives, Services, and Making HTTP Requests in Angular | 91
providers: [CarsService],
92 | Beginning Frontend Development with Angular CLI
For services requesting resources over HTTP servers, we have to use the
HttpClientModule, which is made available by Angular. We will be discussing this in
detail in the next section.
Using Components, Directives, Services, and Making HTTP Requests in Angular | 93
imports: [
HttpClientModule
]
• Inject HttpClientModule inside a service:
import { HttpClient } from '@angular/common/http';
@Injectable()
Error Handling
What happens in a situation where the server couldn't be reached due to network
unavailability or an error? Instead of returning a successful response, HttpClientModule
returns an error object by adding a second callback, as seen in the following snippet:
.subscribe(
(data: Config) => this.config = { ...data}, // success path
error => this.error = error // error path
);
Note
The code files for this activity can be found here: http://bit.ly/2SPq5Gs.
Make sure you have completed all the exercises and activities in both the previous and
current chapter before attempting this activity. Create a Blogging Application project
folder and open it in the IDE (Visual Studio Code). Perform the following steps to
complete the activity:
1. Create a new Angular project, blog.
2. Import the bootstrap theme and its resources from https://goo.gl/VGTYra.
3. Update the automatically created index.html file of the application.
4. Create the header components.
5. Create the title-header component and update the HTML template with the
theme's content.
6. Create the blog-home components.
7. Create the footer components.
8. Update the root component template.
9. Create the view-post components and update the HTML template.
10. Run ng serve -o to start the application.
11. Update the root component template to see the view-post page.
Note
The solution for this activity can be found on page 265.
96 | Beginning Frontend Development with Angular CLI
Note
The code files for this activity can be found here: http://bit.ly/2txtavx.
Note
The solution for this activity can be found on page 271.
Angular Forms
In website development, a form is an essential element of a web application that allows
users to input, validate, and process data before it is used in the program or sent to the
database. Forms are also a very important part of business applications, and they are
used in registering, logging in, submitting requests, and are used to perform countless
data entry tasks. A good data entry experience makes it easy for users to effectively and
efficiently perform tasks that involve data collection and manipulation.
A form is also a part of the HTML element group that contains numerous HTML
element fields or spaces to enter data. Each field holds a field label, and the label gives
the user an idea of the content that's contained in the field view area. For Angular, there
are many built-in directives that enhance form elements and templates. These not only
extend the primary function of a native HTML form template/element, but also make
form implementation, usage, and validation very easy. For example, it saves the builder
the time of writing several click or event-driven functions to validate form inputs and
so on. There are two types of Angular forms:
• Template-driven
• Model-driven
Template-Driven Forms
In a template-driven form, just as the name implies, most of the scripting is done in
an HTML template. To enable template-driven forms, we need to explicitly import a
module called FormsModule in our application and bootstrap the application dynamically
because they are not available by default. For a better understanding, let's dive into
creating a form application in a template-driven way.
Note
The code files for this exercise can be found here: http://bit.ly/2XfLcQM.
1. Create a new Angular project using the CLI in the chapter-3-exercises folder by
using the following command:
ng new packt-course-form
2. Create a model using the ng generate class students command and ensure that
the model has a structure similar to the following snippet:
src/app/students.ts
constructor(
public id: number,
public name: string,
public courseTitle: string,
public duration?: string
) { }
}
3. Using the ng generate component packt-form/PacktStudentForm Angular CLI
command, generate a new component named PacktStudentForm and implement
the following:
src/app/packt-form/packt-student-form.component.ts
import { Component } from '@angular/core';
import { Students } from '../../students';
Using Components, Directives, Services, and Making HTTP Requests in Angular | 99
@Component({
selector: 'app-packt-student-form',
templateUrl: './packt-student-form.component.html',
styleUrls: ['./packt-student-form.component.css']
})
export class PacktStudentFormComponent {
submitted = false;
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
PacktStudentFormComponent
100 | Beginning Frontend Development with Angular CLI
],
providers: [],
bootstrap: [ AppComponent ]
})
export class AppModule { }
5. Update the template file (packt-student-template-form.component.html) by hosting
the form component, as shown here:
<div class="container">
<div [hidden]="submitted">
<h1>Packt Course Form</h1>
<form (ngSubmit)="onSubmit()" #studentForm="ngForm">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name"
#name="ngModel">
<div [hidden]="name.valid || name.pristine" class="alert
alert-danger">
//[…]
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Course Duration</div>
<div class="col-xs-9 pull-left">{{ model.duration }}</div>
</div>
<div class="row">
<div class="col-xs-3">Packt Course Title</div>
<div class="col-xs-9 pull-left">{{ model.courseTitle }}</div>
</div>
<br>
<button class="btn btn-primary" (click)="submitted=false">Edit</
button>
</div>
</div>
Using Components, Directives, Services, and Making HTTP Requests in Angular | 101
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
// style.css
/* You can add global styles to this file, and also import other style files
*/
@import url('https://unpkg.com/[email protected]/dist/css/bootstrap.min.
css');
7. Update the root template file (app.component html) by hosting the form component
src/app/app.component.html, as shown here:
src/app/app.component.html
< app-packt-student-template-form ></ app-packt-student-template-form >
8. Launch the application by running the following snippet in the command-line
terminal and go to the address localhost:4200:
ng serve --open
102 | Beginning Frontend Development with Angular CLI
Now that we have working code, let's break it down to see how the various features are
implemented.
Two-way Data Binding
In the previous exercise, when you run the app, you discover that values have already
been input into the field. Two-way data binding in Angular using [(NgModel)] makes it
possible to display, listen, and update/extract input variable values at the same time.
The [NgModel] syntax is used to bind a form to a model, as shown in the following
snippet:
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name">
<div class="col-xs-9 pull-left">{{ model.name }}</div>
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name" #name="ngModel">
The form input interface is as follows:
Also, we can see how ng-pristine is implemented in the following snippet to bind a
hidden directive to a div:
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" required
[(ngModel)]="model.name" name="name" #name="ngModel">
<div [hidden]="name.valid || name.pristine" class="alert
alert-danger">
Name is required
</div>
</div>
</select>
<div [hidden]="courseTitle.valid || courseTitle.pristine"
class="alert alert-danger">
courseTitle is required
</div>
</div>
You will obtain the following output when the preceding snippet is run:
Now that we've been introduced to the structure of what an Angular application entails
in the preceding section, we will be delving into those features that make Angular great,
as highlighted in the first chapter.
This section will briefly describe how to create, update, and implement a simple and
advanced form control; we will also look into how reactive form values are validated.
Reactive/Model-Driven Forms
When it comes to handling forms whose values change over time, a model-driven
approach or reactive form is used. Such forms are built around observable streams, in
which input values are accessed synchronously. They are highly testable because data
requests have a high probability of being predictable and consistent.
To have a better understanding, let's dive into creating a form application in the
reactive/model-driven format.
106 | Beginning Frontend Development with Angular CLI
Note
The code files for this exercise can be found here: http://bit.ly/2GBuPce.
Using Validators
Pre-built validators are already provided in the Angular Validators module to match
the ones we can define via standard HTML5 attributes. These validators are
required: minlegth, maxlength, and pattern. The implementations are as shown here:
import { FormGroup, FormControl, Validators } from '@angular/forms':
myform = new FormGroup({
name: new FormGroup({
firstName: new FormControl('', Validators.required),
lastName: new FormControl('', Validators.required),
}),
email: new FormControl('', [
Validators.required,
Validators.pattern("[^ @]*@[^ @]*")
]),
password: new FormControl('', [
Validators.minLength(8),
Validators.required
]),
language: new FormControl()
});
}
Rendering Form Controls with Styling
This method works the same as that for the template-driven form. We have already
discussed the various form control instances, which are Dirty/Pristine, Touched/
Untouched, and Valid/Invalid, and these can be implemented as follows:
<div>Valid? {{myform.controls.email.valid}}</div>
y
Using Components, Directives, Services, and Making HTTP Requests in Angular | 109
Routing in Angular
The Angular router makes navigation from one view to another possible in applications.
It adopts the browser navigation model to interpret a URL as an instruction to a client-
generated view. Parameters can be passed to determine the specific content to present.
Routers can be bound to page links, and the page opens as the user clicks the link.
Activities are also logged to the browser's history to ensure back and forward button
navigation functionalities.
110 | Beginning Frontend Development with Angular CLI
The following snippet shows an array of routes that have been configured for different
view navigation:
const appRoutes: Routes = [
{ path: 'post/:id', component: PostDetailComponent },
{ path: 'posts', component: PostListComponent, data: { title: 'Post List' }
},
{ path: '', redirectTo: '/posts', pathMatch: 'full' },
{ path: '**', component: PageNotFoundComponent } ];
It is important to know that the router uses a first-match-wins strategy to match
routes. So, the order of routers really matters. It should be designed in such a way that
more specific routes should be placed above less specific routes. The following flow of
design is advised:
• Routes with a static path
• Empty path route, for the default route
• Wildcard route
Now, let's talk about the route array defined in the app module. In a route such as
post/: id, the ID is a token for a route parameter. For example, let's say we have a URL
that is /post/20. This means it'll retrieve data for the post whose id = 20, and route to a
different page.
Using Components, Directives, Services, and Making HTTP Requests in Angular | 111
The data property in a route is a place to store arbitrary data associated with the
specific route, and it's accessible within each activated route. You can store variables
such as page title, actions, and so on, and all the data that's stored can be retrieved
dynamically. The empty path, ' ', represents the default path for the application,
from the route defined previously; it means that if the route is empty, redirect to the
post. The ** path is a wildcard. This path is used to display a "404 - Not Found" page
or redirect to another route if the path doesn't match any in the route. There is the
enableTracing option configuration, which can be added as a second argument to the
RouterModule.forRoot() method. This is mainly meant for debugging.
Router outlet
RouterOutlet is a tag that's placed in the host view's HTML. It uses the given
configuration to match the URL to the route, as defined in the route array:
<router-outlet></router-outlet>
Router link
The router link makes navigation between pages possible. For example, let's assume we
have a route whose path is set to about-us and we are currently on the home page of
the application. The router link can be configured as follows:
<nav>
<a routerLink="/home" routerLinkActive="active">Home</a>
<a routerLink="/about-us" routerLinkActive="non-active">About Us</a>
</nav>
<router-outlet></router-outlet>
Don't forget that we are still going to have router-outlet on the view to enable routing
on the HTML view.
The routerLinkActive directive visually distinguishes the anchor for the currently
selected "active" route by adding an active CSS class.
112 | Beginning Frontend Development with Angular CLI
Router State
A tree of ActivatedRoute objects is built after successful navigation; this makes up
the current state of the router, which is known as RouterState. Both router service
properties (ActivatedRoute and RouterState) provide methods that enable up ward and
down ward traversing for information retrieval from the tree elements (parent, child,
and sibling node).
Router Events
These are events that are emitted at every stage of the routing process. Events such
as NavigationStart are triggered when navigation starts. RoutesRecognized is triggered
when the router parses the URL and the routes are recognized. RouteConfigLoadStart
is triggered before the router lazy-loads a route configuration. Other events include
RouteConfigLoadEnd, NavigationEnd, NavigationCancel, and NavigationError. Their
meanings can easily be understood by the event names.
Note
The code files for this activity can be found here: http://bit.ly/2IuqzwN.
1. Create an Angular project with the name reactive-form and open the folder in
Visual Studio Code.
2. Create a component and name it packt-student-reactive-form.
3. Register the reactive forms module in app.module.ts and add it to the import
properties to activate the reactive form.
4. Import the FormControl and FormGroup modules into the reactive form component
(packt-student-reactive-form.component.ts).
Using Components, Directives, Services, and Making HTTP Requests in Angular | 113
Note
The solution for this activity can be found on page 278.
Activity 9: Creating and Validating Different Forms Using the Template and
Reactive-Driven Method
Continuing the frontend development of our blogging application, we will be creating
and validating three different forms (login, register, and create/edit) that we will be
making use of in the application as we progress. These forms will be reactive-driven.
Once finished, our aim is to create reactive-driven forms for user login, and template-
driven forms for user registration and for creating or editing the blog post.
Make sure you have completed all the exercises and activities in both the previous and
current chapter before attempting this activity. Open the Blogging Application project
folder in the IDE (Visual Studio Code) and perform the following steps:
Note
The code files for this activity can be found here: http://bit.ly/2BOfXmO.
114 | Beginning Frontend Development with Angular CLI
Note
The solution for this activity can be found on page 282.
Note
The code files for this activity can be found here: http://bit.ly/2U25Pho.
Using Components, Directives, Services, and Making HTTP Requests in Angular | 115
1. Import RouterModule into the app module file (app.module.ts) and update the
import property.
2. Configure the route in app.module.ts.
3. Add router-outlet to the app.component.html file.
4. Add the router link to the blog-home component.
5. Start the server and serve the Angular application using the CLI.
6. Open the browser and input http://localhost:4200/blogin in the URL address bar
to view the router in action.
Note
The solution for this activity can be found on page 291.
Summary
In this chapter, we presented an introduction to the Angular CLI and highlighted the
main steps involved in the creation of Angular applications. We began by creating and
serving an Angular CLI application and performed exercises on creating and running
new applications.
As the chapter progressed, we introduced using components, directives, and services,
and making HTTP requests in Angular. We began with the building blocks of Angular
applications (components). Thereafter, we described various directives and some of
their implementations. We performed exercises on the creation and implementation of
Angular services and described how Angular handles HTTP requests. The last section
introduced us to Angular forms and routing. We began with the types of forms used in
Angular and performed exercises on routing.
In the next chapter, we will introduce you to Node application security practices and
the different forms of access authentication for Node applications.
The MEAN
4
Stack Security
Learning Objectives
This chapter presents best practices for securing Node applications in addition to several
exercises and activities on creating JSON web tokens, authentication using passport, third-party
authentication, many more GI.
118 | The MEAN Stack Security
Introduction
The previous chapter introduced us to frontend development with Angular. We learned
how to jump-start frontend development using the Angular CLI and gainfully engaged
with exercises and activities that taught us how to implement various features, such as
directives, components, templates, reactive forms, and so on. In Chapter 2, Developing
RESTful APIs to Perform CRUD Operations, we described RESTful APIs and their
design concepts, and also developed an HTTP server for a Node application (backend
development).
In this chapter, we will be continuing with the backend development; however, the
focus will be on securing access to the use of APIs and securing Node applications in
general. This chapter introduces Node application security practices and different
forms of authentication. You will learn how to grant user access to the RESTful API
that we developed in the previous chapter and perform user authentication on Node
applications. This chapter also describes the structure and workings of JSON web
tokens (JWTs). We will also implement token-based authentication using JWT. We
will learn the different means of implementing authentication in Node using strategies
provided by the passport middleware.
We will walk through exercises aimed at implementing different forms of authentication
(Facebook, Twitter). Finally, the chapter will end with us performing activities that
will take our blog application to the next level by securing the RESTful API and
distinguishing guest and admin access.
• Security.txt
• Session management
• Cross-Site Request Forgery (CSRF)
Note
There are more headers supported by Helmet. Refer to the following document for
a complete list: https://helmetjs.github.io/docs/.
Helmet also prevents Node applications from common attacking techniques such as the
following:
• SQL Injection: Whereby a partial or complete SQL query is injected via user input.
It is advisable to use parameterized queries or prepared statements to guard
against this kind of attack.
• Command injection: Whereby an OS command is run on the remote web server to
obtain or steal objects or resources such as passwords.
120 | The MEAN Stack Security
Implementing Helmet
It is a best practice to use Helmet at an early stage in our middleware stack. This
gives us the assurance that the HTTP headers are set. The following snippet shows
how Helmet is implemented after being installed using the npm install helmet –save
command:
const express = require('express')
const helmet = require('helmet')
const app = express()
app.use(helmet())
app.use(helmet.noCache())
app.use(helmet.frameguard())
Some middleware is normally enabled by default. It can be disabled if need be; for
example, the x-frame option from Helmet, also known as frameguard, can be disabled
using the following command:
app.use(helmet({
frameguard: false
}))
Validating user input
The repercussions of poor input validation can be devastating. Implementing
application security involves validating user inputs from dreadful attacks such as SQL
injection, XSS, or command injection. One of the best libraries that helps to achieve
this is joi. It is used to validate inputs and define schema for JavaScript objects. The joi
library can be implemented with Mongoose as follows:
const Joi = require('joi'),
mongoose = require('mongoose')
const userSchema = mongoose.Schema({
username: String,
password: String,
email: String,
full_name: String,
created: { type: Date, default: Date.now },
});
userSchema.methods.joiValidate = function(obj) {
Node Security and Best Practices | 121
Security.txt
Risk Society was a concept that was developed by independent risk researchers
in the 1980s. This concept lists the book of action that a contemporary society
undertakes in response to various types of risks. In the field of software development,
often, developers may find that they are unable to effectively communicate to other
stakeholders the various risks that might render applications vulnerable. With this in
mind, Security.txt was proposed. Security.txt specifically deals with web development
by proposing standards that enable websites to define security policies. Even though
Security.txt is in a draft stage, its purpose is to help developers disclose security
vulnerabilities.
Note
For more information, please refer to the following web page: https://tools.ietf.org/
html/draft-foudil-securitytxt-00.
Some of the many attributes, each having a different meaning, that can be set for
cookies, are described as follows:
• Path: In addition to the domain, the URL path that the cookie is valid for can
be specified. If the domain and path match, then the cookie will be sent in the
request.
• Expires: This attribute is used to set persistent cookies, since the cookie does not
expire until the set date is exceeded.
• Secure: Inform the browser to only send the cookie when an HTTPS request is
being sent.
• httpOnly: Cookies are not allowed to be accessed via JavaScript, thereby
preventing an attack.
• Domain: This attribute is used to compare against the domain of the server in which
the URL is being requested. If the domain matches or if it is a sub-domain, then
the path attribute will be checked next.
In Node, there is a cookie module available for creating cookies. Also available is
a wrapper-like cookie-session that can be implemented as follows:
var cookieSession = require('cookie-session');
var express = require('express');
COOKIE_KEY1,
COOKIE_KEY2
],
cookie: {
secure: true,
httpOnly: true,
domain: 'siteexample.com',
path: 'foo/bar',
expires: expiryDate
}
}));
124 | The MEAN Stack Security
app.listen(3000);
The fact that a user has a username and password for an account does not mean
authenticity is guaranteed. We say the user has been authenticated only when access
based on those (username and password) user credentials is provided.
126 | The MEAN Stack Security
There are three factors that ensure authentication: the user's knowledge, ownership,
and inheritance. These can be better explained as something that the user knows
(that is, the user's password, their pattern, security questions answered, and so on),
something the user has (ID card, security token, software token, biometrics, and so on),
and something the user possesses (fingerprint, retina, and so on).
These authentication factors can be combined to reinforce the security level of online
users into the following different authentication types:
• Single-factor authentication: One single component from any of the three
categories of authentication is used to authenticate an individual's identity. This
is the weakest type of authentication and is not recommended for financial or
transactional services that warrant a high level of security.
• Two-factor authentication: Two components of authentication are used to
authenticate an individual's identity. For example, in financial transactions, debit
card information can serve as the ownership component while the PIN can serve
as the knowledge component.
• Three-factor authentication: All components of authentication are employed.
For example, entering a PIN (knowledge component) to unlock the user's phone
(ownership) and then supplying an iris scan (inheritance) to finalize authentication.
This is the strongest of the three types.
Authorization
Authorization is not authentication. Recall that authentication is the process of
verifying that someone is who they claim to be. Authorization means you are permitted
to do what you are trying to do. This does not mean authorization presupposes
authentication. A guest could be authorized to a limited action set. For example, if a
user wants to perform a mobile transfer from an application, after being successfully
logged in (authenticated), the user then proceeds to transfer funds, and knowing that
there is a balance of USD 10,000, the user still intends to transfer USD 20,000. The
transaction will not go through because it won't be permitted (authorized), since the
account balance is less than what the user was trying to transfer.
Authentication simply means the action of proving or showing that something is true
or valid. So, we can say that user authentication is the action of validating a user, while
authorization is permitting or granting the user access to web resources, features, or
pages. In the following section, we will be looking at how a user can be authenticated
and authorized using a JSON Web Token.
Header
The header is declared in the form of an object that consists of two properties: type
declaration and the hashing algorithm. Object declaration can be seen in the following
snippet:
{
"typ": "JWT",
"alg": "HS256"
}
The typ property will always be JWT, while the alg property could take any preferred
hashing algorithm (HMAC SHA256 is preferred in this case). Once these header objects are
base64 encoded, the output becomes the first part of JWT, as shown in the following
code:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
128 | The MEAN Stack Security
Payload
A payload is a JWT object and is known as a claim, where information about the token
with information to be transmitted is held. The object also gives room for multiple
claims, and this includes the following:
1. Registered claim names: These reserved names are claims that are not mandatory
and are of the following types:
iss: This holds the issuer of the token
sub: This holds the subject of the token
aud: This holds the information about the audience of the token
exp: This defines the expiration (after the current date/time) in NumericDate value
nbf: This defines the time before which the JWT should not be accepted for
processing
iat: This defines the time at which the JWT is issued
jti: This is a unique identifier that is used to prevent the replay of JWT and is
helpful for a one-time-use token
2. Public claim names: These are user-created or defined claims such as username,
information, and so on.
3. Private claim names: These claims are defined based on the agreement between
the consumer and the producer. It is advisable to use private claims with caution
because they are subject to name collision (name clashing).
Let's consider an example payload that has two registered claims (iss and exp) and two
public claims (author and company). The resulting snippet would be as follows:
{
"iss": "meanstack courseware",
"exp": 2000000,
"author": "Paul Oluyege",
"company": "Packt"
}
After going through base64 encoding, the preceding snippet will result in the following:
eyJpc3MiOiJtZWFuc3RhY2sgY291cnNld2FyZSIsImV4cCI6MjAwMDAwM
CwiYXV0aG9yIjoiUGF1bCBPbHV5ZWdlIiwiY29tcGFueSI6InBhY2t0In0
Node Application Authentication with JWTs | 129
Now, after putting all this (header, payload and signature) together, we have our full
JSON Web Token, as shown in the following snippet:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJtZWFuc3RhY2sgY291cnNld2FyZSIsImV4cCI6MjAwMDAwMCwiYXV0a
G9yIjoiUGF1bCBPbHV5ZWdlIiwiY29tcGFueSI6InBhY2t0In0.14VeadfCPLa_
XR2Swfu8WI8ZvnU03lnlQN7P1Rea2mk
The following diagram summarizes the structure of a JWT:
Note
The code files for this exercise can be found here: http://bit.ly/2Is9rYA.
We will create the database connection in the next few steps. Install mongoose
from the CLI using the following code:
npm install mongoose
7. Export the mongoose module using the following code:
const mongoose = require("mongoose");
8. Declare and assign values to the mongodb atlas connection parameter, as shown
here:
var uri = "mongodb+srv://username:[email protected]/
test?retryWrites=true";
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
9. Connect to the database by typing in the following code:
mongoose.connect(uri, options).then(
() => {
console.log("Database connection established!");
},
err => {
console.log("Error connecting Database instance due to: ", err);
}
);
10. Create an api directory using the following code:
mkdir api
11. Create three subfolders named controllers, models, and routes in the api
directory using the following code:
mkdir – controllers && mkdir – models && mkdir – routes
12. Create the userModel.js model file inside the model directory using the following
code:
-- touch userModel.js,
13. Install bcryptjs from the CLI using the following code:
npm install bcryptjs
Node Application Authentication with JWTs | 133
19. Create controller file called userController.js inside the controller directory
using the following code:
-- touch userController.js ,
20. Import mongoose, jsonweboken, and bcryptjs using the following code:
const mongoose = require('mongoose'),
jwt = require('jsonwebtoken'),
bcrypt = require('bcryptjs'),
Import user model
const User = mongoose.model('User');
21. Create controller logic for registering new users with the following code:
exports.register = (req, res) => {
let newUser = new User(req.body); // a new user as an instance of the
User model
newUser.hash_password = bcrypt.hashSync(req.body.password, 10); //
Hash the password using bcrypt.hashSync function
if (err) {
res.status(500).send({ message: err });
}
user.hash_password = undefined;
res.status(201).json(user);
});
};
22. Create controller logic to sign users in with the following code:
exports.signIn = (req, res) => {
User.findOne({// User findOne method on User to check if user email
supplied exist
email: req.body.email
}, (err, user) => {
if (err) throw err; // return err if query was not successful,
if (!user) {
res.status(401).json({ message: 'User not found.
Authentication unsuccessful. ' });// if user not found, return a message
} else if (user) {
Node Application Authentication with JWTs | 135
if (!user.comparePassword(req.body.password)) {
res.status(401).json({ message: . Wrong password.
'Authentication unsuccessful ' });
// if password is worng, return a message
} else {
res.json({ token: jwt.sign({ email: user.email, fullName:
user.fullName, _id: user._id }, 'RESTFULAPIs') }); // if user is found,
return a token that is created using user details and some properties
}
}
});
};
23. Create a controller logic to verify whether a user is logged in using the following
code:
exports.loginRequired = (req, res, next) => {
if (req.user) { // Check if user already exist
next(); // If user exist, call next() function
27. Create a route and pass userHandler using the following code:
module.exports = function(app) {
var userHandler = require('../controllers/userController');
app
.route("/auth/register")
.post(userHandler.register);
app
.route("/auth/sign_in")
.post(userHandler.signIn);
app
.route("/")
.get(userHandler.loginRequired);
};
28. Import userModel, jsonwebtoken, and database into server.js using the following
code:
require("./config/db");
var User = require('./api/models/userModel'),
jsonwebtoken = require("jsonwebtoken");
29. Add Express middleware before the route using the following code:
app.use((req, res, next) => {
if (req.headers && req.headers.authorization && req.headers.
authorization.split('')[0] === 'JWT') {
jsonwebtoken.verify(req.headers.authorization.split('')[1],
'RESTfulAPIs', (err, decode) => {
if (err) req.user = undefined;
req.user = decode;
next();
});
} else {
req.user = undefined;
next();
}
});
Node Application Authentication with JWTs | 137
Test for (/) login with the required route by typing http:localhost:4000/ on the
address bar. You will obtain the following output:
Note
The code files for this activity can be found here: http://bit.ly/2IBLo9Q
1. Create an admin user model by creating a file named userModel.js inside the api
folder.
2. Create an admin user controller.
3. Update the route file.
4. Update the server.js file.
5. Run the server using node server on the CLI and open Postman for testing.
6. Register a new user on localhost:3000/auth/register.
140 | The MEAN Stack Security
Note
The solution for this exercise can be found on page 295.
Passport's Features
The following are some of the features of passport:
• Over 500+ authentication strategies are available to pick and use, which makes it
possible to implement custom strategies.
• It features single sign-on with OpenID, which is a safe, fast, and an easy way to
log in to web sites. Also, OAuth provides a simple way to publish and interact with
protected data. It's also a safe and secure way of providing access.
• It allows you to easily handle success and failure using flash messages to display
status information to the user.
• It supports persistent sessions; that is, the client can still access the
backend server for information as long as the client is still in the session window
or period.
• It has dynamic scope and permissions.
• It does not mount routes in the application.
• It has a lightweight code base.
In a situation where the credentials are not valid, done will be invoked as false and
an additional message can also be supplied to specify the reason for the failure. For
example, if the password is not correct, passport will invoke done as false, as seen in the
following snippet:
return done(null, false, { message: 'password not correct!.' });
Node Application Authentication with Passport | 143
Finally, if there is an exception, that is, a database connection could not be established,
err will be invoked, as shown here:
return done(err);
This approach makes passport database agnostic; that is, the application can
choose where to store information without relying solely on the assumptions
imposed by the authentication layer. In LocalStrategy, the login credentials are
username and password by default; however, if you wish to change these, provisions
have been made, as seen in the following snippet:
passport.use(new LocalStrategy({
usernameField: 'email',
passwordField: 'passwd'
},
function(username, password, done) {
// ...
}
));
Implementing OAuth
OAuth, a delegated authentication mechanism that we briefly described earlier is a
standard protocol that guides and allows authorization for APIs to access web, desktop,
or mobile applications. OAuth can be implemented using the generic OAuth (OAuth
1.0 and OAuth 2.0) authentication format, which gives room to specify parameters
based on the service provider in use. For example, a Facebook authentication can be
implemented using OAuth, as seen in the following snippet:
npm install passport-oauth
var passport = require('passport')
, OAuthStrategy = require('passport-oauth').OAuthStrategy;
passport.use('provider', new OAuthStrategy({
requestTokenURL: 'https://www.provider.com/oauth/request_token',
accessTokenURL: 'https://www.provider.com/oauth/access_token',
userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
consumerKey: '123-456-789',
consumerSecret: 'shhh-its-a-secret'
callbackURL: 'https://www.example.com/auth/provider/callback'
},
144 | The MEAN Stack Security
From the callback in the preceding snippet, we can see that the arguments that
are accepted are token, tokenSecret, profile (these are the access tokens), and
their corresponding secret, along with user profile information provided by the
service provider. Passport normalizes profile information for easy implementation;
this is because each service provider tends to have different ways of encoding this
information.
Note
Go to http://www.passportjs.org/docs/profile/ to find out more about the common
fields that are available in the normalized profile argument.
Note that custom callbacks are used to handle success or failure if the built-in options
are not sufficient for handling an authentication request. In the following snippet,
authenticate() was not used as route middleware; it was called from within the route
handler, thereby giving the callback access to the req and res objects through closure:
app.get('/login', function (req, res, next) {
passport.authenticate('local', function (err, user, info) {
if (err) { return next(err); }
if (!user) { return res.redirect('/login') }
req.console.logIn(user, function (err) {
if (err) { return next(err); }
return res.redirect('/users/' + user.username);
});
})(req, res, next);
});
app.use(express.static("public"));
app.use(session({ secret: "cats" }));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(passport.initialize());
app.use(passport.session());
Sessions
For applications that use sessions, on first authentication, the credentials will be sent
to the users' browsers and a session will be established and maintained via a cookie
that's set in the user's browser, for subsequent requests, and as long as the session is
still valid, the request will not contain credentials, but rather the unique cookie that
identifies the session. Passport support for login sessions is obtained by serializing and
de-serializing user instances to and from the session as shown in the following:
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
As seen in the preceding snippet, the amount of data stored within the session is small
because only the user ID is serialized to the session, Also, when subsequent requests
are received, this ID is used to find the user, which will be restored to req.user.
The application supplies serialization and deserialization logic and also chooses an
appropriate database and/or object mapper to avoid application layer imposition.
Node Application Authentication with Passport | 147
Note
The code files for this exercise can be found here: http://bit.ly/2SigWkq.
7. Assign values to the mongodb atlas connection parameter using the following code:
var uri = "mongodb+srv://username:[email protected]/
test?retryWrites=true";
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
8. Connect to the database using the following code:
mongoose.connect(uri, options).then(
() => {
console.log("Database connection established!");
},
err => {
console.log("Error connecting Database instance due to: ", err);
}
);
9. Create an api directory and then create three sub-folders called controllers,
models, and routes using the following code:
mkdir api
mkdir controllers && mkdir models && mkdir routes
10. Create the userModel.js model file inside the controller directory using the
following command:
touch userModel.js
11. Export bryptjs and mongoose using the following code:
const mongoose = require("mongoose"),
bcrypt = require('bcryptjs'),
12. Declare a Mongoose.schema using the following code:
const Schema = mongoose.Schema;
13. Create a UserSchema using the following code:
const UserSchema = new Schema({
fullName: {
type: String,
trim: true,
required: true
},
Node Application Authentication with Passport | 149
email: {
type:String,
unique:true,
lowercase:true,
trim:true,
required:true
} ,
hash_password: {
type:String,
required:true
},
createdOn: {
type: Date,
default: Date.now
}
});
14. Create a method to check the password's validity using the following code:
UserSchema.methods.comparePassword = function(password){
return bcrypt.compareSync(password, this.hash_password);
}
15. Create a mongoose.model from the schema using the following code:
mongoose.model("User", UserSchema);
16. Create a passport file called passport.js inside the config directory using the
following code:
touch passport.js
17. Import mongoose, passport-local, and bcryptjs:
const bcrypt = require('bcryptjs');
const LocalStrategy = require("passport-local").Strategy;
const mongoose = require('mongoose'),
19. Create an function exposed to our app using module.exports, as shown here:
module.exports = function (passport) {
//serialize the user for the session
passport.serializeUser(function (user, done) {
done(null, user.id);
});
20. Deserialize the user using the following code:
passport.deserializeUser(function (id, done) {
User.findById(id, function (err, user) {
done(err, user);
});
});
21. Create a local strategy authentication for signup using the following code:
});
}));
22. Create a local strategy function for login using the following code:
passport.use('local-login', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true //
},
function (req, email, password, done) {
User.findOne({
email: email
}, function (err, user) {
// This is how you handle error
if (err) return done(err);
// When user is not found
if (!user) return done(null, false, req.
flash('loginMessage', 'No user found.'));
// req.flash is the way to set flashdata using connect-flash
// When password is not correct
if (!user.comparePassword(req.body.password)) return
done(null, false, req.flash('loginMessage', 'Oops! Wrong password.'));
// When all things are good, we return the user
return done(null, user);
});
})
)
In the following steps, we will hash the password using the bcrypt.hashSync
function:
23. Call the save function and return the responses using the following code:
exports.register = (req, res) => {
let newUser = new User(req.body);
newUser.hash_password = bcrypt.hashSync(req.body.password, 10);
newUser.save((err, user) => {
if (err) {
res.status(500).send({ message: err });
}
user.hash_password = undefined;
res.status(201).json(user);
});
};
152 | The MEAN Stack Security
24. Create a view folder with four embedded JavaScript templates in its directory,
namely, index.ejs, login.ejs, signup.ejs, and profile.ejs. For index.ejs, use the
following snippet:
<!doctype html>
<html>
<head>
<title> Passport Local strategy - Authentication
</title>
//[…]
<a href="/login" class="btn btn-default"><span class="fa
fa-user"></span> Local Login</a>
<a href="/signup" class="btn btn-default"><span class="fa
fa-user"></span> Local Signup</a>
</div>
</div>
</body>
</html>
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/
bootstrap/3.0.2/css/bootstrap.min.css"> <!-- load bootstrap css -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-
awesome/4.0.3/css/font-awesome.min.css"> <!-- load fontawesome -->
<style>
body { padding-top:80px; }
</style>
</head>
<body>
//[…]
</body>
</html>
<!doctype html>
<html>
<head>
<title>Node Authentication</title>
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/
bootstrap/3.0.2/css/bootstrap.min.css">
154 | The MEAN Stack Security
25. Create a route file inside the routes directory and name it route.js using the
following code:
touch route.js
26. Create an exposed function as an exportable module that takes in app and passport
using the following code:
res.redirect('/');
}
require('./config/passport')(passport);
30. Update the API endpoint using the following code:
var routes = require('./api/routes/route'); //importing route
routes(app,passport);
156 | The MEAN Stack Security
Test for signup by typing localhost:4000/signup in the address bar and fill in the
credentials. You will obtain the following output:
Test for login by typing localhost:4000/login in the address bar. You will obtain
the following output:
passport.use(new ProviderStrategy({
clientID: PROVIDER_APP_ID,
clientSecret: PROVIDER_APP_SECRET,
callbackURL: "http://www.example.com/auth/provider/callback"
},
function(accessToken, refreshToken, profile, done) {
User.findOrCreate(..., function(err, user) {
if (err) { return done(err); }
done(null, user);
});
}
));
158 | The MEAN Stack Security
In the preceding snippet, replace Provider with the name of any provider you want
to implement. For example, if you want to use Facebook, the snippet becomes the
following:
FacebookStrategy = require('passport-facebook').Strategy;
passport.use(new FacebookStrategy({
clientID: FACEBOOK_APP_ID,
clientSecret: FACEBOOK_APP_SECRET,
callbackURL: "http://www.example.com/auth/facebook/callback"
},
Note that the clientID, clientSecret, and callbackURL properties hold the application
connection strings provided by the service provider; these might vary depending on the
provider. The callback function, from line eight, remains the same – the only varying
parameters are the arguments, which might vary depending on the provider.
Note
The code files for this exercise can be found here: http://bit.ly/2T2Zs01.
5. Create a database file named db.js in the config folder directory using the
following code:
touch db.js
6. Export Mongoose using the following code:
const mongoose = require("mongoose");
7. Assign values to the mongodb atlas connection parameter, as shown here:
var uri = "mongodb+srv://username:[email protected]/
test?retryWrites=true";
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
8. Connect to the database using the following code:
mongoose.connect(uri, options).then(
() => {
console.log("Database connection established!");
},
err => {
console.log("Error connecting Database instance due to: ", err);
}
);
9. Create an api directory using the following code:
mkdir api
10. Create three subfolders in the api directory, named controllers, models, and
routes using the following code:
mkdir – controllers && mkdir – models && mkdir – routes
11. Create the model file inside the controller directory named userModel.js:
touch userModel.js
12. Import Mongoose using the following code:
const mongoose = require("mongoose"),
Node Application Authentication with Passport | 161
});
app.get('/auth/facebook/callback',
passport.authenticate('facebook', { failureRedirect: '/error' }),
function(req, res) {
res.redirect('/success');
});
}
23. Update the server.js file, as shown here:
// db instance connection
require("./config/db"); app.get('/success', (req, res) => res.send("You
have successfully logged in"));
app.get('/error', (req, res) => res.send("error logging in"));
24. Import and initialize passport using the following code:
const passport = require("passport");
app.use(passport.initialize());
app.use(passport.session());
require('./config/passport')(passport);
25. Update the API endpoint using the following code:
var routes = require('./api/routes/route'); //importing route
routes(app,passport);
26. Listen to the server using the following code:
app.listen(port, () => {
console.log('Server running at http://localhost:${port}');
});
27. Run the server (node server) and open a browser for testing in the following order:
Test for authorization by typing localhost:4000/auth/facebook in the browser and
fill in the necessary credentials. You will obtain the following output:
Activity 12: Creating a Login Page to Allow Authentication with Twitter Using
Passport Strategies
You have been tasked with creating a login page to allow authentication with Twitter
using passport strategies. Our aim is that, once finished, we should be able to access
a restricted route and have user information stored in the database. Ensure that all
exercises and activities have been completed in this chapter before attempting this
activity. You can use any IDE of your choice. Finally, create a project called Social
Logins before beginning the activity.
Perform the following steps to complete the activity:
Note
The activity requires credentials such as consumerKey and consumerSecret,
which can be obtained by creating an app on twitter. Please refer to the following
documentation to create an app: https://developer.twitter.com. The code files for
this activity can be found here: http://bit.ly/2U0iSQl
9. Create a route file inside the routes directory (using touch route.js) and
then create an exposed function as an exportable module that takes in app and
passport.
10. Update the server.js file.
11. Import and initialize Passport.
12. Update the API endpoints.
13. Run the server (using node server on the CLI), open a browser, and test this by
typing localhost:4000/auth/twitter in the browser address bar.
Note
The solution to this activity can be found on page 302.
Summary
This chapter introduced Node security practices and the different forms of access
authentication for Node applications and APIs. We described different methods for
authenticating and authorizing users to access resources from the frontend and
backend using JWT and passport.
The first section introduced us to Node security and best practices for securing
applications. We also described modules, features, and measures such as Helmet,
input validations, regular expressions, security.txt, session management, and cross-
site request forgery for implementing and ensuring security. That section ended
with an introduction to authorization and authentication. The next section covered
the authentication of Node applications using JWT. JWT's structure was demystified
and explained with comprehensive exercises. The chapter ended with us describing
and implementing passport strategies such as Facebook, local, JWT, and Twitter to
authenticate the security login sessions of Node applications.
In the next chapter, we will be describing declarables, bootstrapping, and modularity in
Angular. Furthermore, we will be creating custom structural and attribute directives, in
addition to bootstrapping an Angular application.
Angular Declarables,
5
Bootstrapping, and
Modularity
Learning Objectives
Introduction
The previous chapter introduced us to Node application security practices and different
forms of authentication for Node applications. We learned, with the aid of exercises and
activities, how to grant user access to a RESTful API and perform user authentication
on Node by implementing token-based authentication using JWT. We also implemented
passport middleware authentication using local and social application strategies.
In this chapter, we will go over an introduction to Angular declarables, which
includes pipes and custom directives, and we'll also describe the concept of
observables. You will learn about various forms of inbuilt pipes, such as Uppercase,
AsyncPipe, percentPipe, and so on. You will create both custom pipes and directives
and describe their usage in the DOM. We'll then describe observables and reactive
extensions (RxJs), and how they are implemented in Angular applications.
Later in this chapter, you'll learn the details of Angular application bootstrapping and
modularity in its code base. You'll also learn how feature modules and lazy-loading
modules are used. At the end of each section, you will be presented with exercises and
activities to ensure a comprehensive understanding of the content.
Angular Pipes
Pipes were initially referred to as a filter in Angular 1. They are used mainly for data
transformation in HTML templates. The purpose of this feature is to reduce the
complexity of tasks such as streaming data over a WebSocket, which involves getting
data, transforming it, and then displaying it to users. Input data is converted into a
desired output through the use of pipes.
For example, if we need a currency output of USD 1,000,000 and we have 1000000 as
the data variable, then how do we achieve our output using the inbuilt pipe for currency
in Angular?
We can achieve this by using a pipe with the | (pipe character) syntax in the template, as
shown in the following snippet:
{{ 1000000 | currency : 'USD' }}
From the preceding code, we can see that the number 1000000 is converted into a
currency string to be displayed in the template. The output is USD 1,000,000.
Using Inbuilt Pipes, Custom Pipes, Custom Directives, and Observables | 169
The output is fine-tuned by providing an optional parameter. The parameter values are
separated with colons in a situation wherein pipe accepts multiple parameters. There
is also a process known as pipe chaining, which provides us with the ability to use two
inbuilt pipes simultaneously, as shown in the following snippet:
{{ 1000000 | currency: 'USD' | lowercase }},
The output will be USD 1,000,000. The types of pipe available in Angular are as follows:
• Inbuilt
• Custom
Inbuilt Pipes
As the name implies, inbuilt pipes are provided by Angular and are all available to be
used in any template without writing any classes or declaring any variables. Inbuilt
pipes include the following:
1. Uppercase Pipe: The uppercase pipe transforms any string into uppercase, as
implemented here:
<p>{{ 'meanstack' | uppercase }}</p> // output : MEANSTACK
2. Currency Pipe: As we discussed previously, it's used to format currencies. The
first argument or parameter denotes the currency type, such as "EUR", "CAD", "
USD," and so on. The implementation is as follows:
{{ 1234.56 | currency:'USD' }} //output : $1,234.56.
If, instead of the abbreviation of USD, we want the currency symbol as output,
a second, or more, parameters can be passed. The following list presents the
available parameter currency pipes:
currencyCode: The input is a string such as a currency code, for example, USD. The
default value is undefined.
display: The input is string or Boolean, which can be any of the following:
code: Shows the code (such as USD)
symbol (default): Shows the symbol (such as $)
symbol-narrow: This is used in cases where there are two symbols for a given
currency.
170 | Angular Declarables, Bootstrapping, and Modularity
5. JSON Pipe: This is used to transform a JavaScript object into a JSON string. The
implementation is as follows:
<p>{{ jsonVal | json }}</p> ,
jsonVal is an object that can be, for example, declared as { a: 1, b: { c: 2 }}.
6. LowerCase Pipe: This pipe is used to transform strings to lowercase, as shown in
the following code:
<p>{{ 'ASIM' | lowercase }}</p> //output: asim
Using Inbuilt Pipes, Custom Pipes, Custom Directives, and Observables | 171
7. Percent Pipe: This pipe is used on numbers and formats them as percentages, as
shown in the following snippet:
<p>{{ 0.123456 | percent }}</p>
<p>{{ 0.123456 | percent: '2.1-2' }}</p>
<p>{{ 0.123456 | percent : "3.4-4" }}</p>
In the preceding snippet, the first argument is in the form of {minIntegerDigits}
{minFractionDigits}-{maxFractionDigits}, and the output is as follows:
8. Slice Pipe: This pipe returns a slice of an array. The first and second arguments are
the first and end indexes, respectively. Negative indexes can be used to indicate an
offset from the end. An example is shown in the following snippet:
<p>{{ [3,1,2,4,5,6] | slice:1:2 }}</p>
<p>{{ [3,1,2,4,5,6] | slice:3 }}</p>
<p>{{ [3,1,2,4,5,6] | slice:3:-1 }}</p>
The preceding snippet will typically generate the following output:
9. Async Pipe: This pipe accepts an observable or a promise and renders the output
without having to call them or subscribe. An async pipe class and template is
shown in the following snippet:
//class
class AsyncPipeComponent {
promiseData: string;
constructor() {
this.getPromise().then(v => this.promiseData = v); (3)
}
//template
{{ promiseData | async }}
172 | Angular Declarables, Bootstrapping, and Modularity
Custom Pipes
In addition to inbuilt pipes, Angular allows you to create and define custom pipes. For
example, if you wanted to create a custom pipe that takes a string and reverses the
order of the letters, you would have to perform the following steps:
1. Create a pipe class file either manually or using the following command-line code:
ng g pipe reverse-str.pipe.ts
2. Import the Pipe and PipeTransform classes from Angular:
import { Pipe, PipeTransform } from '@angular/core';
3. Give the pipe a name in the Pipe decorator:
@Pipe({name: 'reverseStr'})
4. Write an exportable class that implements the PipeTransform class:
export class ReverseStr implements PipeTransform {
transform(value: string): string {
let newStr: string = "";
for (var i = value.length - 1; i >= 0; i--) {
newStr += value.charAt(i);
}
return newStr;
}
}
include the custom pipe as a declaration in your app module
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
//[…]
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
5. Use the custom pipe in the app template
{{ 'inioluwa'| reverseStr }}
Using Inbuilt Pipes, Custom Pipes, Custom Directives, and Observables | 173
Custom Directive
In addition to inbuilt directives, Angular allows you to create and define custom
directives. Therefore, in this section, we will learn how to create both structural and
attribute custom directives. However, before creating them, we will briefly describe
them.
Structural and Attribute Custom Directives
Structural directives are directives that manipulate the DOM structure. These can
create, destroy, or recreate DOM elements based on certain conditions. Attribute
directives manipulate DOM attributes. Typical examples include applying conditional
styles and classes to elements, changing the behavior of an element on the basis of
changing the property, and hiding and showing elements conditionally.
The steps involved in creating and implementing custom attribute directives are
as follows:
1. Call the directive (prefixed with a namespace) and attach it to the HTML element:
Implementation:
<div class="card card-block" cardHover>...</div>
2. Create and import directives. Annotate a class with the @Directive decorator:
Implementation:
import { Directive } from '@angular/core';
..........
@Directive({
selector:"[cardHover]"
})
class CardHoverDirective { }
174 | Angular Declarables, Bootstrapping, and Modularity
Note that the attribute selector is wrapped with [ ], unlike the case of
components, which are wrapped with { }. To associate a selector with any
element, we can write the selector as .cardHover:
Implementation:
import { Directive } from '@angular/core';
........
@Directive({
selector:".cardHover"
})
class CardHoverDirective { }
------------html -------------
<div class="card card-block cardHover">...</div>
3. Wrap the name of the attribute with [ ]; that is, [cardHover], to associate the
directive to an element, which has a certain attribute.
4. Inject an instance of ElementRef into its directive constructor. It's important to
know that ElementRef gives the directive direct access to the DOM element to
which it's attached:
Implementation:
import { ElementRef } from '@angular/core';
class CardHoverDirective {
constructor(private el: ElementRef) {
}
}
To change the background color of our card to gray, we can use the following
code:
el.nativeElement.style.backgroundColor = "gray";
5. Inject Renderer for multiplatform rendering:
Implementation:
import { Renderer } from '@angular/core';
-----
class CardHoverDirective {
constructor(private el: ElementRef,
private renderer: Renderer) {
renderer.setElementStyle(el.nativeElement, 'backgroundColor', 'gray');
}
}
Using Inbuilt Pipes, Custom Pipes, Custom Directives, and Observables | 175
Now that we have learned how to create both custom pipes and directives, we will
be working on some exercises to validate our understanding of this topic in the
next section.
Note
The code files for this exercise can be found here: http://bit.ly/2GEUL6u.
@NgModule({
declarations: [
……………………
IfDirective
],
…………………..
})
export class AppModule { }
4. Import Directive, Input, TemplateRef, and ViewContainerRef from the Angular core
and define the directive decorator using the following code:
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/
core';
We have now created and implemented a structural directive in Angular. In the next
exercise, we will be creating custom attribute directives.
Using Inbuilt Pipes, Custom Pipes, Custom Directives, and Observables | 177
Note
The code files for this exercise can be found here: http://bit.ly/2Iv0NZq.
@NgModule({
declarations: [
……………………
UnderlineDirective
],
…………………..
})
export class AppModule { }
3. Import Directive, Input, ElementRef, and Renderer from the Angular core and
define the @Directive decorator using the following code:
import { Directive, Input, HostListener ElementRef, Renderer } from '@
angular/core';
@Directive({ selector: '[myUnderline]' })
4. Inject ElementRef and Renderer into the constructor, as shown in the following
code:
constructor(public el: ElementRef, public renderer: Renderer) {}
178 | Angular Declarables, Bootstrapping, and Modularity
5. Add Event listeners for the element hosting the directive, as shown in the following
snippet:
@HostListener('mouseenter') onMouseEnter() {
this.hover(true);
}
@HostListener('mouseleave') onMouseLeave() {
this.hover(false);
}
6. Write an event method to be called on mouse enter and on mouse leave events:
hover(shouldUnderline: boolean){
if(shouldUnderline){
this.renderer.setElementStyle(this.el.nativeElement, 'text-
decoration', 'underline');
} else {this.renderer.setElementStyle(this.el.nativeElement,
'text-decoration', 'none');
}
}
7. Call Directive (prefixed with a namespace) and attach it to the app.component.html
element, as shown here:
<p> <span myUnderline>Hover to underline</span> </p>
8. Run ng serve and open a browser on localhost:4200 to view the following output:
Observables
Observables help pass messages between the data creator (publisher) and data
subscriber in an application. They have a significant advantage compared to other
techniques for managing incoming data from the backend (asynchronous data), and in
handling events and delivering multiple values of any type depending on the content.
Owing to the aforementioned advantages, Angular uses Reactive Extensions (RxJS),
which is a third-party library that's used to implement observables.
Observables are declarable. They are called on functions that publish values, and then,
immediately, a subscriber subscribes to these values. The function then executes and
the subscriber unsubscribes once the execution has completed.
Note
To learn more about observable implementation such as multicasting, which
is about running multiples subscribers in a single implementation, refer to the
following guide: https://angular.io/guide/observables.
Note
To read more about the practical use of observables, refer to the following guide:
https://angular.io/guide/practical-observable-usage.
next(response) { console.log(response); },
error(err) { console.error('Error: ' + err); },
complete() { console.log('Completed'); }
});
Note
For more information on the implementation of operations that use observables,
refer to the following documentation: https://angular.io/guide/observables-in-
angular.
Note
The code files for this activity can be found here: http://bit.ly/2BQSnpn.
Note
The solution for this activity can be found on page 306.
})
export class AppModule { }
From the preceding snippet, we can deduce that the NgModule decorator that describes
and initializes properties consists of the following:
• Declarations
• Imports
• Providers
• Bootstrap
We shall describe all the aforementioned components in the following sections. We will
begin with declarations.
Declarations
The module's declarations array tells Angular which components belong to that
module. As you create more components, you need to add them to declarations.
The array holds the declarables (components, directives, and pipes) that belong to
the module. Whenever any declarable (for example, a component) is created, it must
be declared in the array or an error will be returned by Angular. If any declarable
is declared twice, an error will be emitted by the compiler. The steps involved in
implementing a declarable are as follows:
1. Export it from the file where it has been written
2. Import it into the app.module.ts file
3. Declare it in the @NgModule declarations array
Let's see what a declarations array looks like by using the following snippet:
declarations: [
Component,
Pipe,
Directive
],
184 | Angular Declarables, Bootstrapping, and Modularity
Imports
The module's import array appears only in the @NgModule metadata object. It tells
Angular about other NgModules that the module needs to function properly. Modules
that are commonly used are BrowserModule, FormsModule, and HttpClientModule because
they export components, directives, and pipes that the component templates in the
module refer to. A component template can refer to another component, directive,
or pipe when the referenced class is declared in this module or whether the class was
imported from another module.
Providers
The provider array is where you list the services that the app requires. The listed
services in the provider array are made available app-wide. You can also scope them
when using the feature modules and the lazy loading modules.
Bootstrap
The process of bootstrapping involves the insertion of the components contained in the
bootstrap array of NgModule.bootstrap into the browser DOM. The root AppComponent
is the entry component that is created by default during the process of bootstrapping,
which happens immediately when an application launches.
In most cases, applications have a single root component in the bootstrap array list. The
bootstrap array is meant to trigger the creation of other components that fill out that
tree based on the root components.
Angular Modularity
Modules in Angular are defined in the form of NgModules. Modules help to organize
an application and extend its functions using external libraries. NgModules provide an
application with the ability to share functions and reuse code across the application,
which in turn helps reduce the actual size of it. Some of the most frequently used
libraries, such as HttpClientModule, and RouterModule, are available as NgModules. They
provide applications with the ability to use HTTP Services and routing in Angular,
respectively. There are also some popular third-party applications, such as Ionic and
AngularFire2.
Every Angular app has at least one module, which is the root module that is
bootstrapped to launch the application. As the app's complexity increases, the root
module is broken down into feature modules that represent collections of related
functionalities. The features modules are then imported into the root module.
Angular Bootstrapping and Modularity | 185
When you need to access a class from another, we need to import it, as shown here:
import { AppComponent } from './app.component';
NgModules
NgModules are classes that are decorated with @NgModule. The presence of @NgModule
and its metadata differentiates it from regular JavaScript modules. The AppModule that's
generated from the Angular CLI uses both JavaScript modules and NgModule ,as shown in
the following code:
/* These are JavaScript import statements. */
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
Feature Modules
A feature module delivers a well-integrated set of functionalities that is focused and
is distinguished primarily by its intent and purpose. For example, a feature module
can be created for business facilities (forms, HTTP, and routing), business domain, and
user workflow collection of related utilities. The five types of feature module that are
available in Angular are as follows:
Domain feature: This type of feature module is used to deliver a user experience
regarding a particle application domain. They are characterized by the following points:
• They consist of mainly declarations and are only imported once by a more
superior feature module
• They have top components that act as the root features, supporting other
descendants (sub-components)
• They rarely have providers, but when they do, they become available for use if the
provider keeps rendering the service
• They might be imported for the root AppModule of a small application that
lacks routing
Routed feature: This type of feature module is used by router navigation. An example
of this is lazy loaded modules; they are also defined as routed feature modules. Routed
feature modules are characterized by the following points:
• Routed feature components never appear in the template of an external
component; therefore, they don't export anything.
• They rarely have providers, but when they do, they become available to use as
long as the provider keeps rendering a service (that is, the routed module life span
becomes dependent on the provider's life span).
• They are not meant to be imported by any module to avoid eager load (a query for
one type of entity also loads related entities as part of the query).
We are going to talk more about the lazy-loading module in the next section.
Routing: This type of module provides routing configuration for another module.
It separates routing configuration from the main module. These modules are
characterized by the following points:
• They do not have their own declarations; that is, they don't have declare
components, pipes, or directives.
• They can only be imported by the module's companion module.
Angular Bootstrapping and Modularity | 187
• They add guard and resolver service providers to the module's providers.
• The module should use the "Routing" suffix, which is a name parallel to the name
of its companion module.
• They define the route and add the router's configuration to the module's import.
Service: This module has been implemented more than once in the previous sections.
It is one of the most frequently used modules. An example of a service module is
HttpClientModule, as we explained in the previous section. It provides utility services
such as data access and specially formatted data describing events, requests, and
replies. They are characterized by the following points:
• They only consist of providers and are not declarable
• A service module should only be imported by the application root
• Services in one module are available to all modules
A service must be isolated from the other modules so that it can be imported only once.
As an example, you can have a UserModule that consists of many services imported into
it. All these services will be available app-wide whenever the UserModule is imported.
Widget: This type of module is used whenever there is a need to use the declarable in
an external module. Widgets are characterized by the following points:
• They can be imported into any module whose component's template needs it
• They consist entirely of exported declarations
• They rarely have providers
For example, you can have a UIModule that has many components declared inside it and
every time you need to use one or all its exported elements, you import only UIModule.
core.
Entry Components
An entry component is any component that Angular loads in a peremptory manner and
is not referenced in any template. Entry components can be loaded in two ways:
Bootstrapping inside NgModule: The following is an example of specifying a
bootstrapped component, AppComponent, in a basic app.module.ts:
@NgModule({
declarations: [
AppComponent
188 | Angular Declarables, Bootstrapping, and Modularity
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent] // bootstrapped entry component
})
Angular loads a root AppComponent dynamically because it's listed by type in @NgModule.
bootstrap.
Including it in a route definition: This can be seen in the following snippet:
const routes: Routes = [
{
path: '',
component: CustomerListComponent
}
];
You can now inject UserService anywhere into your application. When CLI commands
are used to generate a service, by default, you have the @injectable decorator
configured with a providedIn property set with 'root'. This specifies that the service
will be provided in the root injector, thereby making it available app-wide. Unless a
service is meant to be consumed by a specific @NgModule, the root is recommended
for the providedIn property. Beginning with Angular 6, it is the preferred method for
creating a singleton service. It's done by setting providedIn to root on the service's @
Injectable decorator, as seen in the preceding snippet.
The following snippets show two alternative ways of implementing services in a module:
1. Using injectables (as discussed earlier):
import { Injectable } from '@angular/core';
import { UserModule } from './user.module';
@Injectable({
providedIn: AdminModule,
})
export class UserService {
}
2. Using NgModule:
import { NgModule } from '@angular/core';
import { UserService } from './user.service';
@NgModule({
providers: [UserService],
})
export class UserModule {
}
190 | Angular Declarables, Bootstrapping, and Modularity
When this is set, the service is only made accessible to the components alone since the
component provider and NgModule providers are independent of each other.
In lazy loading, instead of loading all resources for every page of an app, you load what
is needed at a specific time when it is really needed. Therefore, we can say lazy modules
are modules that use lazy loading. This comes with a lot of advantages, with one of
the greatest being performance. For example, on your e-commerce website, where
you have a page that users rarely visit, and the page is meant to load 'new products'.
The logic can be split to not load resources for the page unless it is visited. This will
make your application start faster because there will be one module less to load until
someone visits that page.
Note
The code files for this exercise can be found here: http://bit.ly/2E5BK9A.
@NgModule({
declarations: [ShareTextDirective],
imports: [
CommonModule
],
exports: [
ShareTextDirective ]
})
As can be seen from the preceding output, we have created a shared module for a
custom attribute directive.
Note
The code files for this exercise can be found here: http://bit.ly/2BIHhm7.
Angular Bootstrapping and Modularity | 193
1. Create a new app (student) with routing using the following CLI command:
ng new student --routing
Note that the --routing flag generates a file called app-routing.module.ts, which is
one of the files you need for setting up lazy loading for your feature module.
2. Create a students feature module with routing:
ng generate module students --routing
3. Add a student-list component to the feature module:
ng generate component students/student-list
4. Create a home component:
ng generate component home
5. Create a book feature module (courses) with routing:
ng generate module courses –routing
6. Add a course-list component to the feature module:
ng generate component courses/course-list
7. Update the routes array in the AppRoutingModule with the following code:
src/app/app-routing.module.ts
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { HomeComponent } from "./home/home.component";
{
path: '',
component: HomeComponent,
}
];
194 | Angular Declarables, Bootstrapping, and Modularity
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
providers: []
})
export class AppRoutingModule {}
8. Configure the feature module's routes in students-routing.module.ts.
First, import the component at the top of the file with the other JavaScript import
statements, and then add the route to StudentListComponent, as shown in the
following code:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { StudentListComponent } from './student-list/student-list.
component';
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class StudentsRoutingModule { }
9. Configure the Routes array for the courses-routing.module.ts, as shown in the
following code:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CourseListComponent } from './course-list/course-list.component';
const routes: Routes = [
{
path: '',
component: CourseListComponent
}
];
Angular Bootstrapping and Modularity | 195
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class CoursesRoutingModule { }
10. Update the app.component.html template with the following code:
<h1>
{{title}}
</h1>
<button routerLink="">Home</button>
<button routerLink="/students">Students</button>
<button routerLink="/courses">Courses</button>
<router-outlet></router-outlet>
11. Go to localhost:4200 in the browser. You will obtain the following output:
Click on the Students tab. You will obtain the following output:
Click on the Books tab. You will obtain the following output:
Note
The JSON-server library will be used to mock the API server. The data for this
purpose can be found here: https://api.myjson.com/bins/10v4ns. The code files for
this activity can be found here: http://bit.ly/2NDvb2N
Note
The solution for this activity can be found on page 310.
Summary
This chapter got us started on the use of Angular's inbuilt pipes, such as upper
case, date decimals, percent, async, and so on. We later created custom pipes and
directives via exercises and activities. We wrapped up this section by looking at the
implementation of observables and the RxJs library.
We then described Angular bootstrapping and modularity. We first initialized an
Angular application using NgModule decorator properties such as declarations, imports,
providers, and bootstrap. We later described the difference between NgModule and
JavaScript modules in application development. We continued by describing the feature
modules available, such as domain, routed, service, and widget. The concept of the
entry component service provider scope was described as well. We ended this chapter
with an activity on creating a lazy loaded application.
In the next and final chapter of this book, we will be performing unit and e2e testing, in
addition to animating and optimizing Angular elements.
Testing and
6
Optimizing Angular
Applications
Learning Objectives
Introduction
In the previous chapter, we implemented the Angular animation module. That chapter
described the various animation functions available in Angular, in addition to the
methods for animating an element and a route transition.
In this chapter, we will be addressing the final objective of this book, testing and
optimizing Angular applications. Additionally, you will learn about some features in
Angular 7, along with concepts such as storing constants, Angular router, and more.
Later in the chapter, you will also learn the various ways of optimizing Angular
applications in terms of performance.
This chapter will get you started on performing unit and e2e tests, which are the
two main testing procedures for Angular applications. You will also have a glimpse
of continuous integration in Angular projects. At the end of each part, you will gain
practical experience with exercises and activities that are tailored to the goals and
objectives of this book.
Animation Functions
@angular/animations and @angular/platform-browser are the main Angular modules for
animations. The former comprises several animation functions that can be imported
into component files, while the latter supports the delivery of apps on different
supported browsers. Some of the most used functions are as follows:
trigger(): This function is the container for all other animation function calls. Its major
task is to kick off the animation. It uses an array syntax, and a trigger name is declared
as the first argument. The implementation is as follows:
animations: [
trigger('openClose', [
// ...
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
}))
]),
]
style():This function controls the visual appearance of the HTML. It uses an object
array as its syntax and defines the styles to use in the animations.
state(): This function defines the set of CSS styles that is to be applied to a component
or element. The function also ensures a successful transition to a given state, as shown
in the following snippet:
animations: [
trigger('openClose', [
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
204 | Testing and Optimizing Angular Applications
})),
state('closed', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green'
}))
]),
]
transition(): This function uses an array syntax to define the animation sequence
between two named states. The implementation is as follows:
animations: [
trigger('openClose', [
// ...
transition('open => closed', [
animate('1s')
]),
transition('closed => open', [
animate('0.5s')
]),
]),
],
animate(): The function defines the specific time at which the transition will occur.
It also defines the values for delay and easing (though optional) and can define style
function calls within itself.
keyframes(): This function is used with the animate() function and uses an array syntax
to permit successional change between styles within a given time interval. Styles can be
defined within this function.
Angular Animations and the Latest Angular Features | 205
Some other functions available are group(), query(), sequence(), stagger(), animation(),
useAnimation(), and animateChild().
Note
More details about these functions can be found at https://angular.io/api/
animations.
Animating an Element
Let's quickly see how we can animate an element using the functions we previously
explained. Assuming we have an Angular app generated using the CLI, the following
points can be implemented.
Importing animation functions into component files
We import a function whenever it is defined in any module and is to be used by
components. In the following example, we will be importing the animation functions
from @angular/core and @angular/animations:
import { Component, HostBinding } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
Adding the animation metadata property
The @Component() decorator holds the metadata property of the components. For the
purpose of animations, we declare an animation [trigger ()] array property. This is a
container for all other animation function calls. The implementation is as follows:
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [
206 | Testing and Optimizing Angular Applications
@Component({
selector: 'app-animate',
animations: [
trigger('startEnd', [
state('start', style({
backgroundColor: 'green',
width: '100px',
height: '100px'
208 | Testing and Optimizing Angular Applications
})),
state('end', style({
backgroundColor: 'red',
width: '50px',
height: '50px'
})),
transition('start => end', [
animate('1.5s')
]),
transition('end => start', [
animate('1s')
]),
]),
],
templateUrl: './animate.component.html',
styleUrls: ['./animate.component.css']
})
export class AnimateComponent {
constructor() { }
hasStarted = 'start';
toggle() {
this.hasStarted = this.hasStarted === 'start' ? 'end' : 'start';
}
}
Angular Animations and the Latest Angular Features | 209
Observe that the trigger name is wrapped in square brackets, [ ], and preceded by the
@ symbol. The following screenshot is the output when the preceding code snippets are
run:
Now let's see how we can animate page transition by performing an exercise on
creating an animated route transition.
210 | Testing and Optimizing Angular Applications
Note
The code files for this exercise can be found here: http://bit.ly/2GZ9xEv.
6. Import the routing module into app.module.ts using the following code:
imports: [
…………………………….
RouterModule.forRoot([router]);
]
7. Add a router outlet to the root component (app.component.ts) in the AppComponent
class using the following code:
prepareRoute(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.
activatedRouteData['animation'];
}
8. Create a ts class file for the animation with the following code:
touch animation.ts
9. Import the animation functions into animate.ts:
import {trigger,state,style,animate,transition,query,animateChild,group}
from '@angular/animations';
10. Define the animations as shown in the following code:
export const slideInAnimation =
trigger('routeAnimations', [
transition('HomeComponent <=> AboutComponent', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
//[..]
query(':enter', animateChild()),
])
]);
212 | Testing and Optimizing Angular Applications
As can be seen from the preceding output, we have successfully created and tested
a page transition animation. Let's complete an activity to test our skills on animating
route transitions.
Activity 15: Animating the Route Transition Between the Blog Post Page and
the View Post Page of the Blogging Application
You have been tasked with animating the route transition between the Blog Post page
and the View Post page of the Blogging Application. When the user navigates between
the two pages, the transition should be animated. Ensure that all previous exercises and
activities have been completed. Open the Blogging Application folder and perform the
following steps to complete the activity:
Note
The code files for this activity can be found here: http://bit.ly/2VfAVlL.
Angular Animations and the Latest Angular Features | 213
Note
The solution for this activity can be found on page 318.
Angular Budget
This feature was also recently added to Angular; it enables a developer to actually set
the threshold in the application configuration file (angular.json). This ensures the app
stays within the defined size. In this view, for the default budget configuration, the
maximum error size is raised to 5 MB, and an error warning is initiated when 2 MB is
exceeded. The implementation of this feature is shown in the following code:
"budgets": [{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}]
The Component Development Kit (CDK) for Angular
The CDK is a set of tools that implement a common interaction pattern with
un-opinionated presentation. It helps developers with high-quality predefined
behaviors for components by providing robust, well-tested tools to add common
interaction patterns with minimal effort; it can help you build what you need in less
time, with less code, and fewer bugs. The CDK consists of two categories: common
behaviors and components.
The common behaviors are the tools for implementing the common application features
presented in the following list:
• Accessibility
• Bi-directionality
• Drag and drop
• Layout
• Observers
• Overlay
• Platform
• Portal
• Scrolling
• Text field
Angular Animations and the Latest Angular Features | 215
The preceding snippet can be found in the environment.ts file. We can observe that
a new constant called apiUrl with the local URL is being defined. Let's assume that
the API has been deployed. So, we will add that URL to the apiUrl constant inside the
environment.prod.ts file, as shown in the following code:
export const environment = {
production: true,
apiUrl: "https://apiurladdress.com"
}
Recall that we dealt with node API authentication in the previous chapter. This was
done in the backend. In this section, we shall be describing different ways by which
frontend routes can be authenticated and protected.
• CanLoad: This type of guard decides whether a module can be loaded lazily.
• Resolve: This guard is employed to retrieve dynamic data.
Depending on what we want to do, we might need to implement any one of these
guards. In some cases, we may have to implement all of them.
For example, to implement CanActivate for a JWT, we can base our routing-access
decision on properties, such as expiration time, roles, and so on, that make up the
token. The implementation for all token properties is listed in the following steps:
1. If route decision is based on the expiration time, then install and load the
JwtHelperService class from angular2-jwt:
npm install --save @auth0/angular-jwt
2. If route decision is based on the role assigned to a user, then install and load the
decode class from jwt-decode using the following command:
npm install --save jwt-decode
3. Create a method in your authentication service to check whether a user is
authenticated, as shown in the following snippet:
import { Injectable } from '@angular/core';
import { JwtHelperService } from '@auth0/angular-jwt';
@Injectable()
export class AuthService {
constructor(public jwtHelper: JwtHelperService) {}
public isAuthenticated(): boolean {
const token = localStorage.getItem('token');
@Injectable()
export class ExpiryRoleGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(route: ActivatedRouteSnapshot): boolean {
if (
!this.auth.isAuthenticated() ||
tokenPayload.role !== expectedRole
) {
this.router.navigate(['login']);
return false;
}
return true;
}
5. Apply the guard to any routes you wish to protect using the following snippet:
import { Routes, CanActivate } from '@angular/router';
import { ProfileComponent } from './profile/profile.component';
import {
ExpiryRoleGuardService as ExpiryDateRoleGuard
} from './auth/auth-guard.service';
import { ]
export const ROUTES: Routes = [
{ path: '', component: HomeComponent },
{
path: 'profile',
component: ProfileComponent,
canActivate: [ExpiryDateRoleGuard] ,
data: {
expectedRole: 'admin'
}
},
{
path: 'admin',
component: AdminComponent,
canActivate: [ExpiryDateRoleGuard],
218 | Testing and Optimizing Angular Applications
data: {
expectedRole: 'admin'
}
},
{ path: '**', redirectTo: '' }
];
Note that the preceding implementation is for both expired token and user role.
However, these can also be implemented separately. Having seen example snippets
on storing and defining constants, we will be performing an activity to cement our
understanding of this Angular functionality.
Activity 16: Implementing Router Guard, Constant Storage, and Updating the
Application Functions of the Blogging Application
You have been tasked with implementing router guard, constant storage, and updating
the blogging application components and services. Ensure that all previous exercises
and activities have been completed. Open the Blogging Application folder and perform
the following steps to complete the activity:
Note
The code files for this activity can be found here: http://bit.ly/2NjgLo0.
Note
The solution for this activity can be found on page 320.
2. Input the web address of the site to test and analyze, as shown in the following
screenshot:
4. View performance of the web application (mobile version) by clicking the MOBILE
tab. An example screenshot is shown here:
From the metrics shown in the result, you can identify the faster site and thus estimate
on which site users will experience better performance. Therefore, even if you can
make a website feel faster, actually knowing you're making the right changes is crucial.
Web application performance can be factored into two categories: load time
performance and runtime performance. In the next section, we shall be looking at
looking at methods and techniques in which Angular application load times can be
improved and optimized.
The speed at which a page (web application) loads is dependent on some of the factors
listed here:
• Hosting server
• Amount of bandwidth in transit
• Web page design
• Number, type, and weight of elements on the page
• User location, device, and browser type
In this section, we shall focus on the practices for optimizing a web application
developed using Angular CLI. These practices aim to do the following:
1. Making sure that application build size is small: One of the methods to ensure
a small build application is to eliminate dead code. Dead code elimination can
also be referred to as tree-shaking. It has to do with preventing unused import
and export modules from being included in the bundle during the build process.
The Angular CLI enables tree-shaking to be implemented in webpack by default,
thereby significantly reducing the code size of the application, and the less code
we send over the wire, the more performant the application will be.
2. Uglification: This process is implemented in Angular by webpack using the uglify
plugin. The uglification process help achieve a smaller build after the code has
been subjected to transformation processes such as removal of comments, white
spaces, and so on. The process is performed in Angular CLI by specifying the prod
flag.
3. Minification: Angular 6 made aggressive minification easier by implementing a
closure compiler, which is a bundling optimizer for JavaScript modules. This is
used for almost all Google web applications to generate smaller bundles and for
dead code elimination.
4. Rendering: Also recently introduced by Angular is a render engine called Ivy. The
aim of this is to mainly improve speed and reduce code size by using gzip files,
thereby making the app smaller in size and compilation faster. As of the release of
Angular 7, the engine can only be used by enabling it manually.
5. Compressing images and removing unused resources and packages:
Reducing image size without losing quality and removing unused resources
(images, font, packages, and so on) are a couple of the best techniques for
improving the build time, because bytes transferred over the network are saved.
JPEG and PNG images can be compressed without losing much quality using
extensions such as TinyPNG.
224 | Testing and Optimizing Angular Applications
6. Updated reactive extensions for JavaScript: Using the updated version of RxJs (6)
also reduces the final build size because unnecessary code will be removed.
7. Updated webpack: Using the updated version of Webpack can result in smaller
builds and reduced development time. It has been proven that an updated
webpack can be up to 60%-98% faster when compared to an earlier version.
8. Using CLI flags: The use of Angular CLI flags such as prod and build-optimizer is
advised. The prod flag helps enable various build optimizations discussed earlier,
such as uglify, thereby leading to smaller builds. The build optimizer disables the
vendor chunk, thereby resulting in smaller code.
9. Angular and Angular CLI Updates: One of the alternative ways to improve the
load time of an Angular application is to ensure that the Angular framework and
CLI are regularly updated.
10. Third-party package updates: Third-party packages are also advised to be
regularly updated because this gives the benefits of new features, security,
performance optimizations, and bug fixes.
11. Lazy-loading pages: Angular CLI provides the lazy-loading mechanism, which
ensures that only pages and modules required or needed at a particular moment
are loaded. The basic principle of using lazy loading is "don't load something that
you don't need", and thus implementing this mechanism on application routes can
reduce initial load time.
12. Compiling code offline: The ahead-of-Time (AOT) compiler, which complies
before (during the build stage) the code is run, reduces much of the processing
overhead on the client browser. To enable AOT, you have to specify the aot flag.
However, note that with the prod flag in use, the aot flag is not required.
13. Cache-control header: The cache-control header specifies the directives for
caching mechanisms in both requests and responses. This header controls the
conditions under which the response is cached, the identity of who catches the
response, and the time needed for the network round trip for the resources that
are cached.
14. Progressive web application: A progressive web application (PWA) gives offline
capabilities to the application. It makes the application to load much faster
and gives a near-native app experience, thereby greatly improving the overall
perceived performance by the user.
Optimizing Angular Applications | 225
Note
For more information on how to use the Chrome DevTools Performance panel and
Web tracing framework to analyze runtime performance, refer to the following:
https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/
and https://github.com/google/tracing-framework.
There is a property called trackBy provided by Angular to track objects by IDs and not
references. The trackBy property expects a method and is used as shown:
<ul>
<li *ngFor=" let customer of customers trackBy: customerById">{{ customer.
name }}</li>
</ul>
As seen in the preceding snippet, the current index and the current entity are the
arguments passed into custormerByid with this method. DOM nodes are present only if
the customer ID changes. This implementation is very cheap and doesn't have any cons;
therefore, it is advised not to be hesitant in using it.
OnPush
OnPush is a change strategy offered by Angular to be defined on any component. When
OnPush is employed, component templates will be checked for the following cases:
• If the reference of one of the inputs of the component changes
• If a component's event handler was triggered
Let's assume we have three components, A, B, and C, and all are, in one way or the
other, interconnected.
We can deduce that all components are checked every time a change is detected in the
application. This is a waste of time because we know that if A doesn't change, then B
and C don't need to be checked. How do we solve this?
This issue can be solved using OnPush. Let's add a changeDetection attribute in the @
Component decorator as shown in the following snippet:
@Component({
selector: 'a-comp',
template: '….',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AComponent {
@Input() src: string;
228 | Testing and Optimizing Angular Applications
check() {
console.log( A component checked');
}
}
The result of the preceding snippet is that you will observe only components that are
needed.
We can also add change detection to B as shown in the following:
@Component({
selector: 'b-comp',
template: '
<p>{{ check() }}</p>
<c-comp [src]="getBImageUrl()"></c-comp>
',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BComponent {
@Input() BModel: BModel;
check() {
console.log('b component checked');
}
getBImageUrl() {
return 'images/b-${this.bModel.color}-running.gif';
}
}
Optimizing Angular Applications | 229
@Component({
selector: 'btn',
template: '
<button type="button" class="btn btn-{{ bType }}">
<ng-content></ng-content>
</button>'
})
export class BtnComponent {
@Input() bType;
}
The template :
< btn bType ="primary">Hello!</ btn >
< btn bType ="success">Success</ btn >
From the template, we can see that the string value of the input is a constant, so it is
better to use the @Attribute decorator instead, as shown in the following:
import { Attribute, Component } from '@angular/core';
@Component({
selector: 'btn',
template: '
<button type="button" class="btn btn-{{ btnType }}">
<ng-content></ng-content>
230 | Testing and Optimizing Angular Applications
</button>'
})
Now that we have described various methods and strategies for optimization, we will be
performing an exercise to implement change detection.
Note
The code files for this exercise can be found here: http://bit.ly/2EoHVHr.
1. Open the main.ts file in the student folder and replace the application
bootstrapping code with the following:
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-
dynamic';
import { ApplicationRef } from '@angular/core';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
import { enableDebugTools } from "@angular/platform-browser"
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.then(modRef => {
const appRef = modRef.injector.get(ApplicationRef);
const compRef = appRef.components[0];
Optimizing Angular Applications | 231
In the preceding output, you can clearly see the number of change detection
cycles that were run, in addition to the duration per check.
232 | Testing and Optimizing Angular Applications
Figure 6.8: Detection cycles screen while recording the CPU profile
From figures 6.7 and 6.8, we can say that we have successfully implemented the change
detection method and thus analyzed the runtime performance of the website. Next, we
will describe testing Angular applications.
From the preceding snippet, we notice that some function calls are used. These are
described as follows:
• describe(): It is a suits function that is used for grouping interrelated specs.
• it(): This is a spec function that takes a title and a function containing one or
more expectations.
• expect(): This is an assertion function to evaluate true or false. It is used for
building expectations.
234 | Testing and Optimizing Angular Applications
• .toEqual(): This is one of the matcher functions provided by Jasmine and is used
to compare the expected and the actual results.
Note
There are many other functions defined by the Jasmine; you can refer to the
following for a complete list: https://jasmine.github.io/2.0/introduction.html.
Note
With the Angular CLI, you don't need know the detailed configuration of Karma,
because it already handles the configuration as long as Jasmine is used. Our focus
in this book shall mainly be on the Angular CLI application testing technique.
Testing Components
To test a component, we will use some common setup utilities such as TestBed, Async,
ComponentFixture, and FakeAsync. TestBed can be compared to @NgModule as it helps
to configure dependencies for our test. TestBed.configureTestingModule is called
to pass the test configuration and resolve dependencies, whereas TestBed loads the
corresponding dependencies so they are available during your tests. The async utility
is used when dependencies involve asynchronous handling. It wraps a test function
in an asynchronous test zone. The FakeAysnc utility is similar to async, but it accepts
a parameter, which is the time in milliseconds. The purpose of this is to move time
forward because FakeAysnc doesn't execute in a synchronous order, unlike async.
FakeAsync waits to execute until the tasks inside the asynchronous zone is executed.
The ComponentFixture utility has a method for testing and debugging components. The
isStable method checks whether a component is stable and has completed an async
task while the destroy method destroys a component in order to free up memory. By
default, Angular generates a test file alongside other component files. For example,
assuming we have already generated an Angular CLI application, we can perform the
following steps:
1. Generate components using the ng generate component testPage command in the
CLI.
2. Go to the testPage folder and locate a file named test-page.component.spec.ts,
which is the spec file for components, and run the following:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
//[…]
3. Run ng test to launch the Karma test runner (as seen in figure 6.12) and observe
four test were successfully passed: three from AppComponent and one from
TestPageComponent. Figure 6.11 shows the output for the tests that are run:
Karma displays the unit test result in the browser as shown in the following figure:
Figure 6.10: Karma displaying the unit test results in the browser
Testing Angular Applications | 237
describe('TestService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
@Injectable({
providedIn: 'root'
})
export class TestService {
mockData = 'https://jsonplaceholder.typicode.com/todos'
getPost(id): Observable<Data> {
return this.http.get<Data>(this.mockData+'/'+id);
}
}
From the preceding snippet, we can see a getPost function using an HTTP get request;
our purpose is to write a unit test for the function. Therefore, to create a test for the
HTTP request, we need to open the spec.ts file and perform the following steps:
1. Add HttpClientTestingModule and its HttpTestingController to the import section:
import { HttpClientTestingModule, HttpTestingController } from '@angular/
common/http/testing';
2. Add HttpClientTestingModule to the import the property of the TestBed
configuration object:
describe('TestService', () => {
beforeEach(() => TestBed.configureTestingModule({
providers: [TestService],
imports: [
HttpClientTestingModule ],
}));
3. Write a spec function for the HTTP service function:
it('expects service to fetch data based on post id',
inject([HttpTestingController, TestService],
(httpMock: HttpTestingController, service: TestService) => {
// create fake data
const fakeData = {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
// We call the service
service.getPost(1).subscribe(data => {
expect(data.id).toBe(1);
expect(data.title).toBe('delectus aut autem');
expect(data.userId).toBe(1);
expect(data.completed).toBe(false);
});
// We set the expectations for the HttpClient mock
240 | Testing and Optimizing Angular Applications
Before we move on to the next section, we will briefly compare unit and e2e tests on
the basis of the following parameters:
• Speed: Unit tests run quickly because they basically operate on small chunks of
code and thus they run faster compared to e2e tests, which run in a browser.
• Reliability: The major concern with e2e test is that they run slowly, thereby
leading to source of false positives that fail tests because of timeout issues. Time
out issues can result from using more dependencies and complex interactions.
However, even if a well-written unit test fails, you can trust that there's a problem
with the code.
• Code quality: An e2e test happens via the browser and it does not directly interact
with the code. However, a unit test does so, therefore it helps to ensure that a
code is well written and easy to test. Writing unit tests can help identify needlessly
complex code that may be difficult to test. As a general rule, if you're finding
it hard to write unit tests, your code may be too complex and may need to be
refactored. However, writing e2e tests won't help you write better-quality code
per se.
Cost-effectiveness: Because e2e tests take longer to run and can fail at random, a cost
is associated with that time. It also can take longer to write such tests, because they
may build upon other complex interactions that can fail; therefore, development costs
can be higher when it comes to writing e2e tests.
Mimicking user interactions: Mimicking user interactions with the user interface is
where e2e tests shine. Using Angular protractor, you can write and run tests as if a real
user were interacting with the user interface. You can simulate user interactions using
unit tests, but it'll likely be easier to write e2e tests for that purpose because that's what
they're made for.
We want to find a web element using by.css, so the protractor will be as follows:
let e1 = element(by.css('.contact-email')); let e2 = element(by.
css('#contact-email')); let e3 = element(by.css('input[type="email"]'));
by.id
If we need to find a web element by ID and have the following HTML:
<input class="contact-email" id="contact-email" type="email">
Exercise 27: Performing an E2E Test for a Default Angular CLI Application to
Find an Element Using By.CSS
In this exercise, we will be writing an e2e test for a default Angular application by
finding an element using by.css(). You must have completed the exercises in the
previous chapters. To get started, create an application with the name e2etestapp, and
perform the following steps:
Note
The code files for this exercise can be found here: http://bit.ly/2WrTowJ.
1. Open the app.components.html file to decide the element to be tested using the
following code:
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
Testing Angular Applications | 243
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml; ">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/
tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://github.com/
angular/angular-cli/wiki">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.
io/">Angular blog</a></h2>
</li>
</ul>
2. Open the app.po.ts protractor file, and write the following function:
import { browser, by, element } from 'protractor';
getParagText () {
return element(by.css('app-root h1')).getText();
}
getParagText2() {
return element(by.css('app-root h2')).getText();
}
getParagText3 () {
return element(by.css('app-root li h2 a')).getText();
}
}
244 | Testing and Optimizing Angular Applications
3. Open the e2e folder to access the test file named app.e2e.spec.ts and observe the
following code, which has the default e2e code for testing the content in the title:
import { AppPage } from './app.po';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage ();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagText()).toEqual('Welcome to packtTest!');
});
4. Write the following test to display the content in the H2 tag:
it('should display content in h2', () => {
page.navigateTo();
expect(page.getParagText2()).toEqual('Here are some links to help you
start:');
});
5. Write the following test to display the content in the a tag:
it('should display content in a', () => {
page.navigateTo();
expect(page.getParagText3()).toEqual('Tour of Heroes');
});
6. Go to the terminal and run ng e2e. You will obtain the following output:
As can be seen in the preceding output, we have successfully completed e2e testing and
located web elements using the by.css() method.
Activity 17: Performing Unit Tests on the App Root Component and Blog‑Post
Component
You have been tasked to write a unit test for the app root component and the blog-
home component in the Blogging Application project. Once finished, you should have all
service functions and components tested successfully. Before beginning the exercise,
ensure that all previous exercises and activities have been completed. You can use any
IDE of your choice; open the Blogging Application project and perform the following
steps:
Note
The code files for this activity can be found here: http://bit.ly/2GVJ9LX.
1. Open root component test file, app.components.spec.ts, and import the required
modules.
2. Mock the app-header, router-outlet, and app-footer components into the app.
components.spec.ts file.
3. Write the suits functions for AppComponent.
4. Write the assertion and matcher functions to evaluate true and false conditions.
5. Open the blog-home.component.spec.ts file (in the blog-home folder) and import
the modules.
6. Mock the app-title-header components in the blog-home.component.spec.ts file.
7. Write the suits functions in the blog-home.component.spec.ts file.
8. Write the assertion and matcher functions to evaluate true and false conditions.
9. Run ng test in the command line.
Note
The solution for this activity can be found on page 335.
246 | Testing and Optimizing Angular Applications
Summary
The book began by describing the MongoDB, Express, Angular, and Node (MEAN)
architecture. The MEAN technology components were described in terms of their
features/advantages, limitations, and scenarios in which they are best used. We ran the
Node server and implemented various Node features such as callbacks, event loops,
event emitters, streams, buffers, and the filesystem in the first chapter.
In the second chapter, we introduced RESTful APIs and their designs concepts,
performed operations on MongoDB Atlas, and implemented some features of the
Express framework on Node applications. The chapter activities aimed at implementing
Node, MongoDB Atlas, and Express together to develop a RESTful API that performed
CRUD operations.
The third chapter introduced us to frontend development using the Angular CLI.
We implemented various features such as directives, components, modules such
as templates and reactive forms, and routers. The fourth chapter introduced us to
Node application security practices and to the different forms of authentication/
authorization strategies for Node applications. We learned how to grant user access to
the RESTful API and perform user authentication on a Node application. This was done
by implementing token-based authentication using JWT and Passport middleware in
addition to applying local and social application strategies.
The fifth chapter taught us Angular declarable (pipes and custom directives),
in addition to observables and reactive extension. The chapter wrapped up
with Angular application bootstrapping and modularity, where we implemented
modules, such as feature modules and lazy loading modules, in various exercises
and activities.
In this chapter, we first described Angular animations by understanding some animation
functions provided by the Angular animation modules. The animation functions were
used to implement animation on elements and route transitions. In the latter part of
the chapter, we briefly learned about some newly added features, such as CLI imports,
Angular budget, virtual scroll, drag and drop, and Angular elements.
After this, we looked at optimizing Angular applications. In this section, we learned why
performance optimization is important and briefly about described some tools used for
checking and analyzing the performance of web applications. We later looked at how
load time and runtime performance can be optimized.
The final section took us through testing Angular applications. We looked at how the
two main types of tests, unit and e2e testing, are implemented using the provided
modules, tools, and frameworks. Thus, this book has provided you with the basic
elements of the MEAN stack for building robust web applications.
Appendix
>
About
This section is included to assist the students in performing the activities in the book.
It includes detailed steps that are to be performed by the students to achieve the objectives of
the activities.
250 | Appendix
6. Run the server by first pressing Ctrl + to open the integrated command-line
terminal in Visual Studio and then type the following:
>node server.js
You will obtain the following output:
7. Go to your browser and type http://localhost:3000 in the URL address bar. You
should obtain the following output:
3. Load and import the filesystem module into the stream.js file using the following
code:
var fs = require('fs');
4. Create a readable stream by calling the createReadStream() function on the
readTextFile.txt file using the following code:
Const readableStream = fs.createReadStream('readTextFile.txt');
5. Create a writable stream by calling the createWriteStream() function on the
writeTextFile.txt file using the following code:
Const writableStream = fs.createWriteStream('writeTextFile.txt');
6. Call the on() function on the readableStream() method. Then, pass data as the first
argument and then attach a callback, as shown in the following code:
readableStream.on('data', function (data) {
console.log('Hey!, I am about to write what has been read from this file
readTextFile.txt');
});
7. Call the write() method on the writeableStream() method to write data inside the
readableStream() callback using the following code:
readableStream.on('data', function (data) {
console.log('Hey!, am about to write what has been read from this file
readTextFile.txt');
var txt = ' Written!'
var newdata = data + txt; //Append text string to the read data
if (writableStream.write(newdata) === true) {
console.log('Hey!, am done writing, Open the file writeTextFile.txt to
see what has been written');
}
else
console.log('Writing is not successful');
});
8. Press Ctrl+ to open the integrated command-line terminal in Visual Studio and
run the program by typing in the following code:
node stream.js
Chapter 1: Introduction to the MEAN Stack | 253
Then, open the writeTextFile.txt file to confirm the text that was read and written
into it.
254 | Appendix
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10,
useNewUrlParser:true
};)
5. Create a folder named api (inside config), a subfolder models (inside api) inside it,
and then create an Article.js.
Code for creating the api folder:
mkdir api
Code for creating the models folder:
mkdir models
Code for creating the Article.js file:
touch Article.js
6. Declare the schema and assign a schema class in the Article.js file. First we
import Mongoose using the following code:
const mongoose = require("mongoose");
To declare and assign schema, we use the following code:
const Schema = mongoose.Schema;
To create the schema instance and add schema properties, use the
following code:
const BlogSchema = new Schema({
title: {
type: String,
required: true
},
body: String,
tag: {
type: String,
enum: ['POLITICS', 'ECONOMY', 'EDUCATION']
},
createdOn: {
type: Date,
default: Date.now
}
});
256 | Appendix
if(Article){
console.log('Model Succesfully Imported');
}
9. Run the output.js file to confirm whether the model has been imported using
the following code:
node output
You will obtain the following output:
10. Run node db.js inside the config folder to test the connection using the following
code:
node db.js
You will obtain the following output:
Activity 5: Creating the API Express Route and Testing a Fully Functional REST-
ful API
1. Create a routes folder within the api folder and create an articleListRoutes.
js file using the following code:
touch articleListRoutes.js
2. Open the articleListRoutes.js and create a route function using the follow-
ing code:
module.exports = function(app) { }
3. Import controller into route function using the following code:
module.exports = function(app) {
var articleList = require('../controllers/articleListController');
}
4. Create route for get and post requests on /articles using the following code:
module.exports = function(app) {
var articleList = require('../controllers/articleListController');
app
.route("/articles")
.get(articleList.listAllArticles)
.post(articleList.createNewArticle);
}
5. Create route for get, put, and delete requests on /articles/:articleid
using the following code:
module.exports = function(app) {
conts articleList = require('../controllers/articleListController');
app
.route("/articles")
260 | Appendix
.get(articleList.listAllArticles)
.post(articleList.createNewArticle);
app
.route("/articles/:articleid")
.get(articleList.readArticle)
.put(articleList.updateArticle)
.delete(articleList.deleteArticle);
}
6. Install Express and the bodyParser module using the integrated
command line.
To do this, first open server folder in the Blogging Application folder by using the
keyboard combination Ctrl + O or Ctrl + k from VSC and enable the integrated
terminal with Ctrl + '. Then, install Express and the bodyparser module using the
following code
npm install express – save
npm install bodyparser – save
7. Create a server.js file in the Blogging Application/config folder and then import
Express and the body-parser module using the following code:
const express = require("express");
const bodyParser = require("body-parser");
8. Create the Express application using the express() function:
const app = express();
9. Define the connection port
const port = process.env.PORT || 3000;
10. Call the bodyParser middleware on the created Express app using the
following code:
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
11. Import the db connection:
require("./config/db");
Chapter 2: Developing RESTful APIs to Perform CRUD Operations | 261
12. Add CORS (Cross-Origin Resource Sharing) headers to support cross-site HTTP
requests
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods',
'GET,PUT,POST,DELETE,PATCH,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization,
Content-Length, X-Requested-With');
// allow preflight
if (req.method === 'OPTIONS') {
res.send(200);
} else {
next();
}
});
13. Import the route using the following code:
conts routes = require('./api/routes/articleListRoutes');
14. Call route on the Express application using the following code:
routes(app);
15. Listen to the Express server
app.listen(port, () => {
console.log('Server running at http://localhost:${port}');
});
16. Run server.js inside the server folder to test the connection using the following
code:
node server.js
You will obtain the following output:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blog</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
//[…]
<script type="text/javascript" src="assets/js/main.js"></script>
</body>
</html>
4. Create the header components and update the HTML template with the theme's
header using the following code:
<title-header></title-header>
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="row blog_posts stander_blog">
<div class="col-md-6 col-lg-4">
<article>
<div class="post_img card-blog-img">
<img src="assets/images/home-27/2.
jpg" alt="Card image cap">
<a href="blog-standard-two-col-
right-sidebar.html">
<span class="card-blog-meta">
//[…]
</div>
</div>
</div>
</div>
7. Create the footer components and update the HTML template with the theme's
footer:
</footer>
</div>
10. Run ng serve -o to start the application. You will obtain the following output in
the browser:
11. Update the root component template with the following code:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<view-post></view-post>
<app-footer></app-footer>
</div>
</div>
You will obtain the following output in the browser:
@NgModule({
declarations: [
AppComponent,
BlogHomeComponent,
HeaderComponent,
FooterComponent,
ViewPostComponent,
TitleHeaderComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [ArticleService],
bootstrap: [AppComponent]
})
export class AppModule { }
272 | Appendix
3. Write the interface class in a newly created posts.ts file using the following code:
export interface Post {
photo: string;
title: string;
body: string;
tag: string;
}
4. Import the Injectable, HttpClient, Post, and Observable modules using the follow-
ing code:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Post } from '../posts'
import { Observable } from 'rxjs';
5. Declare Url variables and assign string values in the ArticleService class as shown:
articlesUrl = 'http://localhost:3000/articles';
articleUrl = 'http://localhost:3000/article/';
article: any;
httpOptions:any;
6. Inject HttpClient and set HttpHeaders using the following code:
constructor(private http: HttpClient) {
this.httpOptions = new HttpHeaders({
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'PUT, POST, GET, DELETE, OPTIONS',
});
}
return this.http.post<Post>(this.articlesUrl,
{ 'title': article.title, 'body': article.body, 'tag': article.tag,
'photo': article.photo }, {
headers: this.httpOptions
})
}
deleteArticle(id: number):
Observable<{}> {
return this.http.delete(this.articleUrl + id, {
headers: this.httpOptions
})
}
8. Update the blog-home components class file with the following code:
import { Component, OnInit } from '@angular/core';
// Import service
import { ArticleService } from '../service/article.service';
@Component({
selector: 'app-blog-home',
templateUrl: './blog-home.component.html',
styleUrls: ['./blog-home.component.css']
})
export class BlogHomeComponent implements OnInit {
articles:any=[];
//Inject service
constructor(private articleService:ArticleService) { }
ngOnInit() {
this.articleService.getArticles()
274 | Appendix
.subscribe(
res => {
for(let key in res){
this.articles.push(res[key]);
}
},
err => {
console.log("Error occured");
}
);
}
}
9. Update the blog-home components template file with the following code:
<app-title-header></app-title-header>
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="row blog_posts stander_blog">
<div class="col-md-6 col-lg-4" *ngFor="let article
of //[…]
</div>
</div>
</div>
</div>
10. Update the root component template with the following code:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<app-blog-home></app-blog-home>
<app-footer></app-footer>
</div>
</div>
Chapter 3: Beginning Frontend Development with Angular CLI | 275
11. Run ng serve -o on the command line to view the following output:
12. Update the view-post components class file with the following code:
import { Component, OnInit } from '@angular/core';
import { ArticleService } from '../service/article.service';
@Component({
selector: 'view-post',
templateUrl: './view-post.component.html',
styleUrls: ['./view-post.component.css']
})
export class ViewPostComponent implements OnInit {
id: any;
article: any;
constructor( private articleService: ArticleService) { }
276 | Appendix
ngOnInit() {
this.id = '5b9426b70e473447f400eeb9' // id of first post from API
this.articleService.getArticle(this.id)
.subscribe(
res => {
console.log(res)
this.article = res;
},
err => {
console.log("Error occured");
}
);
}
}
13. Update the view-post components template file with the following code:
</div>
14. Update the root component template with the following code:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<view-post></view-post>
<app-footer></app-footer>
</div>
</div>
15. Run ng serve -o to start the application. You should obtain the following output:
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule
],
4. Import the FormControl and FormGroup modules into the reactive form component
(packt-student-reactive-form.component.ts):
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/
forms';
import { Component, OnInit } from '@angular/core';
5. Declare and initialize the array in the reactive components class, as follows:
courseTitles = ['MEAN Stack', 'MEVN Stack', 'MERN Stack'];
6. Create FormGroup and FormControl variable instances:
myform : FormGroup;
name : FormControl;
courseTitle : FormControl;
duration : FormControl;
constructor() { }
7. Create functions to initialize and validate using the following code:
ngOnInit() {
this.createFormControls();
this.createForm();
}
Chapter 3: Beginning Frontend Development with Angular CLI | 279
createFormControls() {
this.name = new FormControl('Paul Adams', Validators.required),
this.courseTitle = new FormControl(this.courseTitles[0], Validators.
required),
this.duration = new FormControl('6 days')
}
createForm() {
this.myform = new FormGroup({
name: this.name,
courseTitle : this.courseTitle,
duration: this.duration
});
}
submitted = false;
<div class="container">
<div *ngIf="!submitted">
<h1>Packt Course Form</h1>
//[…]
</select>
<div *ngIf="courseTitle.invalid" class="alert alert-
danger">
courseTitle is required
</div>
</div>
<div class="form-group">
<label for="duration">Course Duration</label>
<input type="text" class="form-control" id="duration"
formControlName="duration">
</div>
280 | Appendix
.ng-invalid:not(form) {
Chapter 3: Beginning Frontend Development with Angular CLI | 281
// style.css
/* You can add global styles to this file, and also import other style files
*/
@import url('https://unpkg.com/[email protected]/dist/css/bootstrap.min.
css');
11. Update the root template file (app.componets.html file) by hosting the form
component src/app/app.component.html using the following code:
src/app/app.component.html
< app-packt-student-reactive-form ></ app-packt-student-reactive-form >
12. Launch the application by running the following snippet in the command-line
terminal and go to the address localhost:4200:
ng serve --open
You will obtain the following output:
Activity 9: Creating and Validating Different Forms Using the Template and
Reactive-Driven Method
1. Create the User Login form component using the following code:
ng generate component login
2. Update the User Login form component class and update the HTML template to
be reactive/model-driven using the following code:
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl, Validators, FormBuilder } from '@angular/
forms';
@Component({
selector: 'login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent {
loginForm: FormGroup;
displayMessage: string;
submitted = false;
constructor(private formBuilder: FormBuilder) {
/* Declare Reactive Form Group here */
this.loginForm = this.formBuilder.group({
email: ['', [
Validators.required,
Validators.pattern("[^ @]*@[^ @]*")
]],
password: ['', [
Validators.minLength(8),
Validators.required
]],
});
submitForm() {
this.submitted = true;
/* Change the display message on button click / submit form */
if (this.loginForm.valid) {
this.loginUser();
}
Chapter 3: Beginning Frontend Development with Angular CLI | 283
loginUser() {
console.log('Logged In sucessfully')
}
}
The template is updated with the following:
<title-header></title-header>
<div class="" style="padding-bottom: 3rem!important;">
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
//[…]
<!-- /form card login -->
</div>
</div>
</div>
Update the app component template with the following snippet and run ng serve to
view the User Login form output:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<app-login></app-login>
<app-footer></app-footer>
</div>
</div>
284 | Appendix
You will obtain the following output once you type http://localhost:4200/login in
the browser:
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
export class RegisterComponent implements OnInit {
model = new Users('', '', '');
constructor(/*private auth: AuthService,
private router: Router*/) { }
ngOnInit() {
}
onSubmit() {
this.registerUser();
}
registerUser() {
console.log('Registration Successful')
}
The template is updated as follows:
<title-header></title-header>
286 | Appendix
<div>
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
<h3 class="mb-0" style="text-align:center">Register Admin
User</h3>
<div class="card-header">
</div>
<div class="card-body">
<form (ngSubmit)="userForm.form.valid && onSubmit()"
//[…]
<!-- /form card login -->
</div>
</div>
</div>
6. Update the app component template with the following snippet and run ng serve
to view the User Registration form output:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<app-register></app-register>
<app-footer></app-footer>
</div>
</div>
Chapter 3: Beginning Frontend Development with Angular CLI | 287
You will obtain the following output once you run http://localhost:4200/register
in the browser:
7. Create the Post Create/Edit form component and an exportable Posts class:
ng generate component create
export class Posts {
constructor(
public title: string,
public body: string,
public tag: string,
public photo: string,
) { }
}
8. Update the Post Create/Edit form component class and update the HTML
template to be template-driven.
The class is updated as follows:
import { Component, OnInit } from '@angular/core';
import { Posts } from '../post';
@Component({
selector: 'app-create',
templateUrl: './create.component.html',
styleUrls: ['./create.component.css']
})
export class CreateComponent implements OnInit {
tags = ['POLITICS', 'ECONOMY', 'EDUCATION','STORY','TECH'];
model = new Posts('','','','');
submitted = false;
constructor() { }
onSubmit() {
this.submitted = true;
console.log(this.model)
}
ngOnInit() {
}
}
The template is updated as follows:
<title-header></title-header>
<div class="py-5">
<div class="row">
Chapter 3: Beginning Frontend Development with Angular CLI | 289
9. Update the app component template with the following snippet and run ng serve
to view the Post Create/Edit form output:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<app-create></app-create>
<app-footer></app-footer>
</div>
</div>
290 | Appendix
You will obtain the following output once you type http://localhost:4200/create
in the browser window:
imports: [
BrowserModule,
HttpClientModule,
RouterModule.forRoot(appRoutes),
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule
],
2. Configure the route in app.module.ts as follows:
const appRoutes: Routes = [
{ path: 'blog', component: BlogHomeComponent,
data: { title: 'Article List' }},
{ path: 'blog/post/:id', component: ViewPostComponent },
{ path: 'login', component: LoginComponent},
{ path: 'register', component: RegisterComponent},
{ path: 'create', component: CreateComponent},
{ path: '',
redirectTo: '/blog',
pathMatch: 'full'
},
// { path: '**', component: PageNotFoundComponent }
];
3. Add router-outlet to the app.component.html file:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div class="page-container scene-main scene-main--fade_In">
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>
</div>
292 | Appendix
<title-header></title-header>
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="row blog_posts stander_blog">
<div class="col-md-6 col-lg-4" *ngFor="let article
of articles">
<article>
<a routerLink="post/{{article._id}}">
<div class="post_img card-blog-img">
<img src="https://{{article.photo}}"
//[…]
</div>
</div>
</div>
</div>
5. Start the server and serve the Angular application using the CLI with the following
commands:
> node server.js
> ng serve -o
Chapter 3: Beginning Frontend Development with Angular CLI | 293
6. Open the browser and input http://localhost:4200/blogin in the URL address bar
to view the router in action.
You will obtain the following output:
If you click on one of the posts, it will navigate to the view-post page, as shown:
//[…]
// articleList Routes
app
.route("/articles")
.get(articleList.listAllArticles)
.post(userHandlers.loginRequired, articleList.createNewArticle);
app
.route("/article/:articleid")
.get(articleList.readArticle)
.put(articleList.updateArticle)
.delete(articleList.deleteArticle);
app
.route("/articles/by/:tag")
.get(articleList.listTagArticles);
app
.route("/auth/register")
.post(userHandlers.register);
app
.route("/auth/sign_in")
.post(userHandlers.signIn);
};
4. Update the server.js (inside the server folder) file with the following code:
'use strict'
const express = require("express");
const bodyParser = require("body-parser");
// db instance connection
require("./config/db");
var User = require('./api/models/userModel'),
298 | Appendix
jsonwebtoken = require("jsonwebtoken");
// API ENDPOINTS
var routes = require('./api/routes/articleListRoutes'); //importing route
Chapter 4: The MEAN Stack Security | 299
routes(app);
// LISTENING
app.listen(port, () => {
console.log('Server running at http://localhost:${port}');
});
5. Run the server using node server on the CLI and open Postman for testing.
6. Test for registration by typing in localhost:3000/auth/register on the address bar.
You will obtain the following output:
7. Attempt the login required path and post request on localhost:3000/articles. You
will obtain the following output:
9. Set the authentication key on the header and input the value in JWT token format,
as shown here:
10. Attempt the login required path and post request on localhost:3000/articles. You
will obtain the following output:
Thus, from the preceding outputs, it can be clearly observed that we have successfully
secured the RESTful API we developed in the previous exercise. We also managed to
provide admin access for creating, updating, and deleting data.
302 | Appendix
Activity 12: Creating a Login Page to Allow Authentication with Twitter Using
Passport Strategies
1. Create a package.json file and install express, body-parser, mongoose, passport-
twitter, and passport by running the following code:
npm init
npm install express body-parser mongoose passport-twitter passport
express-session -save
2. Create a server.js (using touch server.js on the CLI) file and import express and
body-parser using the following code:
const express = require("express");
const bodyParser = require("body-parser");
const session = require('express-session');
3. Create an Express application, assign a port number, and use the body-parser
middleware on the Express application using the following code:
const app = express();
const port = process.env.PORT || 4000;
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
4. Create a config folder using the following code:
mkdir config
5. Create a database by first creating a file named db.js (using touch db.js on the
CLI) in the config folder directory and input the following code:
const mongoose = require("mongoose");
const options = {
reconnectTries: Number.MAX_VALUE,
poolSize: 10
};
// Connect to the database using the following code
mongoose.connect(uri, options).then(
() => {
console.log("Database connection established!");
},
Chapter 4: The MEAN Stack Security | 303
err => {
console.log("Error connecting Database instance due to: ", err);
}
);
6. Create an api directory and three subfolders named controllers, models, and
routes using the following code:
mkdir api
mkdir – controllers && mkdir – models && mkdir – routes
7. Create a model file inside the controller directory (using touch userModel.js on
the CLI) and then create the schema and model:
const mongoose = require("mongoose"),
const Schema = mongoose.Schema;
const UserSchema = new Schema({
twitter : {
fullName : String,
email : String,
createdOn: {
type: Date,
default: Date.now
},
},
});
});
//[…]
} else {
done(null, user);
}
});
}
));
}
9. Create a route file inside the routes directory (using touch route.js) and then
create an exposed function as an exportable module that takes in app and passport
using the following code:
module.exports = function(app,passport) {
app.get('/auth/twitter',
passport.authenticate(' twitter ', {scope:"email"}));
}
10. Update the server.js file with the following code:
//db instance connection
require("./config/db"); app.get('/success', (req, res) => res.send("You
have successfully logged in"));
app.get('/error', (req, res) => res.send("error logging in"));
Chapter 4: The MEAN Stack Security | 305
@Injectable()
export class UserService {
private user = new BehaviorSubject<boolean>(false); //create user as
behavior
cast = this.user.asObservable(); //cast user as observable
constructor() { }
User(newUser) {
this.user.next(newUser);
}
}
5. Define the user and inject service into the app-header.component.ts class using
the following code:
import { Component, OnInit } from '@angular/core';
import { User } from '../user/user.model';
import { UserService } from '../user.service';
@Component({
Chapter 5: Angular Declarables, Bootstrapping, and Modularity | 307
selector: 'app-header',
templateUrl: './app-header.component.html',
styleUrls: ['./app-header.component.scss']
})
export class AppHeaderComponent implements OnInit {
user: User = {
firstName: 'Paul',
lastName: 'Oluyege'
};
isLoggedIn: boolean;
constructor(private usersService:UserService) { }
6. Define the user and inject service into the app-content.component.ts class using
the following code:
import { Component, OnInit } from '@angular/core';
import { User } from './../user/user.model';
import { UserService } from '../user.service';
@Component({
selector: 'app-content',
templateUrl: './app-content.component.html',
styleUrls: ['./app-content.component.scss']
})
export class AppContentComponent implements OnInit {
user: User = {
firstName: 'Paul',
lastName: 'Oluyege'
};
isLoggedIn: boolean;
constructor(private usersService: UserService) { }
7. Write ngOnInit(), login(), signup(), and logout() methods for the app-header.
component.ts class, as shown here:
ngOnInit() {
this.usersService.cast.subscribe(user=> this.isLoggedIn = user);
}
login() {
this.isLoggedIn = true;
this.usersService.User(this.isLoggedIn);
}
308 | Appendix
signup() {
this.isLoggedIn = true;
this.usersService.User(this.isLoggedIn);
}
logout() {
this.isLoggedIn = false;
this.usersService.User(this.isLoggedIn);
}
}
8. Write ngOnInit(), login(), signup(), and logout() methods for the app-content.
component.ts class, as shown here:
ngOnInit() {
this.usersService.cast.subscribe(user => this.isLoggedIn = user);
this.isLoggedIn = false;
}
login() {
this.isLoggedIn = true;
this.usersService.User(this.isLoggedIn);
}
logout() {
this.isLoggedIn = false;
this.usersService.User(this.isLoggedIn);
}
}
9. Update the app-header.component.html template using the following code:
<div class="app-header">
<div class="title" routerLink="/">Packt MEANStack Courseware</div>
<div class="profile-dropdown" *ngIf="isLoggedIn">
<div class="initials">
{{user.firstName.charAt(0)}}{{user.lastName.charAt(0)}}
</div>
</div>
<div class="action-btns" *ngIf="!isLoggedIn">
<button class="app-btn login-btn" (click)="login()">Login</button>
<button class="app-btn signup-btn" (click)="signup()">Signup</button>
</div>
Chapter 5: Angular Declarables, Bootstrapping, and Modularity | 309
.user-profile {
margin-bottom: 20px;
}
}
// app-header-component.scss
.app-header {
background: #e94e06;
height: 44px;
color: white;
padding: 6px 10px;
display: flex;
flex-direction: row;
align-items: center;
box-shadow: 0 3px 5px -1px rgba(0,0,0,.2), 0 6px 10px 0
rgba(0,0,0,.14), 0 1px 18px 0 rgba(0,0,0,.12);
.logo {
310 | Appendix
//[…]
}
}
}
}
12. Update the app.component.html template with the following code:
<app-header></app-header>
<app-content></app-content>
13. Run ng serve on the CLI and open the browser on localhost:4200 to see the output
and test it, as shown in the following screenshot:
7. Create a new JSON file named db.json and fill it with data from https://api.myjson.
com/bins/10v4ns. Then, launch this file on the json-server using the following
code:
json-server --watch db.json
8. Add a user-list service using the following code:
ng generate service service/users-list
9. Update the routes array in AppRoutingModule with the following code:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
{
path: '',
redirectTo: '',
pathMatch: 'full'
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
312 | Appendix
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UsersListRoutingModule { }
11. Configure the feature modules for users-detail-routing.module.ts using the
following code:
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { UsersDetailComponent} from './users-detail.component'
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UsersDetailRoutingModule { }
12. Create the user model and update it with the following code:
ng generate cl model/user
export class Users {
first: string;
second: string;
id: number;
Chapter 5: Angular Declarables, Bootstrapping, and Modularity | 313
phone:string;
picture:string;
email:string;
}
13. Write the service functions, as shown here:
import { Injectable } from '@angular/core';
import { Users } from "../model/users.model";
import {HttpClient} from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable()
export class UsersListService {
dataurl = "http://localhost:3000/results";
constructor(private http: HttpClient) { }
}
14. Update and inject the service into users-list.component.ts using the
following code:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Users } from "../model/users.model";
import { UsersListService } from "../service/users-list.service";
@Component({
selector: 'app-users-list',
templateUrl: './users-list.component.html',
styleUrls: ['./users-list.component.css']
})
export class UsersListComponent implements OnInit {
userlists: Users[];
constructor(private router: Router, private usersService:
UsersListService) { }
314 | Appendix
ngOnInit() {
this.usersService.getUsers().subscribe((user: any) => {
this.userlists = user;
console.log(this.userlists);
});
}
}
15. Update and inject service into users-details.component.ts, as shown here:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Router } from '@angular/router';
import { Users } from "../model/users.model";
import { UsersListService } from "../service/users-list.service";
@Component({
selector: 'app-users-detail',
templateUrl: './users-detail.component.html',
styleUrls: ['./users-detail.component.css']
})
export class UsersDetailComponent implements OnInit {
user: Users;
constructor(private router: Router, private route: ActivatedRoute,
private usersService: UsersListService) { }
ngOnInit(): void {
this.usersService.getUser(this.route.snapshot.params["id"]).
subscribe((user: any) => {
this.user = user;
});
}
Chapter 5: Angular Declarables, Bootstrapping, and Modularity | 315
[class*="entypo-"]:before {
font-family: 'entypo', sans-serif;
}
//[…]
.previous {
background-color: #f1f1f1;
color: black;
}
18. Update the app.component.html template, as shown here:
<router-outlet></router-outlet>
316 | Appendix
19. Go to localhost:4200 and then test for /userslist and /userslist/1, as follows:
Type localhost/4200/userslist on the browser address bar and observe the
number of resources requested, as shown here:
20. Type localhost/4200/userslist/1 on the browser address bar and observe the
number of resources requested, as shown here:
Figure 5.12: The resources that have been requested for a given user
318 | Appendix
Activity 15: Animating the Route Transition Between the Blog Post Page and
the View Post Page of the Blogging Application
1. Import the routing module into the app.routing.module file using the following
code:
import { BrowserAnimationsModule } from '@angular/platform-browser/
animations';
import { BrowserModule } from '@angular/platform-browser';
………………..
imports: [
BrowserModule,
BrowserAnimationsModule
],
2. Create an animation.ts file using touch, and then import animation classes and
define animation properties:
touch animation.ts
Here is the code for importing and defining animation classes:
import {trigger,state,style,animate,transition,query,animateChild,group}
from '@angular/animations';
@NgModule({
imports: [RouterModule.forChild(routes), SharedModule,
BrowserAnimationsModule],
exports: [RouterModule]
})
export class AppRoutingModule { }
4. Import the animation and router outlet of the root components class in the app.
component.ts file:
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router'
import { slideInAnimation } from './animation'
//[…]
prepareRoute(outlet: RouterOutlet) {
return outlet && outlet.activatedRouteData && outlet.
activatedRouteData['animation'];
}}
5. Update the root component template file (app.component.html) with the following
code:
<div id="main-content" class="bg-color-gray">
<app-header></app-header>
<div [@routeAnimations]="prepareRoute(outlet)" class="page-container
scene-main scene-main--fade_In">
<router-outlet #outlet="outlet"></router-outlet>
<app-footer></app-footer>
</div>
</div>
320 | Appendix
6. Run the application using ng serve –o on the CLI, and then test and observe the
page transition on the browser by typing in localhost:4200/blog/post in the
browser. You will obtain the following output:
Figure 6.12: Route transition animation between the Blog-Post and View-Post pages
Activity 16: Implementing Router Guard, Constant Storage, and Updating the
Application Functions of the Blogging Application
1. Define a constant for AuthService and ArticleService in the environment.ts file
with local URL's as shown in the following snippet:
export const environment = {
production: false,
articlesUrl: 'http://localhost:3000/articles',
articleUrl: 'http://localhost:3000/article/',
registerUrl: "http://localhost:3000/auth/register",
loginUrl: "http://localhost:3000/auth/sign_in"
};
Chapter 6: Testing and Optimizing Angular Applications | 321
2. Import and declare the environment in the auth.service.ts file as shown in the
following snippet:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'
import { Router } from '@angular/router'
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment'
@Injectable({
providedIn: 'root'
})
export class AuthService {
config = environment;
3. Import the BehaviorSubject class in the auth.service.ts file using the following
command:
import { BehaviorSubject } from 'rxjs';
4. Declare user as an instance of the BehaviorSubject class and then observe using
the Angular asObservable() method as shown in the following snippet:
private user = new BehaviorSubject<boolean>(false);
cast = this.user.asObservable();
5. Write an authentication function in the auth.service.ts file to check if any tokens
exist as shown in the following snippet:
constructor(private http: HttpClient,
private router: Router) { }
loginUser(user) {
322 | Appendix
logoutUser() {
// remove user from local storage to log user out
localStorage.removeItem('currentUser');
this.user.next(false);
this.router.navigate(['/blog'])
}
}
7. Create a new auth-guard.service.ts service file to implement the router guard as
shown in the following snippet:
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
@Injectable()
export class AuthGuardService implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(): boolean {
if (!this.auth.isAuthenticated()) {
this.router.navigate(['login']);
return false;
}
return true;
}
}
Chapter 6: Testing and Optimizing Angular Applications | 323
8. Apply the router guard service to the app.routing.module.ts route file as shown in
the following snippet:
@NgModule({
imports: [RouterModule.forChild(routes), SharedModule,
BrowserAnimationsModule],
exports: [RouterModule]
})
export class AppRoutingModule { }
9. Import and declare the environment in the article.service.ts file as shown in the
following snippet:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Post } from '../posts'
import { Observable } from 'rxjs';
import { environment } from '../../environments/environment'
@Injectable()
getArticles(): Observable<Post> {
this.article = this.http.get<Post>('${this.config.articlesUrl}');
return this.article;
}
//[…]
//blog-home.component.ts
import { Component, OnInit } from '@angular/core';
import { ArticleService } from '../service/article.service';
import { AuthService } from '../service//auth.service';
@Component({
selector: 'app-blog-home',
templateUrl: './blog-home.component.html',
styleUrls: ['./blog-home.component.css']
})
export class BlogHomeComponent implements OnInit {
//[…]
}
logOut() {
this.authService.logoutUser()
}
//blog-home.component.html
<app-title-header></app-title-header>
<div *ngIf='isLoggedIn'>
<a routerLink="/create" style="float: left;margin:-50px 0px 0px
100px;background-color: orangered;color: white" class="button btn">New
Post</a>
<button (click)="logOut()" style="float: right;margin:-50px 100px 0px
0px;background-color: black;color: white" class="button btn">Logout</
button>
</div>
//[…]
</article>
326 | Appendix
</div>
</div>
</div>
</div>
</div>
//view-post.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { ArticleService } from '../service/article.service';
@Component({
selector: 'app-view-post',
templateUrl: './view-post.component.html',
styleUrls: ['./view-post.component.css']
})
Chapter 6: Testing and Optimizing Angular Applications | 327
</div>
14. Update the login component class (login.component.ts) and the template (login.
component.html) using the following code:
The code for updating the login.component.ts file is as follows:
forms';
import { AuthService } from '../service//auth.service';
import { Router } from '@angular/router'
import { first } from 'rxjs/operators';
//[…]
}
<app-title-header></app-title-header>
<div class="" style="padding-bottom: 3rem!important;">
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
<h3 class="mb-0" style="text-align:center" class="mb-0">Login</h3>
<div class="card-header">
<a href="#" class="btn" style="float:left;margin-
right:10px;color:darkblue;border: 1px solid darkblue">
<i class="fa fa-facebook-official"></i>
Facebook
//[…]
</div>
<div *ngIf="success" class="alert alert-success">{{success}}</
div>
<div *ngIf="error" class="alert alert-danger">{{error}}</div>
</form>
</div>
<!--/card-block-->
</div>
<!-- /form card login -->
</div>
Chapter 6: Testing and Optimizing Angular Applications | 329
</div>
</div>
15. Update the register component class (register.component.ts) and the template
(register.component.html) using the following code:
The code for updating the register.component.ts is as follows:
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
//[…]
navigateToLogin() {
this.router.navigate(['/login']);
}
}
<app-title-header></app-title-header>
<div style="padding-bottom: 3rem!important;">
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
<h3 class="mb-0" style="text-align:center">Register Admin
330 | Appendix
User</h3>
//[…]
</div>
</div>
</div>
16. Update the create component class (create.component.ts) and the template
(create.component.html) using the following code snippets:
The code for updating the create.component.ts file is as follows:
//[…]
navigateToBlogHome() {
this.router.navigate(['/blog']);
}
}
<app-title-header></app-title-header>
<div style="padding-bottom: 3rem!important;">
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
<h3 style="text-align:center" class="mb-0">Create Post</h3>
Chapter 6: Testing and Optimizing Angular Applications | 331
<div class="card-header">
</div>
<div class="card-body">
<form (ngSubmit)="postForm.form.valid && onSubmit()"
#postForm="ngForm" novalidate>
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title"
[(ngModel)]="model.title" name="title" #title="ngModel"
[ngClass]="{ 'is-invalid': postForm.submitted && title.
invalid }" required>
<div *ngIf="postForm.submitted && title.invalid"
class="alert alert-danger">
Title is required
</div>
</div>
//[…]
<div *ngIf="error" class="alert alert-danger">{{error}}</div>
</form>
</div>
</div>
</div>
</div>
</div>
17. Update the edit component class (edit.component.ts) and the template (edit.
component.html) using the following code snippets:
The code for updating the edit.component.ts file is as follows:
@Component({
selector: 'app-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.css']
})
//[…]
navigateToBlogHome() {
this.router.navigate(['/blog']);
}
}
<app-title-header></app-title-header>
<div style="padding-bottom: 3rem!important;">
<div class="row">
<div class="col-md-6 mx-auto">
<!-- form card login -->
<div class="card rounded-0">
<h3 style="text-align:center" class="mb-0">Edit Post</h3>
<div class="card-header">
</div>
<div class="card-body">
<form (ngSubmit)="postForm.form.valid && onSubmit()"
#postForm="ngForm" novalidate *ngIf="article.length != 0">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title"
[(ngModel)]="article.title" name="title" #title="ngModel"
[ngClass]="{ 'is-invalid': postForm.submitted && title.
invalid }" required>
<div *ngIf="postForm.submitted && title.invalid"
class="alert alert-danger">
Title is required
</div>
</div>
//[…]
Chapter 6: Testing and Optimizing Angular Applications | 333
</form>
</div>
</div>
</div>
</div>
</div>
18. Run ng serve to test the '/blog' route before and after logging in.
You will obtain the following output once you test the '/blog' route before logging
in:
Figure 6.13: The output obtained for the test on the '/blog' route before logging in.
334 | Appendix
When you test the '/blog' route after logging in, you will obtain the following
output:
Figure 6.14: The output obtained for the test on the '/blog' route after logging in.
Chapter 6: Testing and Optimizing Angular Applications | 335
Activity 17: Performing Unit Testing on the App Root Component and Blog-
Post Component
1. Open the root component test file, app.components.spec.ts, and import the
modules, as shown:
import { TestBed, async,ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { Component, OnInit, DebugElement } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { RouterLinkWithHref } from '@angular/router';
import { By } from '@angular/platform-browser';
2. Mock the app-header, router-outlet, and app-footer components into the app.
components.spec.ts file the with the following code:
@Component({selector: 'app-header', template: ''})
class HeaderStubComponent {}
4. Write the assertion and matcher functions to evaluate true and false conditions:
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
it('should have a link to /', () => {
const fixture = TestBed.createComponent(AppComponent);
const debugElements = fixture.debugElement.queryAll(By.
directive(RouterLinkWithHref));
const index = debugElements.findIndex(de => {
return de.properties['href'] === '/';
});
expect(index).toBeGreaterThanOrEqual(-1);
});
});
5. Open the blog-home.component.spec.ts file (in the blog-home folder) and import
the modules:
import { async, ComponentFixture, TestBed, inject } from '@angular/core/
testing';
import { HttpClientModule } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { BlogHomeComponent } from './blog-home.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { ArticleService } from '../service/article.service';
6. Mock the app-title-header components in the blog-home.component.spec.ts file,
as shown:
@Component({ selector: 'app-title-header', template: '' })
class TitleHeaderStubComponent { }
7. Write the suits functions in the blog-home.component.spec.ts file, as shown:
describe('BlogHomeComponent', () => {
let component: BlogHomeComponent;
let fixture: ComponentFixture<BlogHomeComponent>;
let testBedService: ArticleService;
beforeEach(async(() => {
// refine the test module by declaring the test component
TestBed.configureTestingModule({
Chapter 6: Testing and Optimizing Angular Applications | 337
beforeEach(() => {
// create component and test fixture
fixture = TestBed.createComponent(BlogHomeComponent);
// AuthService provided to the TestBed
testBedService = TestBed.get(ArticleService);
// get test component from the fixture
component = fixture.componentInstance;
fixture.detectChanges();
8. Write the assertion and matcher functions to evaluate true and false, as shown:
it('should create', () => {
expect(component).toBeTruthy();
});
9. Run ng test in the command line. You will obtain an output similar to the
following:
As can be seen in the preceding output, we have successfully performed e2e testing on
the student app.
>
Index
About
All major keywords used in this book are captured alphabetically in this section. Each one is
accompanied by the page number of where they appear.
A F ngoninit: 84, 181, 192
ng-repeat: 85
adapter: 68 failover: 5 ngstyle: 88
agnostic: 8, 143 fakeasync: 235 ngsubmit: 100, 104
algorithm: 127 fakeaysnc: 235 ngswitch: 86
analytics: 40 frameguard: 120 ng-valid: 101
app-footer: 245 framework: 2, 4, 6-9,
app-header: 181-182, 245
appmodule: 80, 84, 100,
36, 40, 42-43, 61, 65,
72, 76, 82, 224-225,
O
172, 175, 177, 182-183, 233, 240, 246 obersver: 179
185-186, 191, 203, 230
approach: 5, 105, 112, 143
app-root: 80, 83, 205, 243
G P
approutes: 110, 210 getitem: 216-217 parallel: 187
appurl: 240 getpost: 239 parameter: 16, 110, 132,
app-wide: 184, 187, 189 getpromise: 171 148, 160, 169, 235, 237
asyncpipe: 168 gettext: 243 plugin: 223
auth-guard: 217-218 getting: 11, 18-19, 40, 45, python: 2, 10
automated: 10 61, 76, 79, 94, 110, 140,
168, 202, 233, 240
R
B
bcryptjs: 131-134,
H resolver: 187
W
widget: 187, 198
X
x-frame: 120
x-prompt: 213