Revisiting Angular

Angular is a platform and framework for building client applications in HTML and TypeScript. The basic building blocks of an Angular application are NgModules, which provide a compilation context for components. NgModules collect related code into functional sets; an Angular app is defined by a set of NgModules. An app always has at least a root module that enables bootstrapping, and typically has many more feature modules.

  • 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.

Both components and services are simply classes, with decorators that mark their type and provide metadata that tells Angular how to use them.

  • The metadata for a component class associates it with a template that defines a view. A template combines ordinary HTML with Angular directives and binding markup that allow Angular to modify the HTML before rendering it for display.
  • The metadata for a service class provides the information Angular needs to make it available to components through dependency injection (DI).

The following are the major parts that make up an Angular application.

 

Modules

Angular NgModules differ from and complement JavaScript (ES2015) modules. 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.

While a small application might have only one NgModule, most apps have many more feature modules. The root NgModule for an app is so named because it can include child NgModules in a hierarchy of any depth.

NgModules provide a compilation context for their components. 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.

The most important properties are as follows.

  • declarations: The componentsdirectives, 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.
  • imports: Other modules whose exported classes are needed by component templates declared in this NgModule.
  • providers: Creators of services 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 should set the bootstrap property.
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

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.

For example, import Angular’s Component decorator from the @angular/core library like this.

import { Component } from '@angular/core';

 

Components

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.

Decorators are functions that modify JavaScript classes. Angular defines a number of decorators that attach specific kinds of metadata to classes, so that the system knows what those classes mean and how they should work.

A component and its template together define a view. A component can contain a view hierarchy, which allows you to define arbitrarily complex areas of the screen that can be created, modified, and destroyed as a unit. A view hierarchy can mix views defined in components that belong to different NgModules. This is often the case, especially for UI libraries.

The @Component decorator identifies the class immediately below it as a component class, and specifies its metadata.

@Component({
  selector:    'app-hero-list',
  templateUrl: './hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

 

Template, Directives and Data Binding

A template combines HTML with Angular markup that can modify HTML elements before they are displayed. Template directives provide program logic, and binding markup connects your application data and the DOM. There are two types of data binding:

  • Event binding lets your app respond to user input in the target environment by updating your application data.
  • Property binding lets you interpolate values that are computed from your application data into the HTML.

Before a view is displayed, Angular evaluates the directives and resolves the binding syntax in the template to modify the HTML elements and the DOM, according to your program data and logic. Angular supports two-way data binding, meaning that changes in the DOM, such as user choices, are also reflected in your program data.

You define a component’s view with its companion template. A template is a form of HTML that tells Angular how to render the component.

Views are typically arranged hierarchically, allowing you to modify or show and hide entire UI sections or pages as a unit. The template immediately associated with a component defines that component’s host view. The component can also define a view hierarchy, which contains embedded views, hosted by other components.

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. Your template can use data binding to coordinate the app and DOM data, pipes to transform data before it is displayed, and directives to apply app logic to what gets displayed.

Angular supports 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. Two-way data binding (used mainly in template-driven forms) combines property and event binding in a single notation.

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

Data binding plays an important role in communication between a template and its component, and is also important for communication between parent and child components.

 

Services

For data or logic that isn’t associated with a specific view, and that you want to share across components, you create a service class. 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.

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 lean and efficient.

Dependency injection (DI) lets you keep your component classes lean and efficient. They don’t fetch data from the server, validate user input, or log directly to the console; they delegate such tasks to services.

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.

  • 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.
  • provider is an object that tells an injector how to obtain or create a dependency.

For any dependency that you need in your app, you must register a provider with the app’s injector, so that the injector can use the provider to create new instances. For a service, the provider is typically the service class itself.

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.

 

Routing and Navigation

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. It is modeled on the familiar browser navigation conventions:

  • Enter a URL in the address bar and the browser navigates to a corresponding page.
  • Click links on the page and the browser navigates to a new page.
  • Click the browser’s back and forward buttons and the browser navigates backward and forward through the history of pages you’ve seen.

The router maps URL-like paths to views instead of pages. When a user performs an action, such as clicking a link, that would load a new page in the browser, the router intercepts the browser’s behavior, and shows or hides view hierarchies.

If the router determines that the current application state requires particular functionality, and the module that defines it hasn’t been loaded, the router can lazy-load the module on demand.

 

Life-Cycle Hooks

A component has a lifecycle managed by Angular.

Angular creates and renders components along with their children, checks when their data-bound properties change, and destroys them before removing them from the DOM.

Angular offers lifecycle hooks that provide visibility into these key life moments and the ability to act when they occur.  For example, the OnInit interface has a hook method named ngOnInit() that Angular calls shortly after creating the component:

export class PeekABoo implements OnInit { 
  constructor(private logger: LoggerService) { } 
  ngOnInit() { 
    this.logIt(`OnInit`); 
  }
}

After creating a component/directive by calling its constructor, Angular calls the lifecycle hook methods in the following sequence at specific moments:

Hook Purpose and Timing
ngOnChanges() Respond when Angular (re)sets data-bound input properties. The method receives a SimpleChanges object of current and previous property values.

Called before ngOnInit() and whenever one or more data-bound input properties change.

ngOnInit() Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component’s input properties.

Called once, after the first ngOnChanges().

ngDoCheck() Detect and act upon changes that Angular can’t or won’t detect on its own.

Called during every change detection run, immediately after ngOnChanges() and ngOnInit().

ngAfterContentInit() Respond after Angular projects external content into the component’s view / the view that a directive is in.

Called once after the first ngDoCheck().

ngAfterContentChecked() Respond after Angular checks the content projected into the directive/component.

Called after the ngAfterContentInit() and every subsequent ngDoCheck().

ngAfterViewInit() Respond after Angular initializes the component’s views and child views / the view that a directive is in.

Called once after the first ngAfterContentChecked().

ngAfterViewChecked() Respond after Angular checks the component’s views and child views / the view that a directive is in.

Called after the ngAfterViewInit() and every subsequent ngAfterContentChecked().

ngOnDestroy() Cleanup just before Angular destroys the directive/component. Unsubscribe Observables and detach event handlers to avoid memory leaks.

Called just before Angular destroys the directive/component.

 

Observables and RxJS

Observables provide support for passing messages between publishers and subscribers in your application. Observables offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values. Observables are declarative—that is, you define a function for publishing values, but it is not executed until a consumer subscribes to it. The subscribed consumer then receives notifications until the function completes, or until they unsubscribe.

An observable can deliver multiple values of any type—literals, messages, or events, depending on the context. The API for receiving values is the same whether the values are delivered synchronously or asynchronously.

As a publisher, you create an Observable instance that defines a subscriber function. This is the function that is executed when a consumer calls the subscribe() method. The subscriber function defines how to obtain or generate values or messages to be published. A handler for receiving observable notifications implements the Observer interface. It is an object that defines callback methods to handle the three types of notifications that an observable can send:

NOTIFICATION TYPE DESCRIPTION
next Required. A handler for each delivered value. Called zero or more times after execution starts.
error Optional. A handler for an error notification. An error halts execution of the observable instance.
complete Optional. A handler for the execution-complete notification. Delayed values can continue to be delivered to the next handler after execution is complete.

An Observable instance begins publishing values only when someone subscribes to it. You subscribe by calling the subscribe() method of the instance, passing an observer object to receive the notifications.

const locations = new Observable((observer) => {
  const {next, error} = observer;
  let watchId;

  if ('geolocation' in navigator) {
    watchId = navigator.geolocation.watchPosition(next, error);
  } else {
    error('Geolocation not available');
  }

  return {unsubscribe() { navigator.geolocation.clearWatch(watchId); }};
});

const locationsSubscription = locations.subscribe({
  next(position) { console.log('Current Position: ', position); },
  error(msg) { console.log('Error Getting Location: ', msg); }
});

setTimeout(() => { locationsSubscription.unsubscribe(); }, 10000);

 

Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change (Wikipedia). RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code. See (RxJS Docs).

RxJS provides an implementation of the Observable type, which is needed until the type becomes part of the language and until browsers support it. The library also provides utility functions for creating and working with observables. These utility functions can be used for:

  • Converting existing code for async operations into observables
  • Iterating through the values in a stream
  • Mapping values to different types
  • Filtering streams
  • Composing multiple streams

 

HttpClient

Most front-end applications communicate with backend services over the HTTP protocol. Modern browsers support two different APIs for making HTTP requests: the XMLHttpRequest interface and the fetch() API.

The HttpClient in @angular/common/http offers a simplified client HTTP API for Angular applications that rests on the XMLHttpRequest interface exposed by browsers. Additional benefits of HttpClient include testability features, typed request and response objects, request and response interception, Observable apis, and streamlined error handling.

Angular’s HttpClient returns observables from HTTP method calls. For instance, http.get(‘/api’) returns an observable. This provides several advantages over promise-based HTTP APIs:

  • Observables do not mutate the server response (as can occur through chained .then() calls on promises). Instead, you can use a series of operators to transform values as needed.
  • HTTP requests are cancellable through the unsubscribe() method.
  • Requests can be configured to get progress event updates.
  • Failed requests can be retried easily.

 


Build and Deployment

You can define different named build configurations for your project, such as stage and production, with different defaults.

Each named configuration can have defaults for any of the options that apply to the various builder targets, such as buildserve, and test. The Angular CLI buildserve, and test commands can then replace files with appropriate versions for your intended target environment. These configurations can be found in the project’s src/environments folder.

└──myProject/src/environments/
  └──environment.ts
  └──environment.prod.ts
  └──environment.stage.ts

This means that when you build your production configuration (using ng build --prod or ng build --configuration=production), the src/environments/environment.ts file is replaced with the target-specific version of the file, src/environments/environment.prod.ts.

ng build --prod

The above command will copy everything within the output folder (dist/ by default) to a folder on the server.

During development, you typically use the ng serve command to build, watch, and serve the application from local memory, using webpack-dev-server. When you are ready to deploy, however, you must use the ng build command to build the app and deploy the build artifacts elsewhere.

Both ng build and ng serve clear the output folder before they build the project, but only the ng build command writes the generated build artifacts to the output folder.

The --prod meta-flag engages the following build optimization features.

  • Ahead-of-Time (AOT) Compilation: pre-compiles Angular component templates.
  • Production mode: deploys the production environment which enables production mode.
  • Bundling: concatenates your many application and library files into a few bundles.
  • Minification: removes excess whitespace, comments, and optional tokens.
  • Uglification: rewrites code to use short, cryptic variable and function names.
  • Dead code elimination: removes unreferenced modules and much unused code.

See ng build for more about CLI build options and what they do.

 

Ahead-of-Time (AOT) Compiler

An Angular application consists mainly of components and their HTML templates. Because the components and templates provided by Angular cannot be understood by the browser directly, Angular applications require a compilation process before they can run in a browser.

Angular offers two ways to compile your application:

  • Just-in-Time (JIT), which compiles your app in the browser at runtime.
  • Ahead-of-Time (AOT), which compiles your app at build time.
// JIT compilation is the default when you run the ng build (build only) or ng serve (build and serve locally) CLI commands:
ng build
ng serve

// For AOT compilation, include the --aot option with the ng build or ng serve command:
ng build --aot
ng serve --aot

 

The Angular Ahead-of-Time (AOT) compiler converts your Angular HTML and TypeScript code into efficient JavaScript code during the build phase before the browser downloads and runs that code. Compiling your application during the build process provides a faster rendering in the browser.

Here are some reasons you might want to use AOT.

  • Faster rendering With AOT, the browser downloads a pre-compiled version of the application. The browser loads executable code so it can render the application immediately, without waiting to compile the app first.
  • Fewer asynchronous requests The compiler inlines external HTML templates and CSS style sheets within the application JavaScript, eliminating separate ajax requests for those source files.
  • Smaller Angular framework download size There’s no need to download the Angular compiler if the app is already compiled. The compiler is roughly half of Angular itself, so omitting it dramatically reduces the application payload.
  • Detect template errors earlier The AOT compiler detects and reports template binding errors during the build step before users can see them.
  • Better security AOT compiles HTML templates and components into JavaScript files long before they are served to the client. With no templates to read and no risky client-side HTML or JavaScript evaluation, there are fewer opportunities for injection attacks.

The Angular AOT compiler extracts metadata to interpret the parts of the application that Angular is supposed to manage. You can specify the metadata explicitly in decorators such as @Component() and @Input(), or implicitly in the constructor declarations of the decorated classes. The metadata tells Angular how to construct instances of your application classes and interact with them at runtime.

There are three phases of AOT compilation.

  • Phase 1 is code analysis. In this phase, the TypeScript compiler and AOT collector create a representation of the source. The collector does not attempt to interpret the metadata it collects. It represents the metadata as best it can and records errors when it detects a metadata syntax violation.
  • Phase 2 is code generation. In this phase, the compiler’s StaticReflector interprets the metadata collected in phase 1, performs additional validation of the metadata, and throws an error if it detects a metadata restriction violation.
  • Phase 3 is template type checking. In this optional phase, the Angular template compiler uses the TypeScript compiler to validate the binding expressions in templates. You can enable this phase explicitly by setting the fullTemplateTypeCheck configuration option; see Angular compiler options.

 


Sample App

Following sample app was used by another post for demonstrating auto balancing and scaling with AWS ECS. This angular frontend calls and an endpoint hosted on ECS.

The app has a form where the user sets how many times the endpoint is called and at what frequency. As angular calls the endpoint, the results are shown on the page at real time. Some of the key classes are:

primes.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

import { Primes } from './primes';
import { PrimesService } from './primes.service';

@Component({
  selector: 'app-primes',
  templateUrl: './primes.component.html',
  styleUrls: ['./primes.component.css'],
  providers: [PrimesService]
})
export class PrimesComponent implements OnInit {
  primes: Primes[];
  url: string;
  numFind: number;
  numCalls: number;
  submitted: boolean;

  callsAttempted: number;
  callsCompleted: number;

  constructor(private service: PrimesService) { }

  ngOnInit() {
    this.onReset();
  }

  onSubmit(): void {
    this.submitted = true;
    console.log('Calling...');
    
    for (var i = 0; i < this.numCalls; i++) { this.callsAttempted++; this.service.getPrimes(this.numFind, this.url).subscribe(result => {
        console.log('Result = ' + JSON.stringify(result));
        var p = result as Primes;
        if (p) {
          this.primes.push(p);
          this.callsCompleted++;
        }
      });
    }
  }

 

primes.service.ts
@Injectable()
export class PrimesService {
  private handleError: HandleError;

  constructor(
    private http: HttpClient,
    httpErrorHandler: HttpErrorHandler) {
    this.handleError = httpErrorHandler.createHandleError('PrimesService');
  }

  getPrimes (n: number, baseUrl: string): Observable {
    const url = `${baseUrl}/primes/${n}`;
    return this.http.get(`${url}`)
      .pipe(
        catchError(this.handleError('getPrimes'))
      );
  }
}

Full source code here:
https://github.com/solidfish/automusprimes

 

The app will be deployed on AWS S3. We first need to build it as follows:

ng build --prod
                                          
Hash: 7c8ba116807e9efe3e27
Time: 24431ms
chunk {0} runtime.26209474bfa8dc87a77c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.b0657154bc33c6ff11ae.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.d2c970d920738533166a.js (main) 340 kB [initial] [rendered]
chunk {3} polyfills.8bbb231b43165d65d357.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.622bda3c9e70a9424e77.css (styles) 960 bytes [initial] [rendered]

 

Once the compile is completed we can find the deployment files in the “/dist” folder. These contents will be copied into an AWS S3 bucket.

 

References

Angular Official Guide
https://angular.io/guide