Theme NexT works best with JavaScript enabled
0%

Angular Manual


IMPORTANT:
Some of the content here is a personal summary/abbreviation of contents on the official documentation and the tutorial on Scrimba. Feel free to refer to the official site if you think some of the sections written here are not clear.


Installing Angular

To install angular, it is recommended that you first install nvm and node: https://github.com/nvm-sh/nvm

Once you have those two installed, you can install theAngular Cli: https://angular.io/guide/setup-local

  • With Angular Cli, it allows you to generate much content with some simple commands. For example, the ng generate/ng g command.
  • For more information on Angular Cli, please visit https://cli.angular.io/

Introduction to Angular

Angular is a platform and framework for building single-page client applications using HTML and TypeScript. Angular is written in TypeScript. It

implements core and optional functionality as a set of TypeScript libraries that you import into your apps.

Creating an Angular Project

To create a new angular project, you can use ng new <projectName>, which will generate a new folder with name <projectName> under your current directory, in which your project source code would go.

Once you have created your first project with ng new <projectName>, you will see that it generated many files as shown below:

project-structure.png

A high level overview of the usages of those file would be:

File Usage
tslint.js Used to control/standardize certain code/project properties across the team
tsconfig.json Controls the compilation settings for ts (type-script) files
package.json Controls the dependencies that our project has
package-lock.json Controls the dependencies that should be immutable
karma.conf.js Used for unit testing your code
.gitignore Controls which files would be ignored for your git version control
.editorconfig Used for customizing coding format in your editors (some editors don’t use it)
.angular-cli.json Controls packaging/deployment properties of your entire project
src Directory where your source code should live

Basic Building Blocks of an Angular App

On a very high level, your application would use the following building blocks:

angular-building-block.png from https://scrimba.com/course/gyourfirstangularapp/enrolled

  • Components
    • UI code, how your web application looks
  • Services
    • Frontend/Backend service, which does the computing
  • Modules
    • A container assembling the above two

An example would be:

angular-project-example.png https://scrimba.com/course/gyourfirstangularapp/enrolled

Component

A component in angular is made up of some html templates and some source codes (these two combined is also called a view), which can also include/use certain services.

  • Components define views, which are sets of screen elements that Angular can choose among and modify according to your program logic and data.
  • Components use services, which provide specific functionality not directly related to views. Service providers can be injected into components as dependencies, making your code modular, reusable, and efficient.

Every Angular application has at least one component, the root component that connects a component hierarchy with the page document object model (DOM). Each component defines a class that contains application data and logic, and is associated with an HTML template that defines a view to be displayed in a target environment.

Source: https://angular.io/guide/architecture

A component typically has a selector, which would look like <app-customer></app-customer> (in this case, app-customzer would be your component name). This is used for Angular in its html to know which component to go to.

component.png from https://angular.io/guide/architecture

Component Code

Codes in a component typically contains the following elements:

  • Imports
    • imports necessary dependencies for your code
  • Decorators
    • Metadata about your component
  • Class
    • Actual code for your UI

For example:

Your imports could be:

1
2
import { Component } from `@angular/code`;	/** This is provided by Angular */
import { DataService } from `../services/data.service` ; /** This is not provided by Angular */

Your decorators could be:

1
2
3
4
5
@Component({
selector: 'app-customers' // how your selector can identify this component
// this is usually done in a format of <projectName>-<componentnName>
templateUrl: './customers.component.html' // the web template that this component will be deployed
})

You define a component’s application logic - what it does to support the view - inside a class. The class interacts with the view through an API of properties and methods:

1
2
3
export class CustomersComponent{	// export makes this component available for other components
...
}

A simple working example could be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// in your app.component.ts
import { Component } from '@angular/core';

@Component({
selector: 'app-root',
// below demonstrates inline data binding
// notice template instead of templateUrl is used
template: `
<h1> {{title}} </h1>
`,
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'test-app'; // bound to here
}

And of course, there needs to be a module to contain/register that component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// in your app.module.ts
import { BrowserModule } from '@angular/platform-browser'; // this is used later for data-binding and directives
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; // imports the class AppComponent

@NgModule({
declarations: [
AppComponent
], // declaring what is inside this module
imports: [
BrowserModule,
AppRoutingModule
], // importing other modules
providers: [],
bootstrap: [AppComponent] // defines which component to load up in this module by default
// to go/be rendered into the webpage
})
export class AppModule { }

Now, if you run ng serve -o, you will see your server up and running.

Note:

  • ng serve compiles and serves up your application. ng serve -open or ng serve -o will do ng serve and then also opens the web browser with your project’s URL address.

Basic Mechanism

How doesAngular node where to look for when it finds <app-root> tag in the main.html?

  1. Angular looks at the main.ts file:
1
2
3
4
5
6
7
8
9
10
11
12
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

if (environment.production) {
enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule) // loads AppModule first, this is also the ROOT module
.catch(err => console.error(err));
  1. Now, in that module, Angular will be told to load AppComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; // imports the class AppComponent

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
], // importing other modules
providers: [],
bootstrap: [AppComponent] // load this on default (if nothing happens on that page)
})
export class AppModule { }

Creating a Component

To create a component, you can use the command ng g component <component-name>. This will generate a folder with name <component-name> along with four files:

  • <component-name>.component.css
    • the css for the below html page
  • <component-name>.component.html
    • you can imagine this as a sub-html page that could be injected later into other html
  • <component-name>.component.spec.ts
  • <component-name>.component.ts
    • where your component code goes

It is recommended that for each new feature, you use a module to manage those components. Therefore, it is recommended that you add:

  • <component-name>.module.ts

For example, you can populate your <component-name>.component.ts with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Component, OnInit } from '@angular/core';

// more on metadata covered in the next section
@Component({
selector: 'app-customers', // so later we use <app-customer> tag to inject it
templateUrl: './customers.component.html'
})

export class CustomersComponent implements OnInit {
title: string;
people: any[];

constructor() {}

ngOnInit() {
this.title = 'Customers';
// populates the people data
// this will not be used yet
this.people = [
{ id: 1, name: 'john Doe', city: 'Phoenix', orderTotal: 9.99, customerSince: new Date(2014, 7, 10) },
{ id: 2, name: 'Jane Doe', city: 'Chandler', orderTotal: 19.99, customerSince: new Date(2017, 2, 22)},
{ id: 3, name: 'Michelle Thomas', city: 'Seattle', orderTotal: 99.99, customerSince: new Date(2002, 10, 31)},
{ id: 4, name: 'Jim Thomas', city: 'New York', orderTotal: 599.99, customerSince: new Date(2002, 10, 31)},
];
}
}

The template html could be as simple as:

1
2
<h1>{{ title }}</h1>
<br/>

Now, importantly, your <component-Name>.module.ts could be:

1
2
3
4
5
6
7
8
9
10
11
import { NgModule }      from '@angular/core';
import { CommonModule } from '@angular/common';

import { CustomersComponent } from './customers.component';

@NgModule({
imports: [ CommonModule ],
declarations: [ CustomersComponent ],
exports: [ CustomersComponent ] // necessary step
})
export class CustomersModule { }

Now, to make this available, we would need to include this module in our root module - app.module.ts in order for Angular to see our work:

1
2
3
4
5
6
7
8
9
10
11
12
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { CustomersModule } from './customers/customers.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [ BrowserModule, CustomersModule ], // imported here. Since CustomersComponent is exported, it is also available
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

Lastly, your app.component.ts for the displayed web page would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { Component, OnInit } from '@angular/core';

@Component({
selector: 'app-root',
// notice that the rendered html for app-customers with all our work above would be injected here
template: `
<h1> {{title}} </h1>
<app-customers></app-customers>
`
})
export class AppComponent implements OnInit {
title = "Test Title"
constructor() { }

ngOnInit() {

}

}

Component Metadata

The metadata for a component tells Angular where to get the major building blocks that it needs to create and present the component and its view. In particular, it associates a template with the component, either directly with inline code, or by reference. Together, the component and its template describe a view.

In addition to containing or pointing to the template, the @Component metadata configures, for example, how the component can be referenced in HTML and what services it requires.

For example:

1
2
3
4
5
6
7
8
9
@Component({
selector: 'app-hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ] // needs the service from HeroService
})
export class HeroListComponent implements OnInit {
constructor(private heroService: HeroService) { } // injected service
/* . . . */
}

This example shows some of the most useful @Component configuration options:

  • selector: A CSS selector that tells Angular to create and insert an instance of this component wherever it finds the corresponding tag in template HTML.
    • For example, if an app’s HTML contains <app-hero-list></app-hero-list>, then Angular inserts an instance of the HeroListComponent view between those tags.
  • templateUrl: The module-relative address of this component’s HTML template. Alternatively, you can provide the HTML template inline, as the value of the template property. This template defines the component’s host view.
  • providers: An array of providers for services that the component requires.
    • In the example, this tells Angular how to provide the HeroService instance that the component’s constructor uses to get the list of heroes to display.

Template Syntax

A template looks like regular HTML, except that it also contains Angular template syntax, which alters the HTML based on your app’s logic and the state of app and DOM data.

For example, here is a template for the Tutorial’s HeroListComponent.

1
2
3
4
5
6
7
8
9
10
<h2>Hero List</h2>

<p><i>Pick a hero from the list</i></p>
<ul>
<li *ngFor="let hero of heroes" (click)="selectHero(hero)">
{{hero.name}}
</li>
</ul>

<app-hero-detail *ngIf="selectedHero" [hero]="selectedHero"></app-hero-detail>

This template uses typical HTML elements like <h2> and <p>, and also includes Angular template-syntax elements, *ngFor, , (click), [hero], and <app-hero-detail>. The template-syntax elements tell Angular how to render the HTML to the screen, using program logic and data.

All those Angular HTML elements above will be explored below.

Data Binding

One of the most important and convenient feature of Angular is its data binding (a feature also common to most Template Engines). However, Angular provides more advanced data bindings as well, which you will find out shortly below.

Note:

  • Without a framework like Angular, you would be responsible for pushing data values into the HTML controls and turning user responses into actions and value updates. Writing such push and pull logic by hand is tedious, error-prone, and a nightmare to read, as any experienced front-end JavaScript programmer can attest.
  • One advanced feature of Angular is its two-way data binding, a mechanism for coordinating the parts of a template with the parts of a component. Add binding markup to the template HTML to tell Angular how to connect both sides.

The following diagram shows the four forms of data binding markup. Each form has a direction: to the DOM, from the DOM, or both.

Picture from https://angular.io/generated/images/guide/architecture/databinding.png

The example from the HeroListComponent template above uses three of these forms.

1
2
3
<li>{{hero.name}}</li>
<app-hero-detail [hero]="selectedHero"></app-hero-detail>
<li (click)="selectHero(hero)"></li>
  • The (inline) interpolation displays the component’s hero.name property value within the <li> element.
  • The [hero] property binding passes the value of selectedHero from the parent HeroListComponent to the hero property of the child HeroDetailComponent.
  • The (click) event binding calls the component’s selectHero method when the user clicks a hero’s name.

Note:

  • If you use interpolation, you can only supply it with a string. If you use property bindings, you can supply it with objects.
  • For property binding, it might look confusing, but it is that the "value" gets rendered from the host component (maps to field with the name value), and then that rendered "value" gets passed into the property with name property for later uses.
    • If you did property="value" without brackets, the "value" will not be passed from the component as Angular does not know it is a property binding.

Two-way data binding (used mainly in template-driven forms) combines property and event binding in a single notation. Here’s an example from the HeroDetailComponent template that uses two-way data binding with the ngModel directive.

1
<input [(ngModel)]="hero.name">

In two-way binding:

  • First, a data property value flows to the input box from the component as with property binding. (Why it starts with a [])
  • Then, if the user’s changes the field, it also flow back to the component, resetting the property to the latest value, as with event binding.

Picture from https://angular.io/generated/images/guide/architecture/component-databinding.png

Class Binding

You can also add and remove CSS class from an element’s class attribute with a class binding.

Here’s how to set the class attribute without a binding in plain HTML:

1
2
<!-- standard class attribute setting -->
<div class="fooBar">Some text</div>
  1. To create a single class binding, start with the prefix class followed by a dot (.) and the name of the CSS class (for example, [class.foo]="hasFoo"). Angular adds the class when the bound expression is true, and it removes the class when the expression is false:
1
<div [class.fooBar]="<some expression>"></div>
  • If <some expression> evaluates to true, then the CSS class foorBar will be added
  • If <some expression> evaluates to false/undefined/null, then the CSS class foorBar will not be added or be removed (if added)
  1. To create a binding to multiple classes, use a generic [class] binding without the dot (for example, [class]="classExpr"). The expression can be a space-delimited string of class names, or you can format it as an object with class names as the keys and true/false expressions as the values. With object format, Angular will add a class only if its associated value is truthy.
Binding Type Syntax Input Type Example Input Values
Single class binding [class.foo]="hasFoo" `boolean undefined
Multi-class binding [class]="classExpr" string "my-class-1 my-class-2 my-class-3"
`{[key: string]: boolean undefined
Array<string> ['foo', 'bar']
Pipes

Angular pipes let you declare display-value-transformations in your template HTML. A class with the @Pipe decorator defines a function that transforms input values to output values for display in a view.

Angular defines various pipes, such as the date pipe and currency pipe; for a complete list, see the Pipes API list. You can also define new pipes.

To specify a value transformation in an HTML template, use the pipe operator |.

1
{{interpolated_value | pipe_name}}

You can chain pipes, sending the output of one pipe function to be transformed by another pipe function. A pipe can also take arguments that control how it performs its transformation. For example, you can pass the desired format to the date pipe.

1
2
3
4
5
6
7
8
<!-- Default format: output 'Jun 15, 2015'-->
<p>Today is {{today | date}}</p>

<!-- fullDate format: output 'Monday, June 15, 2015'-->
<p>The date is {{today | date:'fullDate'}}</p>

<!-- shortTime format: output '9:43 AM'-->
<p>The time is {{today | date:'shortTime'}}</p>
Directives

Angular templates are dynamic. When Angular renders them, it transforms the DOM according to the instructions given by directives. A directive is a class with a @Directive() decorator.

Note:

  • A component is technically a directive. However, components are so distinctive and central to Angular applications that Angular defines the @Component() decorator, which extends the @Directive() decorator with template-oriented features.

In addition to components, there are two other kinds of directives: structural and attribute. Angular defines a number of directives of both kinds, and you can define your own using the @Directive() decorator.

Just as for components, the metadata for a directive associates the decorated class with a selector element that you use to insert it into HTML. In templates, directives typically appear within an element tag as attributes, either by name or as the target of an assignment or a binding.

Structural Directives

Structural directives alter layout by adding, removing, and replacing elements in the DOM (hence changing parts of its structure). The example template uses two built-in structural directives to add application logic to how the view is rendered.

1
2
<li *ngFor="let hero of heroes"></li>
<app-hero-detail *ngIf="selectedHero"></app-hero-detail>
  • *ngFor is an iterative; it tells Angular to stamp out one <li> per hero in the heroes list.
  • *ngIf is a conditional; it includes the HeroDetail component only if a selected hero exists.
Attribute Directives

Attribute directives alter the appearance or behavior of an existing element (hence not changing the overall DOM structure). In templates they look like regular HTML attributes, hence the name.

The ngModel directive, which implements two-way data binding, is an example of an attribute directive. ngModel modifies the behavior of an existing element (typically <input>) by setting its display value property and responding to change events.

1
<input [(ngModel)]="hero.name">

For more information on directives, learn more in the Attribute Directives and Structural Directives guides.

Module

A Module in Angular is called a NgModule, which can be interpreted as a container assembling some of your components and services for a specific feature.

An NgModule declares a compilation context for a set of components that is dedicated to an application domain, a workflow, or a closely related set of capabilities. An NgModule can associate its components with related code, such as services, to form functional units.

Every Angular app has a root module, conventionally named AppModule, which provides the bootstrap mechanism that launches the application. An app typically contains many functional modules.

Like JavaScript modules, NgModules can import functionality from other NgModules, and allow their own functionality to be exported and used by other NgModules. For example, to use the router service in your app, you import the Router NgModule.

Source: https://angular.io/guide/architecture

NgModule Metadata

An NgModule is defined by a class decorated with @NgModule(). The @NgModule() decorator is a function that takes a single metadata object (why it is surrounded by a {}), whose properties describe the module. The most important properties are as follows.

  • declarations: The Component, directives, and pipes that belong to this NgModule.
  • exports: The subset of declarations that should be visible and usable in the component templates of other NgModules (when imported).
  • imports: Other modules whose exported classes are needed by component templates declared in this NgModule.
  • providers: Creators of Service that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level, which is often preferred.)
  • bootstrap: The main application view, called the root component, which hosts all other app views. Only the root NgModule (of a specific view) should set the bootstrap property.

An example would be:

1
2
3
4
5
6
7
8
9
10
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
imports: [ BrowserModule ],
providers: [ Logger ],
declarations: [ AppComponent ],
exports: [ AppComponent ], // technically not necessary here as it is the root module
bootstrap: [ AppComponent ]
})
export class AppModule { }

Note:

  • A component and its template together define a view. You can imagine a view as a sub-html page that is rendered by Angular.

NgModule and Component

The aim of a component is to create a view with a html template. When you create a component, it is associated directly with a single view, called the host view (for this component). The host view can be the root of a view hierarchy, which can contain embedded views, which are in turn the host views of other components.

Those components can be in the same NgModule, or can be imported from other NgModules. Views in the tree can be nested to any depth.

A simple example of a view hierarchy could be:

view-hierachy.png from https://angular.io/guide/architecture-modules

The aim of a Module/NgModule is to define a compilation context - the context where Angular should be able to find all necessary components for rendering a provided html template.

A root NgModule always has a root component that is created during bootstrap, but any NgModule can include any number of additional components, which can be loaded through the router or created through the template. The components that belong to an NgModule share a compilation context.

Angular Library Modules

Angular loads as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the @angular prefix. Install them with the node package manager npm and import parts of them with JavaScript import statements.

NgModules from Angular libraries using JavaScript import statements. For example, the following code imports the BrowserModule NgModule from the platform-browser library.

1
import { BrowserModule } from '@angular/platform-browser';

In the example of the simple root module above, the application module needs material from within BrowserModule. To access that material, add it to the @NgModule metadata imports like this.

1
2
3
4
5
@NgModule({
...
imports: [ BrowserModule ], // importing makes classes exported in that module available in this compilation context
...
})

Service

A Service is usually pieces of code (function, data, feature) that are used across different components.

A service class definition is immediately preceded by the @Injectable() decorator. The decorator provides the metadata that allows other providers to be injected as dependencies into your class.

For example: The Angular Router NgModule provides a service that lets you define a navigation path among the different application states and view hierarchies in your app. (More on Routers will be covered later)

Source: https://angular.io/guide/architecture

Angular distinguishes components from services to increase modularity and reusability. By separating a component’s view-related functionality from other kinds of processing, you can make your component classes clean and efficient.

Example of a Service

Here would be an example of a service (class) - src/app/logger.service.ts :

1
2
3
4
5
export class Logger {
log(msg: any) { console.log(msg); }
error(msg: any) { console.error(msg); }
warn(msg: any) { console.warn(msg); }
}

(The above is just a simple example, an actual service usually has the @Injectable() annotation, in order to be registered with an Angular dependency injector. More on this later.)

Services can depend on other services (made available by dependency injection). For example, here’s a HeroService that depends on the Logger service, and also uses BackendService to get heroes. That service in turn might depend on the HttpClient service to fetch heroes asynchronously from a server.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export class HeroService {
private heroes: Hero[] = [];

// constructor injection
constructor(
private backend: BackendService,
private logger: Logger) { }

getHeroes() {
this.backend.getAll(Hero).then( (heroes: Hero[]) => {
this.logger.log(`Fetched ${heroes.length} heroes.`);
this.heroes.push(...heroes); // fill cache
});
return this.heroes;
}
}

Dependency Injection (DI)

DI is wired into the Angular framework and used everywhere to provide new components with the services or other things they need.

Components consume services; that is, you can inject a service into a component, giving the component access to that service class.

To define a class as a service in Angular, use the @Injectable() decorator to provide the metadata that allows Angular to inject it into a component as a dependency. Similarly, use the @Injectable() decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) has a dependency.

Dependency Injection Mechanism

In general, there are three components involved:

  • The injector is the main mechanism. Angular creates an application-wide injector for you during the bootstrap process, and additional injectors as needed. You don’t have to create injectors.
  • An injector creates dependencies, and maintains a container of dependency instances that it reuses if possible.
    • All of those are done behind the scene.
  • A provider is an object that tells an injector how to obtain or create a dependency.

Angular does dependency injection by the following three steps:

  1. When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the constructor parameter types.

    For example, the HeroService dependency will be injected in the example below:

    1
    constructor(private service: HeroService) { }
  2. When Angular discovers that a component depends on a service, it first checks if the injector has any existing instances of that service. If a requested service instance doesn’t yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular.

  3. When all requested services have been resolved and returned, Angular can call the component’s constructor with those services as arguments.

Picture from https://angular.io/generated/images/guide/architecture/injector-injects.png

Using Dependency Injection

For any using dependency that you need in your app, you must:

  1. Register a provider for the dependency (class/function/value) with the app’s injector (with @Injectable), so that the injector can use the provider to create new instances. For a service, the provider is typically the service class itself.
    • This can be done either directly within the same service class, or in a specific module/component, since this process is done by annotation metadata.
  2. Inject that dependency by including it in the constructor argument.
    • This can be done in three different ways. Each of which would be available project-wide.
  • Case 1: You registered the service within the same service class:

    ​ This can be done by either creating the service with Angular CLI ng g service <service-name>, which will automatically include the annotation:

    1
    2
    3
    @Injectable({
    providedIn: 'root', // provides the dependecy at the root level
    })

    ​ Or including the above annotation manually in your service class.

    ​ When you provide the service at the root level, Angular creates a single, shared instance of HeroService available to the entire project and injects it into any class that asks for it. Registering the provider in the @Injectable() metadata also allows Angular to optimize an app by removing the service from the compiled app if it isn’t used.

  • Case 2: You registered the service within a specific NgModule

    ​ In this case, the same instance of a service is available to all components in that NgModule. To register at this level, use the providers property of the @NgModule() decorator.

    1
    2
    3
    4
    5
    6
    7
    8
    @NgModule({
    // injecting at a module level
    providers: [
    BackendService,
    Logger
    ],
    ...
    })
  • Case 3: You registered the service within a specific Component

    ​ When you register a provider at the component level, you get a new instance of the service with each new instance of that component. At the component level, register a service provider in the providers property of the @Component() metadata.

    1
    2
    3
    4
    5
    @Component({
    selector: 'app-hero-list',
    templateUrl: './hero-list.component.html',
    providers: [ HeroService ]
    })

Angular Fundamentals

This chapter covers, in more details, the functionalities that Angular brings to developing a frontend application. This chapter will also touch on (in the end) how to communicate with a backend server for getting/updating/posting data.

Template Syntax

This section is a comprehensive technical summary to the Angular template language (for a more complete reference, please visit https://angular.io/guide/template-syntax). This section seeks to explain basic principles of the template language and describes most of the syntax that you’ll encounter elsewhere in the guide.

Interpolation

Interpolation refers to embedding expressions into marked up text. By default, interpolation uses as its delimiter the double curly braces, .

The text between the braces is a template expression that Angular first evaluates and then converts to a string. The following interpolation illustrates the point:

1
2
3
4
5
6
7
8
9
10
11
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}.</p>

<!-- Other common usages include: -->
<p>{{title}}</p>

<!-- Can also be used within a DOM property, in this case it will be similar to property binding -->
<div><img src="{{itemImageUrl}}"></div>

<!-- "The sum of 1 + 1 is not 4 (getVal() returns 2)" -->
<p>The sum of 1 + 1 is not {{1 + 1 + getVal()}}.</p>

With interpolation, you can basically access everything defined in the bound component.ts including:

  • methods
  • variables
  • constants

You can also do some basic operations within `{{ }}`. See Template Expressions for more details.

Note:

  • Interpolation cannot evaluate data structure other than a primitive. If you are trying to evaluate an Object, then it will render it as [object Object] (obviously not what you want). If you want to use complex data structures, you need to use Property Binding or a Pipe operator |.

Template Expressions

A template expression produces a value and appears within the double curly braces, {{ }} . Angular executes the expression and assigns it to a property of a binding target; the target could be an DOM property, a component, or a directive.

For example: the interpolation braces in {{1 + 1}} surround the template expression 1 + 1. In the property binding, a template expression appears in quotes to the right of the = symbol as in [property]="expression".

The Angular template expression language employs a subset of JavaScript syntax supplemented with a few special operators for specific scenarios. The next sections cover three of these operators:

  • pipe |
  • safe navigation operator
  • non-null assertion operator

Pipe Operator |

The result of an expression might require some transformation before you’re ready to use it in a binding. For example, you might display a number as a currency, change text to uppercase, or filter a list and sort it (visit Pipes for information on available pipe functions).

Pipes are simple functions that accept an input value and return a transformed value. They’re easy to apply within template expressions, using the pipe operator (|):

1
<p>Title through uppercase pipe: {{title | uppercase}}</p>

Some example usages include:

1
2
3
4
5
6
7
8
<!-- convert title to uppercase, then to lowercase -->
<p>Title through a pipe chain: {{title | uppercase | lowercase}}</p>

<!-- pipe with configuration argument => "February 25, 1980" -->
<p>Manufacture date with date format pipe: {{item.manufactureDate | date:'longDate'}}</p>

<!-- The json pipe is particularly helpful for debugging bindings: -->
<p>Item json pipe: {{item | json}}</p>

Note:

  • The pipe operator has a higher precedence than the ternary operator (?:), which means a ? b : c | x is parsed as a ? b : (c | x). Nevertheless, for a number of reasons, the pipe operator cannot be used without parentheses in the first and second operands of ?:. A good practice is to use parentheses in the third operand too.

Safe Navigation Operator ?

The Angular safe navigation operator, ?, guards against null and undefined values in property paths. Here, it protects against a view render failure if item is null (if null error occurs, the entire module associated with it might fail to render and cause wrong html codes being displayed).

1
<p>The item name is: {{item?.name}}</p>

With the safe navigation operator, ?, Angular stops evaluating the expression when it hits the first null value and renders the view without errors.

Note:

  • Sometimes however, null values in the property path may be OK under certain circumstances, especially when the value starts out null but the data arrives eventually. In this case, you need to be careful for using ? as it will stop evaluating immediately once it is null.

The ? operator works perfectly with long property paths such as a?.b?.c?.d as well.

Non-Null Assertion Operator !

By default, the type checker also throws an error if it can’t determine whether a variable will be null or undefined at runtime. In this case, you can tell the type checker not to throw an error by applying the postfix non-null assertion operator, !.

The Angular non-null assertion operator, !, serves the same purpose in an Angular template. For example, you can assert that item properties are also defined.

1
2
<!-- Assert color is defined, even if according to the `Item` type it could be undefined. -->
<p>The item's color is: {{item.color!.toUpperCase()}}</p>

When the Angular compiler turns your template into TypeScript code, it prevents TypeScript from reporting that item.color might be null or undefined.

Note:

  • Unlike the safe navigation operator, the non-null assertion operator does not guard against null or undefined. Rather, it tells the TypeScript type checker to suspend strict null checks for a specific property expression.

Built-in Template Functions

Sometimes a binding expression triggers a type error during AOT compilation and it is not possible or difficult to fully specify the type. To silence the error, you can use the $any() cast function to cast the expression to the any type as in the following example:

1
2
3
4
<p>The item's undeclared best by date is: {{$any(item).bestByDate}}</p>

<!-- The $any() cast function also works with this to allow access to undeclared members of the component. -->
<p>The item's undeclared best by date is: {{$any(this).bestByDate}}</p>

When the Angular compiler turns this template into TypeScript code, it prevents TypeScript from reporting that bestByDate is not a member of the item object when it runs type checking on the template.

Data Binding and DOM Property

Angular provides many kinds of data-binding. Binding types can be grouped into three categories distinguished by the direction of data flow:

  • From the source-to-view
  • From view-to-source
  • Two-way sequence: view-to-source-to-view
Type Syntax Category
Interpolation Property Attribute Class Style [target]="expression" bind-target="expression" One-way from data source to view target
Event (target)="statement" on-target="statement" One-way from view target to data source
Two-way [(target)]="expression" bindon-target="expression" Two-way

Binding types other than interpolation have a target name to the left of the equal sign, either surrounded by punctuation, [] or (), or preceded by a prefix: bind-, on-, bindon-, and an expression to the right of the equal sign.

The target of a binding is the DOM property or event inside the binding punctuation: [], () or [()], it is not the attribute of the html tag (see below section).

HTML Attribute vs. DOM Property

For more details, please see this StackOverflow post.

In summary, when you write some html code such as:

1
<input type="text" value="Name:">

You have defined 2 attributes (type and value). However, to actually display the page, the browser will first parse this code and create an Object.

Once the browser parses this code, a HTMLInputElement object will be created, and this object will contain dozens of properties like: accept, accessKey, align, alt, attributes, autofocus, baseURI, checked, childElementCount, childNodes, children, classList, className, clientHeight, etc.

Source: this StackOverflow post.

In the end, if some event happened and something in your html has changed, it will be the property of the DOM object being changed, not its html attribute.

Possible Binding Targets

The target of a data-binding is something in the DOM. Depending on the binding type, the target can be a property (element, component, or directive), an event (element, component, or directive), or sometimes an attribute name. The following table summarizes the targets for the different binding types.

Type Target Examples
Property Element property Component property Directive property src, hero, and ngClass in the following:<img [src]="heroImageUrl"> <app-hero-detail [hero]="currentHero"></app-hero-detail> <div [ngClass]="{'special': isSpecial}"></div>
Event Element event Component event Directive event click, deleteRequest, and myClick in the following:(click)="onSave()">Save</button> <app-hero-detail (deleteRequest)="deleteHero()"></app-hero-detail> <div (myClick)="clicked=$event" clickable>click me</div>
Two-way Event and property <input [(ngModel)]="name">
Attribute Attribute (the exception) <button [attr.aria-label]="help">help</button>
Class class property <div [class.special]="isSpecial">Special</div>
Style style property <button [style.color]="isSpecial ? 'red' : 'green'">

Property Binding

Property binding is a one-way mechanism that lets you set the property of a view element. Property binding flows a value in one direction, from a component’s property into a target element property. Property binding uses the [property]="expression" syntax for data binding, where the evaluation occurs in the right hand side"expression".

Note:

  • While property binding allows you to deal with Objects, you cannot use it to read or pull values out of target elements. Similarly, you cannot use property binding to call a method on the target element. If the element raises events, you can listen to them with an event binding.

A simple example is setting the disabled property of a button. However, you can not only set the value of an DOM property, but also pass in variables into a component:

1
2
3
4
5
<!-- Evaluates the buttonDisabled property of the bound component.ts -->
<button [disabled]="buttonDisabled"></button>

<!-- Pass in variable named hero with value evaluated with selectHero into the component bound with app-hero-detail -->
<app-hero-detail [hero]="selectedHero" ></app-hero-detail>

Then to use that variable passed in, in your hero-detail.component.ts:

1
2
3
4
5
6
7
8
9
10
11
@Component({
selector: 'app-hero-detail',
templateUrl: './hero-detail.component.html',
styleUrls: ['./hero-detail.component.css']
})

export class HeroDetailComponent implements OnInit {
// alike Spring annotation, interpret it as @Param
@Input() hero: Hero;
...
}

Property Binding Targets

Targets for property binding includes:

  • DOM properties (e.g. <img [src]="someURL">)
  • Built-in directives (e.g. <p [ngClass]="someCSSClass"></p>)
  • Arguments directives (e.g. <app-my-hero [myHero]="someHero"></app-my-hero>)
    • To retrieve that variable myHero, you need to use @Input() myHero: HeroType

Note:

  • If you use the arguments directives, you need to make sure that the returned type in that "someHero" matches the type you specified in @Input() myHero: HeroType

Once additional usage of arguments directive is to not use the [], which means the expression will not be evaluated (if you not use interpolation):

1
<app-string-init prefix="This is a one-time initialized string."></app-string-init>

In this case, you can still access the variable prefix, which will have the value of whatever inside " ".

However, if you use:

1
<app-string-init prefix="{{some-expression}}"></app-string-init>

Then you can also access the result of that expression (has to be a primitive), which goes back to the concept of Interpolation.

Content Security

Both interpolation and property binding could face security issues such as injecting <script> into the html.

For example:

1
2
// inside app.component.ts
evilTitle = 'Template <script>alert("evil never sleeps")</script> Syntax';

Then, if you used Interpolation:

1
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>

You might be wondering if that script will actually be injected and executed.

Fortunately, Angular data binding is on alert for dangerous HTML. In the above case, the HTML displays as is, and the JavaScript does not execute. Angular does not allow HTML with script tags to leak into the browser, neither with interpolation nor property binding.

This means the above snippet would be rendered as:

1
"Template <script>alert("evil never sleeps")</script> Syntax" is the interpolated evil title.

Similarly, if you used Property Binding:

1
2
3
4
5
<!--
Angular generates a warning for the following line as it sanitizes them
WARNING: sanitizing HTML stripped some content (see http://g.co/ng/security#xss).
-->
<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>

The result would also be sanitized:

1
"Template Syntax" is the property bound evil title.

Class and Style Binding

  • You can also add and remove CSS class names from an element’s class attribute with a class binding.

For more information on class binding, you can visit this section Class Binding

  • If you just want to change a specific style, you can also set styles dynamically with a style binding.

To create a single style binding, start with the prefix style followed by a dot (.) and the name of the CSS style property (for example, [style.width]="width"). The property will be set to the value of the bound expression, which is normally a string. Optionally, you can add a unit extension like em or %, which requires a number type.

For example:

1
2
<!-- standard style attribute setting -->
<div style="color: blue">Some text</div>

If there are multiple styles you’d like to toggle, you can bind to the [style] property directly without the dot (for example, [style]="styleExpr"). The expression attached to the [style] binding is most often a string list of styles like "width: 100px; height: 100px;".

You can also format the expression as an object with style names as the keys and style values as the values, like {width: '100px', height: '100px'}.

Binding Type Syntax Input Type Example Input Values
Single style binding [style.width]="width" `string undefined
Single style binding with units [style.width.px]="width" `number undefined
Multi-style binding [style]="styleExpr" string "width: 100px; height: 100px"
`{[key: string]: string undefined
Array<string> [‘width’, ‘100px’]

Styling Precedence

A single HTML element can have its CSS class list and style values bound to multiple sources (for example, host bindings from multiple directives).

When there are multiple bindings to the same class name or style property, Angular uses a set of precedence rules to resolve conflicts and determine which classes or styles are ultimately applied to the element.

Styling precedence (highest to lowest)

  1. Template bindings
    1. Property binding (for example, <div [class.foo]="hasFoo"> or <div [style.color]="color">)
    2. Map binding (for example, <div [class]="classExpr"> or <div [style]="styleExpr">)
    3. Static value (for example, <div class="foo"> or <div style="color: blue">)
  2. Directive host bindings
    1. Property binding (for example, host: {'[class.foo]': 'hasFoo'} or host: {'[style.color]': 'color'})
    2. Map binding (for example, host: {'[class]': 'classExpr'} or host: {'[style]': 'styleExpr'})
    3. Static value (for example, host: {'class': 'foo'} or host: {'style': 'color: blue'})
  3. Component host bindings
    1. Property binding (for example, host: {'[class.foo]': 'hasFoo'} or host: {'[style.color]': 'color'})
    2. Map binding (for example, host: {'[class]': 'classExpr'} or host: {'[style]': 'styleExpr'})
    3. Static value (for example, host: {'class': 'foo'} or host: {'style': 'color: blue'})

Source: https://angular.io/guide/template-syntax#styling-precedence-highest-to-lowest

Event Binding

Event binding allows you to listen for certain events such as keystrokes, mouse movements, clicks, and touches.

Angular event binding syntax consists of a target event name within parentheses on the left of an equal sign, and a quoted template statement on the right. The following event binding listens for the button’s click events, calling the component’s onSave() method whenever a click occurs:

1
<button (click)="onSave()">Save</button>

Alternatively, use the on- prefix, known as the canonical form:

1
<button on-click="onSave()">Save</button>

Both has the same effect.

Event Binding Targets

Element events may be the more common targets, but Angular looks first to see if the name matches an event property of a known directive, as it does in the following example:

1
2
3
<h4>myClick is an event on the custom ClickDirective:</h4>
<button (myClick)="clickMessage=$event" clickable>click with myClick</button>
{{clickMessage}}

If the name fails to match an element event or an output property of a known directive, Angular reports an “unknown directive” error.

$event and event handling statements

In an event binding, Angular sets up an event listener and handler for the target event.

When the event is raised, it is caught by the listener, and then the handler executes the template statement. The template statement typically involves a receiver, which performs an action in response to the event, such as storing a value from the HTML control into a model.

However, how do we access information stored in that event? This is actually retrieved by using $event.

Note:

  • The target event determines the shape of the $event object. If the target event is a native DOM element event, then $event is a DOM event object, with properties such as target and target.value.

For example, we can emulate the two-way-binding without NgModel with:

1
2
3
<input [value]="currentItem.name"
(input)="updateName($event.target)" >
Two way binding without NgModel

The above snippet does two things:

  1. First the property binding binds the value property in the input to currentItem.name
  2. Then, the event binding is used, and the argument passed in $event.target will contain information about this event.

Then your updateName() method could look like:

1
2
3
4
5
updateName(event: EventTarget): void{
// you need to cast it to HTMLInputElement to visit the field value
const evt = event as HTMLInputElement;
this.setName(evt.value);
}

If the event belongs to a directive—recall that components are directives—$event has whatever shape the directive produces.

Creating a Custom Event

If, for a certain event, you cannot handle directly with a simple method call, you can use a EventEmitter to pass the event to other event listeners. In summary, this can be done with:

  1. Create a new EventEmitter<T>(), where T would be the object that you need to later on handle (optional)
  2. Name that EventEmitter<T>() with a name that you will later use to listen to
  3. Pass the object you want to deal with later with emit(<YourObject>).
  4. Catch the event with the name that you defined at Step 2

For example, you want to delete a specific item in from your DOM, but you do not want to handle it directly there, you can:

1
2
3
4
<img src="{{itemImageUrl}}" [style.display]="displayNone">
<span [style.text-decoration]="lineThrough">{{ item.name }}</span>
<!-- This standard html event will be the entrypoint -->
<button (click)="delete()">Delete</button>

Then your method would look like:

1
2
3
4
5
6
7
8
// This component makes a request but it can't actually delete a hero.
@Output() deleteRequest = new EventEmitter<Item>(); // the object to manipulate has type Item

delete() {
this.deleteRequest.emit(this.item); // passing in the object
this.displayNone = this.displayNone ? '' : 'none';
this.lineThrough = this.lineThrough ? '' : 'line-through';
}

The component defines a deleteRequest property that returns an EventEmitter. At this point, when the user clicks delete, the component invokes the delete() method, telling the EventEmitter to emit an Item object with an event called deleteRequest.

This request can later be caught with another Event Binding:

1
<app-item-detail (deleteRequest)="deleteItem($event)" [item]="currentItem"></app-item-detail>

Two-way Binding

The two way binding binds a specific property by allowing a two way flow. This is achieved with [()] symbol.

In fact, as demonstrated many times before, what happens under the hood is just a Property Binding [] first to initialize and bind the property from component to template, then an Event Binding () to bind changes (by listening to the event <propertyChange>) back to the property.

To demonstrate this, consider the example of resizing a text using two-way binding:

1
2
3
<!-- Inside app.component.ts, Where fontSizePx is defined to 16 -->
<app-sizer [(size)]="fontSizePx"></app-sizer>
<div [style.font-size.px]="fontSizePx">Resizable Text</div>

Note:

  • The two way binding is bound to a property which does not seem to be emitting any event. in this case, Angular will listen to the event with name sizeChange, which you have to define by yourself.

Inside that <app-sizer>, there are buttons to change the size of text by changing the firing event sizeChange:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
selector: 'app-sizer',
templateUrl: './sizer.component.html',
styleUrls: ['./sizer.component.css']
})
export class SizerComponent {


@Input() size: number | string;
@Output() sizeChange = new EventEmitter<number>(); // notice this @Output

// firing sizeChange events
dec() { this.resize(-1); }
inc() { this.resize(+1); }

resize(delta: number) {
this.size = Math.min(40, Math.max(8, +this.size + delta));
this.sizeChange.emit(this.size); // event fired here, this.size will be retrieved with $event, not $event.target
}

}

And this would be the corresponding html :

1
2
3
4
5
<div>
<button (click)="dec()" title="smaller">-</button>
<button (click)="inc()" title="bigger">+</button>
<label [style.font-size.px]="size">FontSize: {{size}}px</label>
</div>

Under the hood, this is what is happening:

1
<app-sizer [size]="fontSizePx" (sizeChange)="fontSizePx=$event"></app-sizer>

This means that, by default, if you two-way bounded: [(property)]="someValue", it is translated to [property]="someValue" (propertyChange)="someValue=$event".

Note:

  • This means that the object you emit must correspond to the type of the variable you defined in the property binding (fontSizePx).

However, if you want to use two-way binding in forms, it will be difficult as it is hard to define an entry point to trigger an event to be fired, since there are no built-in html event for detecting changes in an input, for example.

In this case, Angular provides solutions with its built-in directive ngForm, which you will see later in the section below.

Built-in Directives

As mentioned before, Angular offers two kinds of built-in directives: attribute directives (modifies DOM properties) and structural directives (modifies DOM structure). The below sections will cover some of the most common built-in directives of both kinds.

Attribute Directives

The most common attribute directives include:

  • NgClass
    • adds and removes a set of CSS classes.
  • NgStyle
    • adds and removes a set of HTML styles.
  • NgModel
    • adds two-way data binding to an HTML form element.
NgClass

This is essentially the Class Binding that we have talked about before. The aim of having a NgClass directive is to simplify the code for setting and changing the CSS classes of a DOM object.

Without NgClass directive, to change a specific class you would need to write:

1
2
<!-- toggle the "special" class on/off with a property -->
<div [class.someClass]="isSpecial ? 'special' : ''">This div is special</div>

And this could be painful if you are adding multiple CSS properties.

To simplify the code, you could use NgClass and place all your logic in your typescript:

1
2
<!-- Notice it is ngClass not NgClass when used in html -->
<div [ngClass]="currentClasses">This div is initially saveable, unchanged, and special.</div>

And the corresponding ts file:

1
2
3
4
5
6
7
8
9
currentClasses: {};
setCurrentClasses() {
// CSS classes: added/removed per current state of component properties
this.currentClasses = {
'saveable': this.canSave,
'modified': !this.isUnchanged,
'special': this.isSpecial
};
}

Note:

  • Though the directive is called NgClass, the actual directive used within the DOM property starts with a lower case n (ngClass).
NgStyle

Similar to NgClass, this NgStyle directive is used for a similar purpose of simplifying the process of editing multiple CSS styles.

Without NgStyle directive, you could have:

1
2
3
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'">
This div is x-large or smaller.
</div>

With NgStyle, you would have:

1
2
3
<div [ngStyle]="currentStyles">
This div is initially italic, normal weight, and extra large (24px).
</div>

and this would be the corresponding ts file:

1
2
3
4
5
6
7
8
9
currentStyles: {};
setCurrentStyles() {
// CSS styles: set per current state of component properties
this.currentStyles = {
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
NgModel

This is a directive mainly used to solve the two-way binding problem for form elements in html, for example <input>.

Before you use NgModel, you need to import the FormsModule into the respective ts class for it to be visible:

1
2
3
4
5
6
7
8
9
10
11
12
import { FormsModule } from '@angular/forms'; // <--- JavaScript import from Angular
/* . . . */
@NgModule({
/* . . . */

imports: [
BrowserModule,
FormsModule // <--- import into the NgModule
],
/* . . . */
})
export class AppModule { }

Then, you can use the two-way binding for <input> like this:

1
<input [(ngModel)]="currentItem.name" id="example-ngModel">

What happens under the hood is:

1
2
3
4
<label>(ngModelChange)="...name=$event":</label>
<!-- This ngModelChange is built-in by Angular that is fired whenever there is an input -->
<!-- You can imagine (input)="methodEmitsValueToNgModelChange()") -->
<input [ngModel]="currentItem.name" (ngModelChange)="currentItem.name=$event" id="example-change">

For <input>, is can actually be achieved manually with:

1
2
3
<label>without NgModel:</label>
<!-- the event input can actually be used as an entry point for firing events, which it also does -->
<input [value]="currentItem.name" (input)="currentItem.name=$event.target.value" id="without">
  • If you use (ngModelChange), you could not only set a property, but also call methods. For example:

    1
    <input [ngModel]="currentItem.name" (ngModelChange)="setUppercaseName($event)" id="example-uppercase">

    and this would be the corresponding method:

    1
    2
    3
    4
    // notice that $event would basically be the same as $event.target.value, which is converted already by NgModel 
    setUppercaseName(name: string) {
    this.currentItem.name = name.toUpperCase();
    }
NgModel and Value Accessors

The details are specific to each kind of element and therefore the NgModel directive only works for an element supported by a ControlValueAccessor that adapts an element to this protocol. Angular provides value accessors for all of the basic HTML form elements and the Forms guide shows how to bind to them.

Structural Directives

Structural directives are responsible for HTML layout. They shape or reshape the DOM’s structure, typically by adding, removing, and manipulating the host elements to which they are attached.

This section is an introduction to the common built-in structural directives:

  • NgIf
    • conditionally creates or destroys sub-views from the template.
  • NgFor
    • repeat a node for each item in a list.
  • NgSwitch
    • a set of directives that switch among alternative views.

The deep details of structural directives are covered in the Structural Directives guide, which explains the following:

NgIf

You can add or remove an element from the DOM by applying an NgIf directive to a host element.

For example, to add/remove an element based on the field isActive would look like:

1
2
<!-- Careful that for structural directives, you need to add the * in front -->
<app-item-detail *ngIf="isActive" [item]="item"></app-item-detail>

When the isActive expression returns a true value, NgIf adds the ItemDetailComponentfalseNgIf removes the ItemDetailComponent from the DOM, destroying/removing that component and all of its sub-components.

Note:

  • Showing/hiding an element is different from adding/removing an element. It is perhaps already apparent, that removing an element would incur faster performance hence user experience than leaving the element inside the DOM but just hiding it. However, removing/adding does cause structural change, so that effect has to be controlled as well.
NgIf and Null/Undefined

Another advantage of ngIf is that you can use it to guard against null. Show/hide is best suited for very simple use cases, however if an expression evaluated to null, Angular will throw an error if a nested expression tries to access a property of null.

The problem can be solved natively by using NgIf, which basically treats Null/Undefined to false.

For example, the following shows NgIf guarding a Null value (from currentCustomer) in a div. The currentCustomer name (or actually the entire div) appears only when there is a currentCustomer (when not null):

1
<div *ngIf="currentCustomer">Hello, {{currentCustomer.name}}</div>
NgFor

NgFor is a repeater directive—a way to present a list of items. You define a block of HTML that defines how a single item should be displayed and then you tell Angular to use that block as a template for rendering each item in the list.

A simple example would be:

1
<div *ngFor="let item of items">{{item.name}}</div>

And the above would create a number of divs with the name of the item stored in items.

NgFor and index

If you need the index of the for loop, you could also get it from the variable index, which stores the index of the current item (0-based).

For example:

1
<div *ngFor="let item of items; let i=index">{{i + 1}} - {{item.name}}</div>
NgFor and trackby

One potential problem with NgFor is mutation: if an entry in the large list (items, for example) changed, then you might need to make a re-query the database. This would be costly if the update is minimal yet the list is large.

The solution is to use trackBy, which uses a custom method that returns a certain id to tell Angular which item to re-render and which ones not to.

For example, your custom trackBy() method could look like this:

1
2
3
trackByItems(index: number, item: Item): number { 
return item.id;
}

Then your html would look like:

1
2
3
<div *ngFor="let item of items; trackBy: trackByItems">
({{item.id}}) {{item.name}}
</div>

For more details with using trackBy, please visit https://angular.io/guide/template-syntax#ngfor-with-trackby

NgSwitch

When used, this technically refers to the combination of NgSwitch (attribute directive), NgSwitchCase, and NgSwitchDefault (structural directives).

Therefore, you use:

  • ngSwitch as bindings to a switch value, which can be data of any type (e.g. string)
  • *ngSwitchCase and *ngSwitchDefault are structural directives as they add/remove elements in the DOM.

For example:

1
2
3
4
5
6
7
8
<div [ngSwitch]="currentItem.feature">
<app-stout-item *ngSwitchCase="'stout'" [item]="currentItem"></app-stout-item>
<app-device-item *ngSwitchCase="'slim'" [item]="currentItem"></app-device-item>
<app-lost-item *ngSwitchCase="'vintage'" [item]="currentItem"></app-lost-item>
<app-best-item *ngSwitchCase="'bright'" [item]="currentItem"></app-best-item>
<!-- not shown here, but *ngSwitchCase and *ngSwitchDefault works with native html elements (such as div) as well -->
<app-unknown-item *ngSwitchDefault [item]="currentItem"></app-unknown-item>
</div>

Template Reference Variables

A template reference variable is often a reference to a DOM element within a template. It can also refer to a directive (which contains a component), an element, TemplateRef, or a web component.

Use the hash symbol (#) to declare a reference variable. The following reference variable, #phone, declares a phone variable on an <input> element.

1
<input #phone placeholder="phone number" />

You can refer to a template reference variable anywhere in the component’s template. The scope of those variables is the entire template:

1
2
3
4
5
6
<input #phone placeholder="phone number" />

<!-- lots of other elements -->

<!-- phone refers to the input element; pass its `value` to an event handler -->
<button (click)="callPhone(phone.value)">Call</button>

Alternatively, you can use the ref- prefix alternative to #:

1
2
3
4
5
6
<!-- This has the same effect as above -->
<input ref-phone placeholder="phone number" />

<!-- lots of other elements -->

<button (click)="callPhone(phone.value)">Call</button>

How a reference variable gets its value

In most cases, Angular sets the reference variable’s value to the element on which it is declared. In the previous example, phone refers to the phone number <input>. The button’s click handler passes the <input> value to the component’s callPhone() method.

The NgForm directive can change that behavior and set the value to the NgForm directive:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- Reference to the ngForm directive -->
<form #itemForm="ngForm" (ngSubmit)="onSubmit(itemForm)">
<label for="name">Name
<input class="form-control" name="name" ngModel required />
</label>
<button type="submit">Submit</button>
</form>

<!-- the form.valid attribute is not available from the DOM, but from the NgForm directive -->
<div [hidden]="!itemForm.form.valid">
<p>{{ submitMessage }}</p>
</div>

with NgForm, itemForm is a reference to the NgForm directive with the ability to track the value and validity of every control in the form.

The native <form> element doesn’t have a form property, but the NgForm directive does, which allows disabling the submit button if the itemForm.form.valid is invalid and passing the entire form control tree to the parent component’s onSubmit() method.

@Input() and @Output

@Input() and @Output() allow Angular to share data between the parent context and child directives or components. An @Input() property is writable while an @Output() property is observable.

For example, consider the following structure:

1
2
3
<parent-component>
<child-component></child-component>
</parent-component>

In order for the child to communicate with the parent, you would need to use @Input() or @Output(). You can think of @Input() and @Output() like ports or doorways—@Input() is the doorway into the component allowing data to flow in while @Output() is the doorway out of the component, allowing the child component to send data out.

Using @Input

Use the @Input() decorator in a child component or directive to let Angular know that a property in that component can receive its value from its parent component. It helps to remember that the data flow is from the perspective of the child component. So an @Input() allows data to be input into the child component from the parent component.

To illustrate the use of @Input(), these parts of your app will be edited:

  • The child component class and template
  • The parent component class and template
Inside the Child

In summary, you will need to add:

  • Import the Input from, @angular/core
  • Use @Input() <variableName>:<type> to that it can be accessed inside your component

For example, it might look like this:

1
2
3
4
import { Component, Input } from '@angular/core'; // First, import Input
export class ItemDetailComponent {
@Input() item: string; // decorate the property with @Input()
}

Note:

  • The <variableName> has to be the same as the variable name in the html template, which you will see below.

Then in order to pass in the variable, you can use the format of Property Binding in your html template:

1
<app-item-detail [item]="currentItem"></app-item-detail>
Inside the Parent

In summary, here you do not need to do much:

  • All you need to do is to make a certain variable/method available in the template

Changes will only go to your html template where you will need to pass in the variable to the child component. This has been already shown above:

1
2
<!-- Inside the template of app-component-ts  -->
<app-item-detail [item]="currentItem"></app-item-detail>

where the app-item-detail is the child component.

A diagram from the official site shows the overall structure:

Image from https://angular.io/generated/images/guide/inputs-outputs/input-diagram-target-source.svghttps://angular.io/generated/images/guide/inputs-outputs/input-diagram-target-source.svg

Note:

  • To watch for changes on an @Input() property, use OnChanges, one of Angular’s lifecycle hooks. OnChanges is specifically designed to work with properties that have the @Input() decorator. See the OnChanges section of the Lifecycle Hooks guide for more details and examples.

Using @Output

Use the @Output() decorator in the child component or directive to allow data to flow from the child out to the parent.

Just like with @Input(), you can use @Output() on a property of the child component but its type should be EventEmitter. (The child component then has to raise an event so the parent knows something has changed. To raise an event, @Output() works hand in hand with EventEmitter, which is a class in @angular/core that you use to emit custom events.)

When you use @Output(), edit these parts of your app:

  • The child component class and template
  • The parent component class and template

The following example features an <input> where a user can enter a value and click a <button> that raises an event. The EventEmitter then relays the data to the parent component.

Inside the Child

In summary, you need to:

  • Import Output and EventEmitter
  • Decorate a property with @Output(), with type EventEmitter<YourType>
  • Create a method (or anything) that can emit an event

For example, your ts would look like this:

1
2
3
4
5
6
7
8
9
10
import { Output, EventEmitter } from '@angular/core';
...
export class ItemOutputComponent {
// the name newItemEvent will be used later for capturing this event
@Output() newItemEvent = new EventEmitter<string>();

addNewItem(value: string) {
this.newItemEvent.emit(value);
}
}

Then, its html page could look like:

1
2
3
<label>Add an item: <input #newItem></label>
<!-- so technicall, @Output is basically sending out events through EventEmitter -->
<button (click)="addNewItem(newItem.value)">Add to parent's list</button>
Inside the Parent

In this case, since what actually comes through is the event object, you don’t need to use @Input.

In summary, all you need to do is:

  • Create a method (or anything) that uses $event which comes from the child
  • Edit your html page to get that event

For example, to get that newItemEvent, your html would look like:

1
2
3
<!-- Inside your app.component.html -->
<!-- Event is emitted from app-item-output, then passed into the method addItem() of the parent -->
<app-item-output (newItemEvent)="addItem($event)"></app-item-output>

Your ts for the parent component could look like:

1
2
3
4
5
6
7
8
export class AppComponent {
items = ['item1', 'item2', 'item3', 'item4'];

// Because you emitted the value directly
addItem(newItem: string) {
this.items.push(newItem);
}
}

Using @Input and @Output Together

Combining the above two, you can basically have a child process properties from its parent and emit the changes back.

In summary, you need to:

  • Input to a child component with Argument Binding
  • Use that inputted variable with the @Input (and do some operations with it)
  • Create a EventEmitter and a method that emits changed result
  • Output that EventEmitter with Event Binding

An image should also summarize the information above:

Image from https://angular.io/generated/images/guide/inputs-outputs/input-output-diagram.svg

To combine property and event bindings using the banana-in-a-box syntax, [()], see Two-way Binding.

Using Inputs and Outputs Metadata

If you have multiple properties and EventEmitters that you need to input/output, you can use the inputs: and outputs: metadata. For example:

1
2
3
4
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['clearanceItem'],
outputs: ['buyEvent']
// tslint:enable: no-inputs-metadata-property no-outputs-metadata-property

Aliasing Inputs and Outputs

If, due to name collisions, you want to have a different name in your ts than the names you inputted/outputted from/to your template, you could use aliasing.

Aliasing with @Input() and @Output()

An example illustrates this the fastest:

1
2
@Input('wishListItem') input2: string; //  @Input(alias)
@Output('wishEvent') outputEvent2 = new EventEmitter<string>(); // @Output(alias) propertyName = ...
Aliasing with inputs and outputs Metadata

With inputs and outputs metadata, you need to use :

1
2
3
4
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property
inputs: ['input1: saveForLaterItem'], // propertyName:aliasFromTemplate
outputs: ['outputEvent1: saveForLaterEvent']
// tslint:disable: no-inputs-metadata-property no-outputs-metadata-property

User Input/Event

User actions such as clicking a link, pushing a button, and entering text raise DOM events. This section explains how to bind those events to component event handlers using the Angular event binding syntax.

Binding DOM Events

You can use Angular event bindings to respond to any DOM event. Many DOM events are triggered by user input. Binding to these events provides a way to get input from the user.

To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted template statement to it. All of these has been covered in the section Event Bindings

Getting Information Stored in $event

One of the most important step of interacting with user events is to get information of what the user has done.

In summary, all the information of a event (emitted either by a standard html event or a customized EventEmitter) is encapsulated in the $event DOM object.

For instance:

1
2
3
4
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`

In this case, the keyup event is handled and passed to onKey() method.

Now, for a standard html event, the event object will have a heavy payload:

  • All standard DOM event objects have a target property, a reference to the element that raised the event.
  • And information (such as the text inputted in the example above) can be retrieved form that target property

For instance, to get the string input, you need to access the event.target.value property:

1
2
3
4
5
6
7
export class KeyUpComponent_v1 {
values = '';

onKey(event: any) { // without type info
this.values += event.target.value + ' | ';
}
}

Note:

  • The properties of an $event object vary depending on the type of DOM event. For example, a mouse event includes different information than an input box editing event. Sometimes, in order to access a specific property, you need to typecast the event to HTMLInputElement to access that value property.

A better coding style of the above would be:

1
2
3
4
5
6
7
export class KeyUpComponent_v1 {
values = '';

onKey(event: KeyboardEvent) { // with type info
this.values += (event.target as HTMLInputElement).value + ' | ';
}
}
Using Template Variables

You can also achieve the same thing with a template variable. However, you need to be careful as Angular will only reload when an asynchronous event happened.

A naïve approach that does not work because Angular does not update the template is:

1
2
3
4
5
6
7
8
@Component({
selector: 'app-loop-back',
template: `
<input #box>
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }

To make it work, you can either emit an empty event so that Angular updates:

1
2
3
4
5
6
7
8
@Component({
selector: 'app-loop-back',
template: `
<input #box (keyup)="0">
<p>{{box.value}}</p>
`
})
export class LoopbackComponent { }

Or, the better approach would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component({
selector: 'app-key-up2',
template: `
<input #box (keyup)="onKey(box.value)">
<p>{{values}}</p>
`
})
export class KeyUpComponent_v2 {
values = '';

// here you can also have some complex processings
onKey(value: string) {
this.values += value + ' | ';
}
}
Key Event Filtering

The (keyup) event handler hears every keystroke. Sometimes only the Enter key matters, because it signals that the user has finished typing. One way to reduce the noise would be to examine every $event.keyCode and take action only when the key is Enter.

There’s an easier way: bind to Angular’s keyup.enter pseudo-event. Then Angular calls the event handler only when the user presses Enter.

1
2
3
4
5
6
7
8
9
10
11
@Component({
selector: 'app-key-up3',
template: `
<input #box (keyup.enter)="onEnter(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v3 {
value = '';
onEnter(value: string) { this.value = value; }
}

However, what if you also want to save the data when the user clicked away before finishing typing and pressing enter? In this case, you need to also listen to the blur event, which is introduced below.

Blur

Another event which is quite useful for dealing with inputs is blur, which is the opposite of focus, meaning that the user has clicked away from the element.

Therefore, to solve the previous problem, you can add another event binding with(blur) to handle this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
selector: 'app-key-up4',
template: `
<input #box
(keyup.enter)="update(box.value)"
(blur)="update(box.value)">

<p>{{value}}</p>
`
})
export class KeyUpComponent_v4 {
value = '';
update(value: string) { this.value = value; }
}

Pipes

Pipes can be used to transform and format strings, currency amounts, dates, and other display data. Pipes are simple functions you can use in template expressions to accept an input value and return a transformed value.

Angular provides built-in pipes for typical data transformations, including transformations for internationalization (i18n), which use locale information to format data. The following are commonly used built-in pipes for data formatting:

  • DatePipe: Formats a date value according to locale rules.
  • UpperCasePipe: Transforms text to all upper case.
  • LowerCasePipe: Transforms text to all lower case.
  • CurrencyPipe: Transforms a number to a currency string, formatted according to locale rules.
  • DecimalPipe: Transforms a number into a string with a decimal point, formatted according to locale rules.
  • PercentPipe: Transforms a number to a percentage string, formatted according to locale rules.

Using a Pipe in a Template

To apply a pipe in a template, use the pipe operator (|) within a template expression. The basic usage has been shown in the section pipe operator (|), but it is shown here again just to demonstrate its simplicity:

1
2
<!-- where birthday = new Date(1988, 3, 15); is defined in the app.component.ts -->
<p>The hero's birthday is {{ birthday | date }}</p>

Formatting Pipe Output

Some pipes have optional parameters that you can use to fine-tune the pipe’s output. To specify the optional parameter, follow the pipe name (currency) with a colon (:) and the parameter value ('EUR').

  • For example, you can use the CurrencyPipe with a country code such as EUR as a parameter. The template expression {{ amount | currency:'EUR' }} transforms the amount to currency in euros.

If the pipe accepts multiple parameters, separate the values with colons.

  • For example, {{ amount | currency:'EUR':'Euros '}} adds the second parameter, the string literal 'Euros ', to the output string. You can use any valid template expression as a parameter, such as a string literal or a component property.

For a full list of built-in Angular pipes with their optional parameters, please visit: https://angular.io/api/common#pipes

A complete example would be:

1
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>

Or, if you want to specify options:

1
2
3
4
5
6
7
8
9
10
11
12
13
template: `
<p>The hero's birthday is {{ birthday | date:format }}</p>
<button (click)="toggleFormat()">Toggle Format</button>
`
...

export class HeroBirthday2Component {
birthday = new Date(1988, 3, 15); // April 15, 1988 -- since month parameter is zero-based
toggle = true; // start with true == shortDate

get format() { return this.toggle ? 'shortDate' : 'fullDate'; }
toggleFormat() { this.toggle = !this.toggle; }
}

For a full list of date pipe optional parameters, please visit: https://angular.io/api/common/DatePipe

Chained Pipes

You can also chain pipes so that the output of one pipe becomes the input to the next (going from left to right).

For example:

1
2
3
4
<p>
The chained hero's birthday is
{{ birthday | date | uppercase}}
</p>

In the above example, chained pipes first apply a format to a date value, then convert the formatted date to uppercase characters.

Making a Custom Pipe

First, we need to briefly go through how Angular finds and uses a pipe.

Pipes are actually just classes annotated with @Pipe, implementing the PipeTransform interface. Whenever a pipe is detected in a template, Angular will go to that annotation and execute the transform method defined within the class.

This also provides hints for us on how to create a custom pipe function.

Creating a Class as a Pipe

In summary, you need to have a class that:

  • Implements the PipeTransform interface
  • Is Annotated with @Pipe()

For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Pipe, PipeTransform } from '@angular/core';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent?: number): number {
return Math.pow(value, isNaN(exponent) ? 1 : exponent);
}
}

Detecting changes with data binding in pipes

When you use pipes, you use data binding with a pipe to display values and respond to user actions. If the data is a primitive input value, such as String or Number, or an object reference as input, such as Date or Array, Angular executes the pipe whenever it detects a change for the input value or reference.

For example, if you edited the above example to make a two-way binding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Component } from '@angular/core';

@Component({
selector: 'app-power-boost-calculator',
template: `
<h2>Power Boost Calculator</h2>
<div>Normal power: <input [(ngModel)]="power"></div>
<div>Boost factor: <input [(ngModel)]="factor"></div>
<p>
Super Hero Power: {{power | exponentialStrength: factor}}
</p>
`
})
export class PowerBoostCalculatorComponent {
power = 5;
factor = 1;
}

The exponentialStrength pipe executes every time the user changes the “normal power” value or the “boost factor”, as shown below.

GIF from https://angular.io/generated/images/guide/pipes/power-boost-calculator-anim.gif

However, if you change something inside a composite object (such as the month of a date, an element of an array, or an object property), you need to understand how change detection works, and how to use an impure pipe.

Change Detection Mechanism

For elements except pipes, change detection in Angular is based on every DOM event. This means that Angular updates the page based on things such as keystrokes, mouse clicks, server response, and etc.

An example demonstrating the above has been shown in the section Using Template Variables, and here we show another example.

Consider the html page that looks like this:

1
2
3
4
5
6
7
8
New hero:
<input type="text" #box
(keyup.enter)="addHero(box.value); box.value=''"
placeholder="hero name">
<button (click)="reset()">Reset</button>
<div *ngFor="let hero of heroes">
{{hero.name}}
</div>

We see that the event that will get handled is keyup.enter, which will add the value to the heroes list. Now, since Angular updates its elements whenever there is an event, the *ngFor will update itself automatically.

The following would be the corresponding ts code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export class FlyingHeroesComponent {
heroes: any[] = [];
canFly = true;
constructor() { this.reset(); }

addHero(name: string) {
name = name.trim();
if (!name) { return; }
let hero = {name, canFly: this.canFly};
this.heroes.push(hero);
}

reset() { this.heroes = HEROES.slice(); }
}

Change Detection for Pipes

In the section Detecting changes with data binding in pipes, you have seen that Angular seems to update/re-run pipes as well when variables/anything you had in the data binding have changed. However, this is not true for all the cases.

For detecting changes with pipes, Angular uses the approach by detecting whether if the variable’s reference has changed. This means for primitives types, this would work nicely, but for complex objects, changing one of its many property fields will not change the object’s reference, hence Angular will not update/re-run the pipe.

For example, if you changed the previous example to:

1
2
3
4
5
6
7
8
9
New hero:
<input type="text" #box
(keyup.enter)="addHero(box.value); box.value=''"
placeholder="hero name">
<button (click)="reset()">Reset</button>

<div *ngFor="let hero of (heroes | flyingHeroes)">
{{hero.name}}
</div>

With its corresponding pipe class:

1
2
3
4
5
6
7
8
9
10
import { Pipe, PipeTransform } from '@angular/core';

import { Flyer } from './heroes';

@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}

You will see that things don’t work since (heroes | flyingHeroes) will not get re-run and the same heroes gets ngFored. This is because the addHero() method we have seen pushes new hero to the heroes list, which does not change the heroes list’s reference.

To solve the issue, you can either:

  • Change your addHero() method to create a new array and replace heroes to that new array
    • Since this changed the reference, (heroes | flyingHeroes) will get re-run and new data will be seen
  • Use an impure pipe

impure Pipe

pure changes means changes that causes changes to the variable’s references, which is basically what’s mentioned above.

impure changes would be changes that are within a composite object.

Therefore, in order to solve the problem mentioned above, we need to create an impure pipe that deals with impure changes.

In summary, you need to:

  • Create a pipe class as usual, with its @Pipe() annotation and implementing the PipeTransform interface
  • Add the property pure: false in the @Pipe() annotation

For example:

1
2
3
4
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})

And a complete example would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* tslint:disable use-pipe-transform-interface */
import { Pipe, PipeTransform } from '@angular/core';

import { Flyer } from './heroes';

@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}

/////// Identical except for the pure flag
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
export class FlyingHeroesImpurePipe extends FlyingHeroesPipe {}


/*
Copyright Google LLC. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/

Lastly, don’t forget to change your html to use the impure pipe:

1
2
3
<div *ngFor="let hero of (heroes | flyingHeroesImpure)">
{{hero.name}}
</div>

async Pipe

The async pipe is an impure pipe, which is used commonly for handling event handling, asynchronous programming, and handling multiple values.

The built-in AsyncPipe to accept an observable as input and subscribe to the input automatically. Without this pipe, your component code would have to subscribe to the observable to consume its values, extract the resolved values, expose them for binding, and unsubscribe when the observable is destroyed in order to prevent memory leaks. AsyncPipe is an impure pipe that saves boilerplate code in your component to maintain the subscription and keep delivering values from that observable as they arrive.

Observables provide support for passing messages asynchronously between parts of your application (also between another server and your application, which we will discuss in Accessing Servers Over HTTP)

For details and examples of observables, see the Observables Overview.

The following code example binds an observable of message strings (message$) to a view with the async pipe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { Component } from '@angular/core';

import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';

@Component({
selector: 'app-hero-message',
template: `
<h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message$ | async }}</p>
<button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
message$: Observable<string>;

private messages = [
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];

constructor() { this.resend(); }

resend() {
this.message$ = interval(500).pipe(
map(i => this.messages[i]),
take(this.messages.length)
);
}
}

Caching HTTP Request

Another example of using observables is the HTTPClient service, which uses observables and offers the HTTPClient.get() method to fetch data from a server. The asynchronous method get() sends an HTTP request, and returns an observable that emits the requested data for the response.

The following code from Angular mimics an async data sending and processes it with async pipe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { HttpClient }          from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';

constructor(private http: HttpClient) { }

transform(url: string): any {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe(result => this.cachedData = result);
}

return this.cachedData;
}
}

The corresponding parent component is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Component } from '@angular/core';

@Component({
selector: 'app-hero-list',
template: `
<h2>Heroes from JSON File</h2>

<div *ngFor="let hero of ('assets/heroes.json' | fetch) ">
{{hero.name}}
</div>

<p>Heroes as JSON:
{{'assets/heroes.json' | fetch | json}}
</p>`
})
export class HeroListComponent { }

The built-in JsonPipe provides a way to diagnose a mysteriously failing data binding or to inspect an object for future binding.

Lifecycle Hooks

A component instance has a lifecycle that starts when Angular instantiates the component class and renders the component view along with its child views. The lifecycle continues with change detection, as Angular checks to see when data-bound properties change, and updates both the view and the component instance as needed. The lifecycle ends when Angular destroys the component instance and removes its rendered template from the DOM.

Directives have a similar lifecycle, as Angular creates, updates, and destroys instances in the course of execution.

Your application can use lifecycle hook methods to tap into key events in the lifecycle of a component or directive in order to initialize new instances, initiate change detection when needed, respond to updates during change detection, and clean up before deletion of instances. This can be achieved by implementing one or more of the lifecycle hook interfaces in the Angular core library.

For example, to tap into the hook method named ngOnInit() (so that Angular calls it shortly after checking the input properties for that component or directive for the first time):

1
2
3
4
5
6
7
8
9
10
11
@Directive()
export class PeekABooDirective implements OnInit {
constructor(private logger: LoggerService) { }

// implement OnInit's `ngOnInit` method
ngOnInit() { this.logIt(`OnInit`); }

logIt(msg: string) {
this.logger.log(`#${nextId++} ${msg}`);
}
}

Note:

  • Each interface defines the prototype for a single hook method, whose name is the interface name prefixed with ng. For example, the OnInit interface has a hook method named ngOnInit(), as shown above.

Lifecycle Event Sequence

After your application instantiates a component or directive by calling its constructor, Angular calls the hook methods you have implemented at the appropriate point in the lifecycle of that instance.

Angular executes hook methods in the following sequence. You can use them to perform the following kinds of operations:

Hook method Purpose Timing
ngOnChanges() Respond when Angular sets or resets data-bound input properties. The method receives a SimpleChanges object of current and previous property values. Note that this happens very frequently, so any operation you perform here impacts performance significantly. See details in Using change detection hooks in this document. Called before ngOnInit() and whenever one or more data-bound input properties change.
ngOnInit() Initialize the directive or component after Angular first displays the data-bound properties and sets the directive or component’s input properties. See details in Initializing a component or directive in this document. Called once, after the first ngOnChanges().
ngDoCheck() Detect and act upon changes that Angular can’t or won’t detect on its own. See details and example in Defining custom change detection in this document. Called immediately after ngOnChanges() on every change detection run, and immediately after ngOnInit() on the first run.
ngAfterContentInit() Respond after Angular projects external content into the component’s view, or into the view that a directive is in.See details and example in Responding to changes in content in this document. Called once after the first ngDoCheck().
ngAfterContentChecked() Respond after Angular checks the content projected into the directive or component.See details and example in Responding to projected content changes in this document. Called after ngAfterContentInit() and every subsequent ngDoCheck().
ngAfterViewInit() Respond after Angular initializes the component’s views and child views, or the view that contains the directive.See details and example in Responding to view changes in this document. Called once after the first ngAfterContentChecked().
ngAfterViewChecked() Respond after Angular checks the component’s views and child views, or the view that contains the directive. Called after the ngAfterViewInit() and every subsequent ngAfterContentChecked().
ngOnDestroy() Cleanup just before Angular destroys the directive or component. Unsubscribe Observables and detach event handlers to avoid memory leaks. See details in Cleaning up on instance destruction in this document. Called immediately before Angular destroys the directive or component.

(Table source: https://angular.io/guide/lifecycle-hooks#lifecycle-event-sequence)

Using ngOnInit

Use the ngOnInit() method to perform the following initialization tasks.

  • Perform complex initializations outside of the constructor. Components should be cheap and safe to construct. You should not, for example, fetch data in a component constructor. You shouldn’t worry that a new component will try to contact a remote server when created under test or before you decide to display it.

    An ngOnInit() is a good place for a component to fetch its initial data. For an example, see the Tour of Heroes tutorial.

    In Flaw: Constructor does Real Work, Misko Hevery, Angular team lead, explains why you should avoid complex constructor logic.

  • Set up the component after Angular sets the input properties. Constructors should do no more than set the initial local variables to simple values.

    Keep in mind that a directive’s data-bound input properties are not set until after construction. If you need to initialize the directive based on those properties, set them when ngOnInit() runs.

    The ngOnChanges() method is your first opportunity to access those properties. Angular calls ngOnChanges() before ngOnInit(), but also many times after that. It only calls ngOnInit() once.

Using ngOnDestroy

Put cleanup logic in ngOnDestroy(), the logic that must run before Angular destroys the directive.

This is the place to free resources that won’t be garbage-collected automatically. You risk memory leaks if you neglect to do so.

  • Unsubscribe from Observables and DOM events.
  • Stop interval timers.
  • Unregister all callbacks that the directive registered with global or application services.

The ngOnDestroy() method is also the time to notify another part of the application that the component is going away.

Using ngOnChanges

Angular calls the ngOnChanges() method of a component or directive whenever it detects changes to the input properties. The following example demonstrates this by monitoring the OnChanges() hook.

1
2
3
4
5
6
7
8
ngOnChanges(changes: SimpleChanges) {
for (let propName in changes) {
let chng = changes[propName];
let cur = JSON.stringify(chng.currentValue);
let prev = JSON.stringify(chng.previousValue);
this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
}
}

The ngOnChanges() method takes an object that maps each changed property name to a SimpleChange object holding the current and previous property values. This hook iterates over the changed properties and logs them.

For all the other Lifecycle Hooks not mentioned above, please visit https://angular.io/guide/lifecycle-hooks#general-examples

Forms for User Input

Handling user input with forms is the cornerstone of many common applications. Applications use forms to enable users to log in, to update a profile, to enter sensitive information, and to perform many other data-entry tasks.

Angular provides two different approaches to handling user input through forms: reactive and template-driven. Both capture user input events from the view, validate the user input, create a form model and data model to update, and provide a way to track changes.

Reactive forms and template-driven forms process and manage form data differently. Each approach offers different advantages.

  • Reactive forms provide direct, explicit access to the underlying forms object model. Compared to template-driven forms, they are more robust: they’re more scalable, reusable, and testable. If forms are a key part of your application, or you’re already using reactive patterns for building your application, use reactive forms.
  • Template-driven forms rely on directives in the template to create and manipulate the underlying object model. They are useful for adding a simple form to an app, such as an email list signup form. They’re easy to add to an app, but they don’t scale as well as reactive forms. If you have very basic form requirements and logic that can be managed solely in the template, template-driven forms could be a good fit.

The following sections only covers using reactive forms. If you need information on using template-driven forms, please visit https://angular.io/guide/forms-overview for more information.

Common Form Foundation Classes

Both reactive and template-driven forms are built on the following base classes.

  • FormControl tracks the value and validation status of an individual form control.
  • FormGroup tracks the same values and status for a collection of form controls.
  • FormArray tracks the same values and status for an array of form controls.
  • ControlValueAccessor creates a bridge between Angular FormControl instances and native DOM elements.

Setting Up Reactive Forms

In general, all the information will be sent to the FormControl object that you will create. So, to use reactive forms, you need to:

  • Register the ReactiveFormsModule in your app module (e.g. app.module.ts).

    • This module declares the reactive-form directives that you need to use reactive forms.
  • Import the necessary module and create a FormControl object in your ts

  • Bind to the directive formControl in your html template with the object you created in the step above

For example, to register the ReactiveFormsModule (in order to use the FormControl) directive, you can do:

1
2
3
4
5
6
7
8
9
import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
imports: [
// other imports ...
ReactiveFormsModule
],
})
export class AppModule { }

Then, you can use the formControl directive in your html with:

1
2
3
4
<label>
Name:
<input type="text" [formControl]="name">
</label>

And that name would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

@Component({
selector: 'app-name-editor',
templateUrl: './name-editor.component.html',
styleUrls: ['./name-editor.component.css']
})

export class NameEditorComponent {
// binds to this FormControl object, which you will use later
name = new FormControl('');
}

Using Reactive Forms

Now, the key comes to accessing the value that is stored in that FormControl object.

You can access the value in the following ways.

  • Through the valueChanges observable where you can listen for changes in the form’s value in the template using AsyncPipe or in the component class using the subscribe() method.
  • With the value property, which gives you a snapshot of the current value.

The following example shows you how to display the current value using interpolation in the template.

1
2
3
<p>
Value: {{ name.value }}
</p>

The displayed value changes as you update the form control element, since changing the input will trigger input events that will be handled by formControl directive.

In general, reactive forms provide access to information about a given control through properties and methods provided with each instance. These properties and methods of the underlying AbstractControl class are used to control form state and determine when to display messages when handling input validation.

Read about other FormControl properties and methods in the API Reference.

Grouping Form Controls

The above reactive form examples is fine when there is only a limited number of inputs. However, when you are dealing with a form with quite a few input fields, or even some nested input structures, you should try to use a FormGroup together with FormControls to manage multiple inputs cleanly.

In summary, when you want to group multiple inputs, you need to:

  • Create a top level FormGroup that contains all the information of its children
  • Create children elements within that top level FormGroup
    • For FormControls, you create them with formControlName="someName"
    • For nested FormGroups, you create them with formGroupName="someOtherName"
  • Create a FormGroup object with the name defined in step 1 in your ts, and pass in a map reflecting the same structure as step 2 into its constructor
  • Most of the time you want to save that data and use it later. So you also need to create a button (or anything else) that emits a submit event, and you can save all the data and handle them with (ngSubmit)="onSubmit()"
    • all the data will be stored in the FormGroup object you created in step 3. The data will be package into a map, where the key will be either the fromControlName or the formGroupName
  • *You can also add validators for your form, this will be touched on in the section Validate Form Input

Example: Simple Grouping Form

First, you create the form structure in your html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- Notice that only the top level needs binding, since all the control/information of its children are passed to it -->
<form [formGroup]="profileForm">

<label>
First Name:
<input type="text" formControlName="firstName">
</label>

<label>
Last Name:
<input type="text" formControlName="lastName">
</label>

</form>

Then, in your ts, you create that FormGroup object with name profileForm, and pass in the same children hierarchy into its constructor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
// its constructor takes a mapping, that maps the name you specified with the type of form (formControl/formGroup)
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
});
}

Then, if you want to save the value, you need to add a button (or anything else) that can emit a submit event.

Luckily, this event can be emitted natively by html with <button type="submit">:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- (ngSubmit) listens for the submit event and triggers onSubmit() method -->
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

<label>
First Name:
<input type="text" formControlName="firstName">
</label>

<label>
Last Name:
<input type="text" formControlName="lastName">
</label>

<!-- Native emitter for submit events -->
<button type="submit">Submit</button>
</form>

Now, for most of the cases, your data handling would be outside of this component where you define all the form actions. This means you will probably use an EventEmitter and @Output that to other components for handling.

Here, we just show you how to access the data of the forms:

1
2
3
4
onSubmit() {
// TODO: Use EventEmitter with form value
console.warn(this.profileForm.value);
}

And the profile.value will be a map that looks like:

1
{firstName: "user's input", lastName: "user's input"}

Example: Complex Grouping Form

If you have nested FormGroups within a top level form group, most of the logics will be the same as above.

For example, to also get address information besides name, you can nest another formGroup inside the top level form:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<form [formGroup]="profileForm">

<label>
First Name:
<input type="text" formControlName="firstName">
</label>

<label>
Last Name:
<input type="text" formControlName="lastName">
</label>

<!-- Notice you nest it in a div -->
<div formGroupName="address">
<h3>Address</h3>

<label>
Street:
<input type="text" formControlName="street">
</label>

<label>
City:
<input type="text" formControlName="city">
</label>

<label>
State:
<input type="text" formControlName="state">
</label>

<label>
Zip Code:
<input type="text" formControlName="zip">
</label>
</div>

</form>

Then the corresponding ts would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = new FormGroup({
firstName: new FormControl(''),
lastName: new FormControl(''),
address: new FormGroup({ // simply another FormGroup object is passed in
street: new FormControl(''),
city: new FormControl(''),
state: new FormControl(''),
zip: new FormControl('')
})
});
}

Then the rest is the same (e.g. add a submit button). However, since you have nested another formGroup, the value you get from profileForm.value will be a nested map as well:

1
{firstName: "user's input", lastName: "user's input", address: {street: "user's input", ...}}

Update Values in the Form Group

When you have a single FormControl, you can simply change the values in that input by changing the value property. However, now you have a FormGroup that collects all its power from its child FormControl and FormGroup. To update/change the value of a specific child under the top level FormGroup, you can use the methods setValue() or patchValue() of the FormGroup object:

  • setValue()
    • sets a new value for the entire FormGroup object, meaning that you need to set all of its children FormControl or FormGroups. Sometimes this will not be necessary, but it can be useful since it will explicitly throw an error when the structure you provided in the setValue() method differs from the FormGroup structure
  • patchValue()
    • patches specific values for the FormGroup object, meaning that you do not need to set all of its children. However, if there is a mismatch between the structure you provided in patchValue() and the FormGroup in html, it will fail silently.

For example, to update only the firstName and street information in the above form:

1
2
3
4
5
6
7
8
9
updateProfile() {
// using patchValue()
this.profileForm.patchValue({
firstName: 'Nancy',
address: {
street: '123 Drew Street'
}
});
}

However, if you want to use setValue():

1
2
3
4
5
6
7
8
9
10
11
12
13
updateProfile() {
// using setValue()
this.profileForm.patchValue({
firstName: 'Nancy',
lastName: ''
address: {
street: '123 Drew Street',
city: '',
state: '',
zip: ''
}
});
}

Using FormBuilder Service

Angular also provides a builder class that can simplify the syntax for creating complex forms. The FormBuilder has three methods: control(), group(), array(), which generates form controls, form groups, and form arrays (see Creating Dynamic Forms).

In summary, you need to:

  • Import the FormBuilder from @angular/forms
  • Use group to create form groups, control to create form control, array to create form arrays.

For example, the same form above would be refactored to be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
selector: 'app-profile-editor',
templateUrl: './profile-editor.component.html',
styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
profileForm = this.fb.group({
firstName: [''], // notice the array here
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});

constructor(private fb: FormBuilder) { }
}

Note:

  • Notice that the value for each control name is an array containing the initial value as the first item in the array. You can define the control with just the initial value, but if your controls need sync or async validation, add sync and async validators as the second and third items in the array.

Validate Form Input

Form validation is used to ensure that user input is complete and correct. This section covers adding a single validator to a form control and displaying the overall form status. Form validation is covered more extensively in the Form Validation guide.

In summary, to add a validator, you need to:

  • Import a validator function (e.g. Validators.required from @angular/forms)
  • Add that validator function to the field(s) of your form
  • *Add logic to handle the validation status
    • There is a native status property of a form object, which will be invalid if any of its children is invalid

For example, to make the field firstName required, you can use the Validators.required function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Validators } from '@angular/forms';
...
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
});

constructor(private fb: FormBuilder) { }

Alternatively, since there is also a native required validator in html, the following has the same effect:

1
<input type="text" formControlName="firstName" required>

Then, you can see the status with:

1
2
3
<p>
Form Status: {{ profileForm.status }}
</p>

which will be invalid if firstName is not filled, or simply with:

1
2
3
<p>
Form Valid: {{ profileForm.valid }}
</p>

which will be false if firstName is not filled.

So a simple usage would be disabling the submit button if the status is invalid:

1
<button type="submit" [disabled]="!profileForm.valid">Submit</button>

Creating Dynamic Forms with Form Array

FormArray is an alternative to FormGroup for managing any number of unnamed controls. As with form group instances, you can dynamically insert and remove controls from form array instances, and the form array instance value and validation status is calculated from its child controls. However, you don’t need to define a key for each control by name, so this is a great option if you don’t know the number of child values in advance.

To define a dynamic form, take the following steps.

  1. Import the FormArray class.
  2. Define a FormArray control.
  3. Access the FormArray control with a getter method, so that you could access its fields or populate it
    • You can populate the FormArray control with FormControl instances or FormGroup instances by push() to that array
  4. Display the form array in a template.

The following uses a basic approach of creating a dynamic form using a form array. For more information on creating a dynamic form, please visit https://angular.io/guide/dynamic-form#building-dynamic-forms.

For example:

Your html would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
...

<div formArrayName="aliases">

<h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>

<div *ngFor="let alias of aliases.controls; let i=index">
<!-- The repeated alias template -->
<label>
Alias:
<input type="text" [formControlName]="i">
</label>
</div>

</div>
</form>

where aliases.controls would return a list of FormControls that is stored in the array.

Your ts would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { FormArray } from '@angular/forms';
...
profileForm = this.fb.group({
firstName: ['', Validators.required],
lastName: [''],
address: this.fb.group({
street: [''],
city: [''],
state: [''],
zip: ['']
}),
// form array created here
aliases: this.fb.array([
this.fb.control('')
])
});

Then, to get that form array, you can do:

1
2
3
4
get aliases() {
// gets the FormArray
return this.profileForm.get('aliases') as FormArray;
}

Now, once you have that FormArray, you can manipulate it, such as populating it with more FormControls:

1
2
3
addAlias() {
this.aliases.push(this.fb.control(''));
}

Lastly, if you look at the profileForm.values, you will see that the aliases will have data stored in an array:

1
{ "firstName": "", "lastName": "", "address": { "street": "", ...}, "aliases": [ "" ] }

Accessing Servers Over HTTP

Most front-end applications need to communicate with a server over the HTTP protocol, in order to download or upload data and access other back-end services. Angular provides a simplified client HTTP API for Angular applications, the HttpClient service class in @angular/common/http.

The HTTP client service offers the following major features.

Requesting Data from Server with GET

Use the HTTPClient.get() method to fetch data from a server. The asynchronous method sends an HTTP request, and returns an Observable that emits the requested data when the response is received.

Note:

  • In order for this to work with servers on a different domain, you either need to have CORS protocol enabled (for Spring Boot, this can be done simply with @CrossOrigin(origins = {"<other domain address>"}), or use the JSONP request.

The get() method takes two arguments; the endpoint URL from which to fetch, and an options object that you can use to configure the request.

The options object contains fields like this:

1
2
3
4
5
6
7
8
options: {
headers?: HttpHeaders | {[header: string]: string | string[]},
observe?: 'body' | 'events' | 'response',
params?: HttpParams|{[param: string]: string | string[]},
reportProgress?: boolean,
responseType?: 'arraybuffer'|'blob'|'json'|'text',
withCredentials?: boolean,
}

Important options include the observe and responseType (params is useful when you have query strings) properties.

  • The observe option specifies how much of the response to return.
  • The responseType option specifies the format in which to return data.

Applications often request JSON data from a server. If you want to get JSON data, you need to do:

  • specify a URL that maps to the get request in your backend
  • specify the options with {observe: 'body', responceType: 'json'}
    • There are actually the default value as well. So if you specified nothing (did not provide the options), it works as well.

For example:

You can get JSON data (e.g. Objects) with:

1
2
3
4
5
6
configUrl = 'assets/someQuery';

getConfig() {
const options = { observe: 'body', responceType: 'json' };
return this.http.get(this.configUrl, options);
}

which is equivalent to:

1
2
3
4
5
configUrl = 'assets/someQuery';

getConfig() {
return this.http.get(this.configUrl);
}

Now, because the service method returns an Observable of configuration data, you need to subscribe to the method’s return value in order to see results.

The subscription callback performs minimal post-processing. It copies the data fields into the component’s config object, which is data-bound in the component template for display.

1
2
3
4
5
6
7
private config: Config;

showConfig() {
// subscribe( <receivedData: TypeToCast> => variableToStore = <receivedData> )
this.configService.getConfig()
.subscribe((data: Config) => this.config = data);
}

Requesting a Typed Response

The above takes quite some effort in the end to typecast to the desired object you need. While this is fine, you could make the data handling easier by declaring the type of the response object with get<Type>().

In summary, you need to:

  • Create an interface rather than class so that JSON can be converted to an instance of the interface
  • Supply that interface to the get<TypeHere>()

For example, if you have a Config interface for the examples above:

1
2
3
4
export interface Config {
heroesUrl: string;
textfile: string;
}

Then you can do:

1
2
3
4
5
// this should be in one of your service class
getConfig() {
// now returns an Observable of Config
return this.http.get<Config>(this.configUrl);
}

This can simplify your code in your component:

1
2
3
4
5
6
7
config: Config;

showConfig() {
this.configService.getConfig()
// clone the data object, using its known Config shape
.subscribe(data => this.config = data);
}

Reading a Full Response

The above example only reads the body of a html response. However, a full response contain information such as headers, status, statusText, url, ok, type, and finally body.

To get the full response, we need to change to observe: 'response' instead of the default observe: 'body'.

OBSERVE AND RESPONSE TYPES

The types of the observe and response options are string unions, rather than plain strings.

1
2
3
4
5
6
7
options: {
...
observe?: 'body' | 'events' | 'response',
...
responseType?: 'arraybuffer'|'blob'|'json'|'text',
...
}

This can cause confusion. For example:

1
2
3
4
5
6
7
8
// this works
client.get('/foo', {responseType: 'text'})

// but this does NOT work
const options = {
responseType: 'text',
};
client.get('/foo', options)

In the second case, TypeScript infers the type of options to be {responseType: string}. The type is too wide to pass to HttpClient.get which is expecting the type of responseType to be one of the specific strings. HttpClient is typed explicitly this way so that the compiler can report the correct return type based on the options you provided.

Use as const to let TypeScript know that you really do mean to use a constant string type:

1
2
3
4
const options = {
responseType: 'text' as const,
};
client.get('/foo', options);

So, if you need more information about the transaction than is contained in the response body (e.g. sometimes servers return special headers or status codes to indicate certain conditions that are important to the application workflow), you can have:

1
2
3
4
5
6
// notice the HttpResponse<Config> object
getConfigResponse(): Observable<HttpResponse<Config>> {
// this is still Config
return this.http.get<Config>(
this.configUrl, { observe: 'response' });
}

Now HttpClient.get() returns an Observable of type HttpResponse rather than just the JSON data contained in the body.

Requesting Non-JSON Data

Not all APIs return JSON data. In this next example, a DownloaderService method reads a text file from the server and logs the file contents, before returning those contents to the caller as an Observable<string>.

For example:

1
2
3
4
5
6
7
8
9
10
11
12
getTextFile(filename: string): Observable<string> {
// The Observable returned by get() is of type Observable<string>
// because a text response was specified.
// There's no need to pass a <string> type parameter to get().
return this.http.get(filename, {responseType: 'text'})
.pipe(
tap( // Log the result or error
data => this.log(filename, data),
error => this.logError(filename, error)
)
);
}

where:

  • The RxJS tap operator (as in “wiretap”) lets the code inspect both success and error values passing through the observable without disturbing them.
    • data would be the data returned from server if the request is successful
    • error would be the error message if the request is not successful

Now HttpClient.get() returns a string rather than the default JSON because of the responseType option, and you can easily use it with:

1
2
3
4
5
6
private contents: string;
...
download() {
this.downloaderService.getTextFile('assets/textfile.txt')
.subscribe(results => this.contents = results);
}

Handling Request Errors

If the request fails on the server, HttpClient returns an error object instead of a successful response.

The same service that performs your server transactions should also perform error inspection, interpretation, and resolution.

When an error occurs, you can obtain details of what failed in order to inform your user. In some cases, you might also automatically retry the request. The following section explores how you could achieve those.

Getting Error Details

In general, there are two types of errors can occur.

  • The server backend might reject the request, returning an HTTP response with a status code such as 404 or 500. These are error responses.
  • Something could go wrong on the client-side such as a network error that prevents the request from completing successfully or an exception thrown in an RxJS operator. These errors produce JavaScript ErrorEvent objects.

HttpClient captures both kinds of errors in its HttpErrorResponse. You can inspect that response to identify the error’s cause.

In summary, you need to:

  1. Create a custom error handling method, which takes a HttpErrorResponce object. You can include your own logic there.

    • You should throw a customized Object that contains the error information with throwError(<YourErrorObject>), so that you could also display on the page later
  2. Pass this function to the catchErorr operator in the pipe right after the get method.

  3. *Use the error object you thrown to display some messages on the page for users to see

For example, a simple error handling method could look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
private handleError(error: HttpErrorResponse): Observable<never>{
if (error.error instanceof ErrorEvent){
console.error('client side error ' + error.error.message);
return throwError(`Please try again. ${error.error.message}`);
} else {
console.error('server side error:');
console.error(`error status: ${error.status}
error body: ${error.error}
error header: ${error.headers.keys()}
error URL: ${error.url}`);
return throwError(`Please try again. ${error.status}; ${error.message}; ${error.url}`);
}
}

The above example basically stores information in a single string. In your own application, you can create your own ErrorObject and store formatted information there.

Then, for the get() method to use this, you need to have:

1
2
3
4
5
6
7
getConfig() {
return this.http.get<Config>(this.configUrl)
// add the pipe here, pass in your function to the operator catchError()
.pipe(
catchError(this.handleError)
);
}

Now, if an error actually occurs when you call the get request, it will be handled by your handleError function.

Lastly, if you want to use the error information that you have thrown with throwError(<whatever you have here>), you could:

1
2
3
4
5
errorRequest(request: string): void{
this.heroService.errorRequest(request)
// notice this error => this.randomQueryError = error
.subscribe(data => this.randomQueryResult = data, error => this.randomQueryError = error);
}

Now, the error object that you have thrown before will be caught by subscribe and stored in randomQueryError, which then you could display by binding it to your html template.

Retrying a Failed Request

Sometimes the error is transient and goes away automatically if you try again. For example, network interruptions are common in mobile scenarios, and trying again can produce a successful result.

The RxJS library offers several retry operators. For example, the retry() operator automatically re-subscribes to a failed Observable a specified number of times. Re-subscribing to the result of an HttpClient method call has the effect of reissuing the HTTP request.

In summary, you need to:

  1. Add the operator retry(<number of retries>) to the pipe
    • This should go before catchError, since operators are executed sequentially

For example, to issue retries before deciding to catch the error, you could:

1
2
3
4
5
6
7
getConfig() {
return this.http.get<Config>(this.configUrl)
.pipe(
retry(3), // retry a failed request up to 3 times
catchError(this.handleError) // then handle the error
);
}

Sending Data to the Server

In addition to fetching data from a server, HttpClient supports other HTTP methods such as PUT, POST, and DELETE, which you can use to modify the remote data.

Making a POST Request

Apps often send data to a server with a POST request when submitting a form.

Note:

  • Data that you want to send using a POST request has a similar format to a GET request, in which both uses the key-value pair separated with & to pass data. However, POST request stores those information in the request header, yet GET request goes directly in the URL.

In summary, to send a POST request containing the data you want to post, you need to:

  1. Have the data you want to post ready
    • It can be an Object, for example
  2. Use the post<ExpectedResultObject>(URL, <YourDataObject>, httpOptions)
    • Same as get() method, post() also returns an Observable
  3. Actually fire the post event by using subscribe, which you have done with get() as well

For example:

Your service class (that does all the backend communication) could look like:

1
2
3
4
5
6
7
8
/** POST: add a new hero to the database */
addHero (hero: Hero): Observable<Hero> {
// notice that the data you want to POST is also passed in here
return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
.pipe(
catchError(this.handleError('addHero', hero))
);
}

From the above, you see the post() method takes a resource URL and two additional parameters:

  • body - The data to POST in the body of the request.
  • options` - An object containing method options which, in this case, specify required headers.

Lastly, you initiate the POST request with subscribe in your component:

1
2
this.heroesService.addHero(newHero)
.subscribe(hero => this.heroes.push(hero)); // fires the event and fetched the result

Note:

  • If you only call the addHero(newHero) method, nothing will happen as that does NOT fire the request. To actually fire the event, it is done by calling the subscribe method, which both fires the event and gets the response asynchronously.

Making a DELETE Request

This looks very similar to a GET request. The only difference for syntax is that you use the delete method instead of the get method.

In summary:

  1. Specify the DELETE request URL. This will look like a GET request URL.
  2. Send the DELETE request with delete()
    • Though the URL looks the same, a DELETE request still differs from a GET request internally. For example, if you used @DeleteMapping in Spring Boot, then using a get() in Angular does NOT work (and vice versa). You need to use delete().
  3. Subscribe to fire the event, even if you do not have any result to get from the request.

A simple example would be:

Your Spring Boot could have implemented:

1
2
3
4
5
6
7
// even this looks like a GET request, typing in the URL does NOT WORK
@DeleteMapping("/api/delHero/{id}")
public Hero deleteHero(@PathVariable int id){
Hero result = heroService.getHero(id);
heroService.delHeroById(id);
return result;
}

Then, your Angular service class would look like:

1
2
3
4
delHero(id: number): Observable<Hero>{
const URL = `http://localhost:8080/api/delHero/${id}`;
return this.httpService.delete(URL, {observe: 'response'}); // Here I want to look at the entire response
}

Now, to fire/retrieve the result from the request, you will have:

1
2
3
deleteRequest(id: string): void{
this.heroService.delHero(+id).subscribe((data: Hero) => this.deleteResult = data);
}

Making a PUT Request

PUT request looks identical to POST request in terms of syntax, yet, again, they are different internally. If you are using a @PutMapping in your Spring Boot Application, you need to send a PUT request instead of a POST (or any other request).

In summary, it is very alike making a POST request:

  1. Prepare the data that you want to update
  2. Use the put() method, which takes a URL for the request, a field for the actual data, and an optional HttpOptions object
  3. To fire the request and fetch the returned result, use subscribe

For example:

In your Spring Boot application, you could have:

1
2
3
4
@PutMapping("/api/updateHero")
public Hero updateHero(@RequestBody Hero hero){
return this.heroService.updateHero(hero);
}

Then in your Angular service class:

1
2
3
4
updateHero(hero: Hero): Observable<Hero>{
const URL = `http://localhost:8080/api/updateHero`;
return this.httpService.put<Hero>(URL, hero);
}

Now, in your component, you could have:

1
2
3
4
5
6
7
putNewHero(data: any): void {
// because I used a FormGroup, and I had a different formControlName than the properties of Hero interface
const hero = new HeroImpl(data.heroId, data.heroName);
this.heroService.updateHero(hero).subscribe(data2 => this.updateResult = data2);
// the last method clears the input field
this.clearUpdateHeroForm();
}

Adding and Updating Headers

Many servers require extra headers for the operations defined above. For example, a server might require an authorization token or “Content-Type” header.

Adding Headers

In summary, all you need to do is to specify the httpOptions

  1. Create a mapping and specify headers: new HttpHeaders({...})

For example:

1
2
3
4
5
6
7
8
import { HttpHeaders } from '@angular/common/http';

const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'my-auth-token'
})
};

Then you can pass the above httpOptions into all the request you make.

Updating Headers

Though you can’t directly modify the existing headers within the previous options object because instances of the HttpHeaders class are immutable, you can use the set() method instead, which returns a clone of the current instance with the new changes applied.

For example, when a token has expired and you want to reset it, you can do:

1
2
httpOptions.headers =
httpOptions.headers.set('Authorization', 'my-new-auth-token');

Configuring HTTP URL Parameters

Some requests, such as a GET and sometimes a DELETE request, contain query strings that carries some data for the backend to process. Although you could write them manually in a string, a better way is to set them with HttpParams().set() (again, set method is used because HttpParams is immutable).

In summary, the process is the same as above for adding headers:

  1. Create a mapping and specify params: new HttpParams().set(...)
    • Or you can create them directly with: params: new HttpParams({fromString: 'key=value'})

For example:

If you want to create them from scratch:

1
2
3
4
5
6
7
8
9
10
11
12
/* GET heroes whose name contains search term */
searchHeroes(term: string): Observable<Hero[]> {
term = term.trim();

// Add safe, URL encoded search parameter if there is a search term
const options = term ? { params: new HttpParams({fromString: `name=${term}`})} : {};

return this.http.get<Hero[]>(this.heroesUrl, options)
.pipe(
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}

If you are updating (or creating from scratch):

1
2
3
4
5
6
7
8
9
10
11
searchHeroes(term: string): Observable<Hero[]> {
term = term.trim();

// Using set() method
const options = term ? { params: new HttpParams().set('name', term) } : {};

return this.http.get<Hero[]>(this.heroesUrl, options)
.pipe(
catchError(this.handleError<Hero[]>('searchHeroes', []))
);
}

Intercepting Requests and Responses

With interception, you declare interceptors that inspect and transform HTTP requests from your application to a server. The same interceptors can also inspect and transform a server’s responses on their way back to the application. Multiple interceptors form a forward-and-backward chain of request/response handlers.

Interceptors can perform a variety of implicit tasks, from authentication to logging, in a routine, standard way, for every HTTP request/response.

Without interception, developers would have to implement these tasks explicitly for each HttpClient method call.

In summary, to use an interceptor(s), you need to:

  1. Create a class(es) that implements HttpInterceptor
  2. Implement the intercept() method, handling your interceptor logics there
  3. Provide that Interceptor at the same place as you provide the HttpClient
    • Because interceptors are (optional) dependencies of the HttpClient service, you must provide them in the same injector (or a parent of the injector) that provides HttpClient. Interceptors provided after DI creates the HttpClient are ignored.
    • After you have provided them, they will be used automatically in the order you provided them as they are dependencies of the HttpClient service

Creating an Interceptor

To implement an interceptor, declare a class that implements the intercept() method of the HttpInterceptor interface.

Below is an example of a no operation interceptor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Injectable } from '@angular/core';
import {
HttpEvent, HttpInterceptor, HttpHandler, HttpRequest
} from '@angular/common/http';

import { Observable } from 'rxjs';

/** Pass untouched request through to the next request handler. */
@Injectable()
export class NoopInterceptor implements HttpInterceptor {

intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
return next.handle(req);
}
}

Most interceptors inspect the request on the way in and forward the (perhaps altered) request to the handle() method of the next object which implements the HttpHandler interface.

1
2
3
export abstract class HttpHandler {
abstract handle(req: HttpRequest<any>): Observable<HttpEvent<any>>;
}

Like intercept(), the handle() method transforms an HTTP request into an Observable of HttpEvents which ultimately include the server’s response. The intercept() method could inspect that observable and alter it before returning it to the caller.

In general:

  • If you want to inspect and manipulate the HttpRequest<any>, you should do it inside the intercept() method.
  • If you want to inspect and manipulate the HttpResponse<any>, you should do it inside the handle() method.

Note:

  • Both HttpRequest and HttpRepsonse are HttpEvent.

next: HttpInterceptor

The next object represents the next interceptor in the chain of interceptors. The final next in the chain is the HttpClient backend handler that sends the request to the server and receives the server’s response.

Most interceptors call next.handle() so that the request flows through to the next interceptor and, eventually, the backend handler. An interceptor could skip calling next.handle(), short-circuit the chain, and return its own Observable with an artificial server response.

This is a common middleware pattern found in frameworks such as Express.js.

Registering the Interceptor

The NoopInterceptor is a service managed by Angular’s dependency injection (DI) system. Like other services, you must provide the interceptor class before the app can use it.

However. because interceptors are (optional) dependencies of the HttpClient service, you must provide them in the same injector (or a parent of the injector) that provides HttpClient.

For example:

If you have provided HttpClient in the app’s root injector (as a side-effect of importing the HttpClientModule in AppModule), you should provide interceptors in AppModule as well.

After importing the HTTP_INTERCEPTORS injection token from @angular/common/http, write the NoopInterceptor provider like this:

1
{ provide: HTTP_INTERCEPTORS, useClass: NoopInterceptor, multi: true },

Note the multi: true option. This required setting tells Angular that HTTP_INTERCEPTORS is a token for a multiprovider (because you will add multiple interceptors later) that injects an array of values, rather than a single value.

Though you could directly write all of the interceptors manually inside the AppModule, it is usually more convenient to create another file that exports the array of interceptors as follows:

1
2
3
4
5
6
7
8
9
10
11
/* "Barrel" of Http Interceptors */
import { HTTP_INTERCEPTORS } from '@angular/common/http';

import { NoOpInterceptor } from './NoOp-interceptor';
import {NoOpInterceptor2} from './NoOp-interceptor2';

/** Http interceptor providers in outside-in order */
export const httpInterceptorProviders = [
{ provide: HTTP_INTERCEPTORS, useClass: NoOpInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: NoOpInterceptor2, multi: true }
];

Lastly, you import this const into AppModule:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
...

@NgModule({
declarations: [
...
],
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
HttpClientModule,
ReactiveFormsModule
],
providers: [httpInterceptorProviders], // added here
bootstrap: [AppComponent]
})
export class AppModule { }
Interceptor Order

Interceptor will be called based on the order of registration. In the example above, NoOpInterceptor will be called first, and then NoOpInterceptor2 will happen next.

In general, if you provide interceptors A, then B, then C, requests flow in A->B->C and responses flow out C->B->A.

However, you cannot change the order or remove interceptors later. If you need to enable and disable an interceptor dynamically, you’ll have to build that capability into the interceptor itself.

Modifying a Request

Most HttpClient methods return observables of HttpResponse<any>. The HttpResponse class itself is actually an event, whose type is HttpEventType.Response. A single HTTP request can, however, generate multiple events of other types, including upload and download progress events. The methods HttpInterceptor.intercept() and HttpHandler.handle() return observables of HttpEvent<any>.

Many interceptors are only concerned with the outgoing request and return the event stream from next.handle() without modifying it. Some interceptors, however, need to examine and modify the response from next.handle(); these operations can see all of these events in the stream.

Although interceptors are capable of modifying requests and responses, the HttpRequest and HttpResponse instance properties are readonly, rendering them largely immutable. They are immutable for a good reason: an app might retry a request several times before it succeeds, which means that the interceptor chain can re-process the same request multiple times. If an interceptor could modify the original request object, the re-tried operation would start from the modified request rather than the original. Immutability ensures that interceptors see the same request for each try.

This means that if you want to modify a request, including its header/body/URL and etc., you need to create a clone and modify it from there.

Modifying a Request Body

The readonly assignment guard can’t prevent deep updates and, in particular, it can’t prevent you from modifying a property of a request body object. However, it is in general a better practice if you still modify its copy:

  1. Copy the body and make your change in the copy.
  2. Clone the request object, using its clone() method.
  3. Replace the clone’s body with the modified copy.
1
2
3
4
5
6
// copy the body and trim whitespace from the name property
const newBody = { ...body, name: body.name.trim() };
// clone request and set/replace its body
const newReq = req.clone({ body: newBody });
// send the cloned request to the next handler.
return next.handle(newReq);

If you want to clear the body, you can use the same approach.

However, you need to be careful of setting the body to null instead of undefined:

1
2
3
newReq = req.clone({ ... }); // body not mentioned => preserve original body
newReq = req.clone({ body: undefined }); // preserve original body
newReq = req.clone({ body: null }); // clear the body

Note:

  • This means that if you set the cloned request body to undefined, Angular assumes you intend to leave the body as is.
Modifying a Request Header

Again, similar to setting the request body, you need to clone the request object again in order to set/add the request header.

In summary, you need to:

  1. Use the set() method to update/add a header
    • This will return a cloned header object with your changes, which saves you some time
  2. Clone the request object
  3. Replace the clone’s headers

A common usage is to add an authorization token in the header once the user is authorized, so that all the outgoing request will have that token and the user could potentially have more access to your backend APIs.

The above can be implemented with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { AuthService } from '../auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
// Get the auth token from the service.
const authToken = this.auth.getAuthorizationToken();

// Clone the request and replace the original headers with
// cloned headers, updated with the authorization.
const authReq = req.clone({
headers: req.headers.set('Authorization', authToken)
});

// send cloned request with header to the next handler.
return next.handle(authReq);
}
}

In fact, the practice of cloning a request to set new headers is so common that there’s a setHeaders shortcut for it:

1
2
// Clone the request and set the new header in one step.
const authReq = req.clone({ setHeaders: { Authorization: authToken } });

So you could technically do the above with one step:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { AuthService } from '../auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
const authToken = this.auth.getAuthorizationToken();

const authReq = req.clone({ setHeaders: { Authorization: authToken } });

// send cloned request with header to the next handler.
return next.handle(authReq);
}
}

Using Interceptors for Logging

Because interceptors can process the request and response together, they can perform tasks such as timing and logging an entire HTTP operation by using the pipe() method.

For example, the below snippet will record the time before the event reaches the backend and the time when response is provided:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { finalize, tap } from 'rxjs/operators';
import { MessageService } from '../message.service';

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
constructor(private messenger: MessageService) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
const started = Date.now();
let ok: string;

// extend server response observable with logging
return next.handle(req)
.pipe(
tap(
// Succeeds when there is a response; ignore other events
event => ok = event instanceof HttpResponse ? 'succeeded' : '',
// Operation failed; error is an HttpErrorResponse
error => ok = 'failed'
),
// Log when response observable either completes or errors
finalize(() => {
const elapsed = Date.now() - started;
const msg = `${req.method} "${req.urlWithParams}"
${ok} in ${elapsed} ms.`;
this.messenger.add(msg);
})
);
}
}

where:

  • The RxJS tap operator captures whether the request succeeded or failed.
  • The RxJS finalize operator is called when the response observable either errors or completes (which it must), and reports the outcome to the MessageService.

Note:

  • Though the intercept method gets hold of the response, neither tap nor finalize touch the values of the observable stream returned to the caller. You should not modify the response unless it is absolutely necessary.

Using Interceptors for Caching

Interceptors can handle requests by themselves, without forwarding to next.handle().

In summary, if you want to do this, you need to:

  1. Create and inject a RequestCache cache, where your cache will be stored
  2. Write a method to determine whether if a request is cacheable, before determining to cache the request or not
  3. Write a method to re-send the request.
    • You can simply continue to send the request with next.handle(req)
    • If you need to change something in the request, such as request header, you can do so as well.

For example:

Your main CachingInterceptor that intercepts and determines whether to return the result from cache or proceed the request would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Injectable()
export class CachingInterceptor implements HttpInterceptor {
// where caches will be stored
constructor(private cache: RequestCache) {}

intercept(req: HttpRequest<any>, next: HttpHandler) {
// continue if not cacheable.
if (!isCacheable(req)) { return next.handle(req); }

const cachedResponse = this.cache.get(req);
return cachedResponse ?
of(cachedResponse) : sendRequest(req, next, this.cache);
}
}

where:

  • The isCacheable() function determines if the request is cacheable.
  • If the request is not cacheable, the interceptor simply forwards the request to the next handler in the chain.
  • If a cacheable request is found in the cache, the interceptor returns an of() observable with the cached response, by-passing the next handler (and all other interceptors downstream).
  • If a cacheable request is not in cache, the code calls sendRequest().
    • You can/should handle the cache adding before sending with next.handle(req) in that sendRequest() method.
    • Or you can modify the request (following the contents mentioned above), cache it, and then send it with next.handle(req).

For instance, if you want to delete the request header information in the sendRequest() processing, you can:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Get server response observable by sending request to `next()`.
* Will add the response to the cache on the way out.
*/
function sendRequest(
req: HttpRequest<any>,
next: HttpHandler,
cache: RequestCache): Observable<HttpEvent<any>> {

// No headers allowed in npm search request
const noHeaderReq = req.clone({ headers: new HttpHeaders() });

return next.handle(noHeaderReq).pipe(
tap(event => {
// There may be other events besides the response.
if (event instanceof HttpResponse) {
cache.put(req, event); // Update the cache.
}
})
);
}

Note:

  • Even in the case above, the original response continues untouched back up through the chain of interceptors to the application caller.

Tracking and Showing Request Progress

Sometimes applications transfer large amounts of data and those transfers can take a long time. File uploads are a typical example. You can give the users a better experience by providing feedback on the progress of such transfers.

In summary, you need to:

  1. Create a request that has the option reportProgress: true specified
    • If you are constructing a raw request, that is all you need to do
    • If you are constructing from an existing request (e.g. POST), you also need to add observe: event
      • This is because every progress event triggers change detection, and you can see those events if you specify observe: event
  2. Use map() to process the event that you get from the request, and tap to provide messages for users to see

For example:

If you are constructing a raw request:

1
2
3
const req = new HttpRequest('POST', '/upload/file', file, {
reportProgress: true
});

Then, to listen and map the events reported back to string information, use map:

1
2
3
4
5
6
7
8
// The `HttpClient.request` API produces a raw event stream
// which includes start (sent), progress, and response events.
return this.http.request(req).pipe(
map(event => this.getEventMessage(event, file)), // passes on string information to tap()
tap(message => this.showProgress(message)), // this basically presents information to the user
last(), // return last (completed) message to caller
catchError(this.handleError(file))
);

tap gets the messages that gets returned from map, which are strings indicating the request status:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/** Return distinct message for sent, upload progress, & response events */
// notice that strings are returned, which then goes through the tap() operator
private getEventMessage(event: HttpEvent<any>, file: File) {
switch (event.type) {
case HttpEventType.Sent:
return `Uploading file "${file.name}" of size ${file.size}.`;

case HttpEventType.UploadProgress:
// Compute and show the % done:
const percentDone = Math.round(100 * event.loaded / event.total);
return `File "${file.name}" is ${percentDone}% uploaded.`;

case HttpEventType.Response:
return `File "${file.name}" was completely uploaded!`;

default:
return `File "${file.name}" surprising upload event: ${event.type}.`;
}
}

Techniques

Animations

Internationalization