Professional Documents
Culture Documents
ng new my-app
Would you like to add Angular routing? (y/N) N
Which stylesheet format would you like to use? (Use arrow keys)
accept the default choice, CSS, and press Enter.
Navigate to the newly created folder and start your application with the following
command:
ng serve
Open this projecr using vs code
src folder
-----------
app : Contains all the Angular-related files of the application. You
interact with this folder most of the time during development.
assets : Contains static assets such as fonts, images, and icons.
favicon.ico : The icon displayed in the tab of your browser, along with the page
title.
index.html : The main HTML page of the Angular application.
main.ts : The main entry point of the Angular application.
styles.css : Contains application-wide styles. These are CSS styles that apply
globally to the Angular application. The extension of this file depends on the
stylesheet format you choose when creating the application.
Components
-------------
When we talk about files that start with "app.component" in Angular, we're usually
referring to a part of a web application that does something specific.
Imagine you're building a house of cards. Each card in the house has a special
design and place. These "app.component" files are like the cards. They have
instructions on how they should look and fit into the house.
Each Angular application has a main HTML file, named index.html, that exists inside
the src folder and contains the following body HTML element:
<body>
<app-root></app-root>
</body>
The app-root tag is used to identify the main component of the application and acts
as a container to display its HTML content. It instructs Angular to render the
template of the main component inside that tag.
When the Angular CLI builds an Angular app, it first parses the index.html file and
starts identifying HTML tag elements inside the body tag. An Angular application is
always rendered inside the body tag and comprises a tree of components. When the
Angular CLI finds a tag that is not a known HTML element, such as app-root, it
starts searching through the components of the application tree.
Modules
-----------
The main module of our application is a TypeScript file that acts as a container
for the main component. The component was registered with this module upon creating
the Angular application; otherwise, the Angular framework would not be able to
recognize and load it. A typical Angular application has at least a main module
called AppModule by convention.
The main module is also the starting point of an Angular application. The startup
method of an Angular application is called bootstrapping, and it is defined in the
main.ts file inside the src folder:
The main task of the bootstrapping file is to define the module that will be loaded
at application startup. It calls the bootstrapModule method of the
platformBrowserDynamic method and passes AppModule as the entry point of the
application.
Open the application with your browser at http://localhost:4200 and notice the text
next to the rocket icon that reads my-app app is running! The word my-app that
corresponds to the name of our application comes from a variable declared in the
TypeScript file of the main component. Open the app.component.ts file and locate
the title variable:
Nx Console
-----------
Nx Console is an interactive UI for the Angular CLI that aims to assist developers
who are not very comfortable with the command-line interface or do not want to use
it. It works as a wrapper over Angular CLI commands, and it helps developers
concentrate on delivering outstanding Angular applications instead of trying to
remember the syntax of every CLI command they want to use.
Nx Console includes most of the Angular CLI commands. The user interface allows
developers to use them without remembering the available options each command
supports.
Using this extension, you can easily spot the type of Angular files in a project,
such as components and modules, and increase developer productivity, especially in
large projects with lots of files.
EditorConfig
-------------
VS Code editor settings, such as indentation or spacing, can be set at a user or
project level. EditorConfig can override these settings using a configuration file
called .editorconfig, which can be found in the root folder of an Angular CLI
project. You can define unique settings in this file to ensure the consistency of
the coding style across your team.
Angular Evergreen
-----------------
The stability of the Angular platform is heavily based on the regular release
cycles according to the following schedule:
The Angular Evergreen extension helps us follow the previous schedule and keep our
Angular projects up to date. It provides us with a unique user interface containing
information about the current Angular version of our project and actions we can
take to update it. Updating an Angular project can also be accomplished using a
custom menu that appears if we right-click on the angular.json file of the
workspace.
TypeScript
-------------
we will be using TypeScript 4.8 as it is supported by Angular 15.
String :We can use single or double quotes for the value of a string variable. We
can define multiline text strings with support for text interpolation using
placeholder variables and backticks.
Declaring Variable
------------------
let, which denotes that the scope of the variable is the nearest enclosing block
(either a function, for loop, or any other enclosing statement).
const indicates that the value of the declared variable cannot be changed once it’s
set.
const obj = {
a: 3
};
obj.a = 4;
Number
The number type is probably the other most widespread primitive data type, along
with string and boolean:
Boolean
The boolean type defines a variable that can have a value of either true or false:
Custom types
In TypeScript, you can come up with your own type if you need to by using the type
keyword in the following way:
Enum
The enum type is a set of unique numeric values that we can represent by assigning
user-friendly names to each one. Its use goes beyond assigning an alias to a
number. We can use it to list the variations that a specific type can assume in a
convenient and recognizable way. It begins numbering members, starting at 0 unless
explicit numeric values are assigned to them:
In the preceding code, if we inspect the variable myCar, we will see that it
returns the value 1, which is the index of Cadillac. As we mentioned already, we
can also assign custom numeric values like the following:
enum StackingIndex {
None = 0,
Dropdown = 1000,
Overlay = 2000,
Modal = 3000
};
const mySelectBoxStacking: StackingIndex = StackingIndex.Dropdown;
One last point worth mentioning is the possibility to look up a member mapped to a
given numeric value:
It should also be mentioned that from TypeScript 2.4 and onward, it is possible to
assign string values to enums. It is a technique preferred in Angular projects
because of its extended support in template files.
Void
The void type represents the absence of a type, and its use is constrained to
annotating functions that do not return an actual value:
Anonymous Functions
const sayHello = function(name: string): string {
return 'Hello, ' + name;
}
Optional parameters
----------------------
Parameters are defined as optional by adding the character ? after the parameter
name
greetMe('John');
add('some string');
add('some string', 3.14);
Both versions are valid. Be aware that optional parameters should be placed last in
a function signature
Default Parameters
----------
function greetMe(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}`;
}
Like optional parameters, default parameters must be put right after the required
parameters in the function signature.
Rest parameters
--------------
prefixed by an ellipsis (…) and typed as an array
Rest parameters are beneficial when we don’t know how many arguments will be passed
in.
Function overloading
---------------------
function hello(names: string): string
function hello(names: string[]): string
function hello(names: any, greeting?: string): string {
let namesArray: string[];
if (Array.isArray(names)) {
namesArray = names;
} else {
namesArray = [names];
}
if (!greeting) {
greeting = 'Hello';
}
return greeting + ', ' + namesArray.join(' and ') + '!';
}
Arrow functions
-------------------
ES6 introduced the concept of fat arrow functions (also called lambda functions in
other languages)
If the function signature contains more than one argument, we need to wrap them all
between parentheses
const add = (x, y) => x + y;
Arrow functions can also contain statements by wrapping the whole implementation in
curly braces
const addAndDouble = (x, y) => {
const sum = x + y;
return sum * 2;
}
A spread parameter uses the same ellipsis syntax as the rest parameter but is used
inside the body of a function
const newItem = 3;
const oldArray = [1, 2];
const newArray = [...oldArray, newItem];
We add an item to an existing array without changing the old one. The old array
still contains 1, 2, whereas the new array contains 1, 2, and 3. The current
behavior is called immutability, which means not changing the old array but rather
creating a new state from it
Template strings
-------------------
Template strings are all about making your code clearer
const url = 'http://path_to_domain' +
'path_to_resource' +
'?param=' + parameter +
'¶m2=' + parameter2;
In above code readability is not good so to overcome this, we can use template
strings in the following way
const url =
`${baseUrl}/${path_to_resource}?param=${parameter}¶m2={parameter2}`;
Generics
---------------
Generics are expression indicating a general code behavior that we can employ,
regardless of the data type. They are often used in collections because they have
similar behavior, regardless of the type. They can, however, be used on other
constructs such as methods. The idea is that generics should indicate if you are
about to mix types in a way that isn’t allowed
method<string>(1);
We specify that T should be a string, but we insist on passing it a value as a
number. The compiler clearly states that this is not correct. You can, however, be
more specific on what T should be. You can make sure that it is an array type so
that any value you pass must adhere to this
interface Shape {
area(): number;
}
class Square implements Shape {
area() { return 1; }
}
class Circle implements Shape {
area() { return 2; }
}
function allAreas<T extends Shape>(...args: T[]): number {
let total = 0;
args.forEach (x => {
total += x.area();
});
return total;
}
allAreas(new Square(), new Circle())
Optional chaining
------------------
The optional chaining in TypeScript is a powerful feature that can help us with
refactoring and simplifying our code. In a nutshell, it can guide our TypeScript
code to ignore the execution of a statement unless a value has been provided
somewhere in that statement. Let’s see optional chaining with an example
In the preceding snippet, we create a square object using the Square class of the
previous section. Later, we read the value of the area method by making sure that
the object has a value set before reading it
The previous snippet is a precautionary step in case our object has been modified
in the meantime. If we do not check the object and it has become undefined, the
compiler will throw an error. However, we can use optional chaining to make the
previous statement more readable:
The character ? after the square object ensures that the area method will be
accessed only if the object has a value. The case where optional chaining shines is
in more complicated scenarios with much more values to check, such as the
following:
Nullish coalescing
------------------------
The nullish coalescing feature in TypeScript looks similar to the optional chaining
we learned about in the previous section. However, it is more related to providing
a default value when a variable is not set. Consider the following example that
assigns a value to the mySquare variable only if the square object exists:
Anatomy of a class
-----------------
Property members are declared first in a class, then a constructor, and several
other methods and property accessors follow. None contain the reserved function
keyword, and all the members and methods are annotated with a type, except the
constructor.
class Car {
private distanceRun: number = 0;
private color: string;
getGasConsumption(): string {
return this.isHybrid ? 'Very low' : 'Too high!';
}
Members: Any instance of the Car class will contain three properties: color typed
as a string, distanceRun typed as a number, and isHybrid as a boolean. Class
members will only be accessible from within the class itself. If we instantiate
this class, distanceRun, or any other member or method marked as private, won’t be
publicly exposed as part of the object API.
We need to prefix the constructor parameter with an access modifier such as private
or public. As we learned when analyzing functions, we can define rest, optional, or
default parameters, as depicted in the previous example with the color argument,
which falls back to red when it is not explicitly defined.
Static members: Members marked as static are associated with the class and not with
the object instances of that class. We can consume static members directly without
having to instantiate an object first. Static members are not accessible from the
object instances, which means they cannot access other class members using the this
keyword. These members are usually included in the class definition as helper or
factory methods to provide a generic functionality unrelated to any specific object
instance.
Interfaces
--------------
As applications scale and more classes are created, we need to find ways to ensure
consistency and rule compliance in our code. One of the best ways to address the
consistency and validation of types is to create interfaces. An interface is a code
contract that defines a particular schema. Any artifacts such as classes and
functions that implement an interface should comply with this schema. Interfaces
are beneficial when we want to enforce strict typing on classes generated by
factories or when we define function signatures to ensure that a particular typed
property is found in the payload.
interface Vehicle {
make: string;
}
Any class that implements the preceding interface must contain a member named make,
which must be typed as a string:
Interfaces are also beneficial for defining the minimum set of members any artifact
must fulfill, becoming an invaluable method to ensure consistency throughout our
code base.
It is important to note that interfaces are not used just to define minimum class
schemas but any type out there. This way, we can harness the power of interfaces by
enforcing the existence of specific fields that are used later on as function
parameters, function types, types contained in specific arrays, and even variables.
interface Exception {
message: string;
id?: number;
}
In the following snippet, we define the contract for our future class with a typed
array and a method, with its returning type defined as well:
interface ErrorHandler {
exceptions: Exception[];
logException(message: string, id?: number): void
}
We can also define interfaces for standalone object types, which is quite useful
when we need to define templated constructors or method signatures:
interface ExceptionHandlerSettings {
logAllExceptions: boolean;
}
Let’s bring them all together by creating a custom error handler class:
constructor(settings: ExceptionHandlerSettings) {
this.logAllExceptions = settings.logAllExceptions;
}
The preceding class manages an internal array of exceptions. It also exposes the
logException method to log new exceptions by saving them into an array. These two
elements are defined in the ErrorHandler interface and are mandatory.
So far, we have seen interfaces as they are used in other high-level languages, but
interfaces in TypeScript are stronger and more flexible; let’s exemplify that. In
the following code, we’re declaring an interface, but we’re also telling the
TypeScript compiler to treat the instance variable as an A interface:
interface A {
a: number;
}
const instance = { a: 3 } as A;
instance.a = 5;
Imagine that you are building an order module. You have logic in your order module,
and you know that, at some point, you will need to talk to a database service. You
come up with an interface for the database service, and you defer the
implementation of this interface until later. At this point, a mocking library can
help you create a mock instance from the interface. Your code, at this point, might
look something like this:
interface DatabaseService {
save(order: Order): void
}
class Order {}
class OrderProcessor {
process(order) {
this.databaseService.save(order);
}
}
let orderProcessor = new OrderProcessor(mockLibrary.mock<DatabaseService>());
orderProcessor.process(new Order());
Let’s reiterate that the reason for mocking anything in your code is to make it
easier to test. Let’s assume your code looks something like this:
class Auth {
srv: AuthService = new AuthService();
execute() {
if (srv.isAuthenticated()) {}
else {}
}
}
A better way to test this is to make sure that the Auth class relies on
abstractions, which means that the AuthService should be created elsewhere and that
we use an interface rather than a concrete implementation. So, we should modify our
code so that it looks like this:
interface AuthService {
isAuthenticated(): boolean;
}
class Auth {
constructor(private srv: AuthService) {}
execute() {
if (this.srv.isAuthenticated()) {}
else {}
}
}
It would, however, become quite tedious to write a mock version of every dependency
that you wanted to mock. Therefore, mocking frameworks exist in most languages. The
idea is to give the mocking framework an interface from which it would create a
concrete object. You would never have to create a mock class, as we did previously,
but that would be something that would be up to the mocking framework to do
internally.
Class inheritance
-----------------
Just like an interface can define a class, it can also extend the members and
functionality of other classes. We can make a class inherit from another by
appending the extends keyword to the class name, including the name of the class we
want to inherit its members from:
In the preceding class, we extend from a parent Car class, which already exposes a
member called make. We can populate the members by the parent class and execute
their constructor using the super method, which points to the parent constructor.
We can also override methods from the parent class by appending a method with the
same name. Nevertheless, we can still execute the original parent’s class methods
as it is still accessible from the super object.
Classes and interfaces are basic features of the TypeScript language. As we will
see in the following section, decorators enhance the use of classes in an
application by extending them with custom functionality.
Decorators
------------
Decorators are a very cool functionality that allows us to add metadata to class
declarations for further use. By creating decorators, we are defining special
annotations that may impact how our classes, methods, or functions behave or simply
altering the data we define in fields or parameters. They are a powerful way to
augment our type’s native functionalities without creating subclasses or inheriting
from other types. It is, by far, one of the most exciting features of TypeScript.
It is extensively used in Angular when designing modules and components or managing
dependency injection, as we will learn later in Chapter 6, Managing Complex Tasks
with Services.
The @ prefix recognizes decorators in their name, which are standalone statements
above the element they decorate. We can define up to four different types of
decorators, depending on what element each type is meant to decorate:
Class decorators
Property decorators
Method decorators
Parameter decorators
Custom decorators
Class decorators
----------------------
Class decorators allow us to augment a class or perform operations on its members.
The decorator statement is executed before the class gets instantiated. Creating a
class decorator requires defining a plain function, whose signature is a pointer to
the constructor belonging to the class we want to decorate. The formal declaration
defines a class decorator as follows:
Let’s see how we can use a class decorator through a simple example:
In the preceding snippet, we use the banana method, which was not initially defined
in the FruitBasket class. However, we decorate it with the @Banana decorator. It is
worth mentioning, though, that this won’t compile. The compiler will complain that
FruitBasket does not have a banana method, and rightfully so because TypeScript is
typed. So, at this point, we need to tell the compiler that this is valid. So, how
do we do that? One way is that, when we create our basket instance, we give it the
type any:
The compiler will not complain about the method now, and the compilation of our
code will complete successfully.
If we run the preceding code, the browser console will print the following message:
Property decorators
-------------------
Property decorators are applied to class fields and are defined by creating a
function whose signature takes two parameters:
Possible use cases for this decorator are logging the values assigned to class
fields when instantiating objects or reacting to data changes in such fields. Let’s
see an actual example that showcases both behaviors:
Parameter decorators do not modify the value of the parameters decorated or alter
the behavior of the methods or constructors where these parameters live. They
usually log or prepare the object created to implement additional layers of
abstraction or functionality through higher-level decorators, such as a method or
class decorator. Common use case scenarios for this encompass logging component
behavior or managing dependency injection.
Advanced types
-----------------
We have already learned about some basic types in the TypeScript language that we
usually meet in other high-level languages. In this section, we’ll look at some
advanced types that will help us during Angular development.
Partial
The Partial type is used when we want to create an object from an interface but
include some of its properties:
interface Hero {
name: string;
power: number;
}
const hero: Partial<Hero> = {
name: 'Boothstomper'
}
In the preceding snippet, we can see that the hero object does not include power in
its properties.
Record
------
Some languages, such as C#, have a reserved type when defining a key-value pair
object or dictionary, as it is known. In TypeScript, there is no such thing. If we
want to define such a type, we declare it as follows:
interface Hero {
powers: {
[key: string]: number
}
}
However, the preceding syntax is not clear. In a real-world scenario, interfaces
have many more properties. Alternatively, we can use the Record type to define the
interface:
interface Hero {
powers: Record<string, number>
}
It defines the key as a string, which is the name of the power in this case, and
the value, which is the actual power factor, as a number.
Union
--------
We’ve already learned about generics and how they can help us when we want to mix
types. A nice alternative, when we know what the possible types are, is the Union
type:
interface Hero {
name: string;
powers: number[] | Record<string, number>;
}
And that wraps up advanced types. As we learned, the TypeScript typing system is
very flexible and allows us to combine types for more advanced scenarios.
In the following section, we will learn how to use modules with TypeScript.
Modules
As our applications scale and grow, there will be a time when we need to organize
our code better and make it sustainable and reusable. Modules are a great way to
accomplish these tasks, so let’s look at how they work and how we can implement
them in our application.
A module works at a file level, where each file is the module itself, and the
module name matches the filename without the .ts extension. Each member marked with
the export keyword becomes part of the module’s public API. Consider the following
module that is declared in a my-service.ts file:
The ./my-service path is relative to the location of the file that imports the
module. If the module exports more than one artifact, we place them inside the
curly braces one by one, separated with a comma: