Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Accelerating Angular Development with Ivy

You're reading from   Accelerating Angular Development with Ivy A practical guide to building faster and more testable Angular apps with the new Ivy engine

Arrow left icon
Product type Paperback
Published in Oct 2021
Publisher Packt
ISBN-13 9781800205215
Length 242 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Authors (3):
Arrow left icon
Lars Gyrup Brink Nielsen Lars Gyrup Brink Nielsen
Author Profile Icon Lars Gyrup Brink Nielsen
Lars Gyrup Brink Nielsen
Jacob Andresen Jacob Andresen
Author Profile Icon Jacob Andresen
Jacob Andresen
Mateus Carniatto Mateus Carniatto
Author Profile Icon Mateus Carniatto
Mateus Carniatto
Arrow right icon
View More author details
Toc

Table of Contents (14) Chapters Close

Preface 1. Chapter 1: Discovering New APIs and Language Syntax 2. Chapter 2: Boosting Developer Productivity Through Tooling, Configuration, and Convenience FREE CHAPTER 3. Chapter 3: Introducing CSS Custom Properties and New Provider Scopes 4. Chapter 4: Exploring Angular Components Features 5. Chapter 5: Using CSS Custom Properties 6. Chapter 6: Using Angular Components 7. Chapter 7: Component Harnesses 8. Chapter 8: Additional Provider Scopes 9. Chapter 9: Debugging with the New Ivy Runtime APIs 10. Chapter 10: Using the Angular Compatibility Compiler 11. Chapter 11: Migrating Your Angular Application from View Engine to Ivy 12. Chapter 12: Embracing Ahead-of-Time Compilation 13. Other Books You May Enjoy

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.

You have been reading a chapter from
Accelerating Angular Development with Ivy
Published in: Oct 2021
Publisher: Packt
ISBN-13: 9781800205215
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime