Book Image

Accelerating Angular Development with Ivy

By : Lars Gyrup Brink Nielsen, Mateus Carniatto, Jacob Andresen
Book Image

Accelerating Angular Development with Ivy

By: Lars Gyrup Brink Nielsen, Mateus Carniatto, Jacob Andresen

Overview of this book

Angular Ivy is the latest rendering engine and compiler introduced in Angular. Ivy helps frontend developers to make their Angular applications faster, better optimized, and more robust. This easy-to-follow guide will help you get to grips with the new features of Angular Ivy and show you how to migrate your Angular apps from View Engine to Ivy. You'll begin by learning about the most popular features of Angular Ivy with the help of simple stand-alone examples and realize its capabilities by working on a real-world application project. You'll then discover strategies to improve your developer workflow through new debugging APIs, testing APIs, and configurations that support higher code quality and productive development features. Throughout the book, you'll explore essential components of Angular, such as Angular Component Dev Kit (CDK), Ahead-of-time (AOT) compilation, and Angular command line interface (CLI). Finally, you'll gain a clear understanding of these components along with Angular Ivy which will help you update your Angular applications with modern features. By the end of this Angular Ivy book, you will learn about the core features of Angular Ivy, discover how to migrate your Angular View Engine application, and find out how to set up a high-quality Angular Ivy project.
Table of Contents (14 chapters)

Using predictable style bindings

Angular has many ways to bind styles and classes to Document Object Model (DOM) elements. Ivy introduces predictable style bindings because of a precedence ruleset that covers all of Angular's style binding APIs except for the NgClass and NgStyle directives.

Template element bindings have higher priority than directive host bindings, which have higher priority than component host bindings. Binding of individual Cascading Style Sheets (CSS) classes and style properties have higher priority than binding maps of class names and style properties. Binding values that define the full class or style attributes have even lower priority. The NgClass and NgStyle directives override all other bindings on every value change.

Bottom values in style bindings are treated differently. Binding undefined will defer to lower-priority bindings, while null will override bindings with lower priority.

Let's look at the following example:

@Component({
  selector: 'app-root',
  template: `
    <app-host-binding
      [ngStyle]="{ background: 'pink' }"
      [style.background]="'red'"
      [style]="{ background: 'orange' }"
      style="background: yellow;"
      appHostBinding
    ></app-host-binding>
  `,
})
class AppComponent {}
@Directive({
  host: {
    '[style.background]': "'blue'",
    style: 'background: purple;',
  },
  selector: '[appHostBinding]',
})
class HostBindingDirective {}
@Component({
  host: {
    '[style.background]': "'gray'",
    style: 'background: green;',
  },
  selector: 'app-host-binding',
})
class HostBindingComponent {}

In the preceding code example, we see components and a directive using many different types of style bindings. Despite this, it will output only a single style rule to the DOM for the <app-host-binding> element. The background color of this rule will be evaluated as pink.

The order in which the background colors are applied is shown here, with the highest precedence first:

  1. Pink (NgStyle directive binding)
  2. Red (template property binding)
  3. Orange (template map binding)
  4. Yellow (static style value)
  5. Blue (directive host property binding)
  6. Purple (static directive host style binding)
  7. Gray (component host property binding)
  8. Green (static component host style binding)

As seen in the example, the order in which the bindings are mentioned in templates and metadata options does not matter—the precedence ruleset is always the same.

Having predictable style bindings makes it easier to implement complex use cases in our applications. It is worth mentioning that another reason for introducing this breaking change is that Ivy does not guarantee the order in which data bindings and directives are applied.

In this section, we witnessed the following styling precedence rules in effect, from highest priority to lowest:

  1. Template property bindings
  2. Template map bindings
  3. Static template class and style values
  4. Directive host property bindings
  5. Directive host map bindings
  6. Static directive host class and style bindings
  7. Component host property bindings
  8. Component host map bindings
  9. Static component host class and style bindings

The order in which style bindings are listed in code only matters if two bindings share the same precedence, in which case the last one wins.

NgClass and NgStyle directive bindings override all other style bindings. They are the !important equivalents of Angular style bindings.

Now that we can predict how multiple style bindings and values affect our user interface (UI), let's look at how we can use class inheritance to share directive and component metadata.

Sharing metadata through directive and component inheritance

Angular Ivy changes directive and component inheritance in a more explicit but predictable manner, which allows the bundle size and compilation speed to decrease.

When a base class is using any of the following Angular-specific features, it has to have a Directive or Component decorator applied:

  • Dependency or Attribute injection
  • Input or Output properties
  • HostBinding or HostListener bindings
  • ViewChild, ViewChildren, ContentChild, or ContentChildren queries

To support this, we can add a Directive decorator without any options. This conceptually works like an abstract directive and will throw a compile-time error if declared in an Angular module.

We could make the base class abstract, but that would cause us to have to extend it to test it, so it is a trade-off.

By extending base directives, we can inherit the inputs, outputs, host, and queries metadata options. Some of them will even be merged if declared both in the subclass and base class.

Components are able to inherit the same metadata options from their base class but are unable to inherit styles and template metadata. It is possible to refer to the same styleUrls and templateUrl though.

Let's write some example components that share behavior through a base class. First, we will create a base search component, as seen in the following code snippet:

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
@Component({
  selector: 'app-base-search',
  template: '',
})
export class BaseSearchComponent {
  #search = new EventEmitter<string>();
  @Input()
  placeholder = 'Search...';
  @Output()
  search = this.#search.pipe(debounceTime(150), 
   distinctUntilChanged());
  onSearch(inputEvent: Event): void {
    const query = (inputEvent.target as 
     HTMLInputElement)?.value;
    if (query == null) {
      return;
    }
    this.#search.next(query);
  }
}

The base search component has an event handler that handles input events representing a search query. It debounces searches for 150 milliseconds (ms) and ignores duplicate search queries before outputting them through its search output property. Additionally, it has a placeholder input property.

Next, we will create a simple search box component that inherits from the base search component, as seen in the following code snippet:

import { Component } from '@angular/core';
import { BaseSearchComponent } from './base-search.component';
@Component({
  selector: 'app-search-box',
  styleUrls: ['./base-search.scss'],
  template: `
    <input
      type="search"
      [placeholder]="placeholder"
      (input)="onSearch($event)"
    />
  `,
})
export class SearchBoxComponent extends BaseSearchComponent {}

The search box component uses base search styles and can add its own component-specific styles if it needs to. The <input> element in its component template binds to the placeholder input property it inherits from the base search component. Likewise, the input event is bound to the onSearch event handler it inherits.

Let's create another component that inherits from the base search component. The following code block lists the suggested search component:

import { Component, Input } from '@angular/core';
import { BaseSearchComponent } from './base-search.component';
@Component({
  selector: 'app-suggested-search',
  styleUrls: ['./base-search.scss'],
  template: `
    <input
      list="search-suggestions"
      [placeholder]="placeholder"
      (input)="onSearch($event)"
    />
    <datalist id="search-suggestions">
      <option *ngFor="let suggestion of suggestions" 
       [value]="suggestion">
        {{ suggestion }}
      </option>
    </datalist>
  `,
})
export class SuggestedSearchComponent extends BaseSearchComponent {
  @Input()
  suggestions: readonly string[] = [];
}

In addition to the inherited input property, placeholder, the suggested search component adds a suggestion input property, which is a list of search query suggestions. The component template loops over these suggestions and lists them as <option> elements in a <datalist> element that is tied to the <input> element.

Similar to the search box component, the suggested search component binds to the onSearch event handler and the placeholder input property. It also uses the base search styles.

Important Note

As seen in these examples, we do not have to add duplicate constructors in subclasses to enable constructor injection.

Directive and component metadata is the special glue that ties TypeScript classes to the DOM through component templates and data binding. Through classical object-oriented programming (OOP) patterns, we are familiar with sharing properties and methods through class inheritance.

In this section, we learned how Ivy has enabled us to share metadata in a similar, predictable way through metadata-enabled class inheritance.

The next topic we are going to explore is AOT compilation. Ivy heralds the era of AOT compilation everywhere in Angular applications.