Accelerating Angular Development with Ivy

By Lars Gyrup Brink Nielsen , Jacob Andresen
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Free Chapter
    Chapter 2: Boosting Developer Productivity Through Tooling, Configuration, and Convenience
About 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.

Publication date:
October 2021
Publisher
Packt
Pages
242
ISBN
9781800205215

 

Chapter 2: Boosting Developer Productivity Through Tooling, Configuration, and Convenience

Across data binding and attribute directives, there are many options to dynamically change styles in an Angular application. Angular Ivy supports multiple styling application programming interfaces (APIs) in a predictable way. This chapter teaches us about Angular Ivy's style binding precedence rules through a simple example that uses almost every possible style API in Angular.

Through a few examples, we will explore how directive and component inheritance and the sharing of metadata have changed in Angular Ivy. Before that, we learn which metadata properties are sharable through inheritance.

Ahead-of-time (AOT) compilation was introduced in one of the first major releases of Angular. Angular Ivy is the first generation to enable it in all phases of the application life cycle—from development through testing to builds. We will discuss how this affects our workflow and bundle size after briefly peeking under the hood of Angular to discuss some of the internal building blocks that have enabled the performance improvements needed to make this change.

The strict mode preset available with Angular Ivy enables additional compile-time checks of our Angular applications as well as bundle optimizations.

Angular Ivy and its supported versions of TypeScript have added significant speed improvements and stricter checks to tests. We will learn about this through examples that are easy to understand.

Angular's compiler analyzes our application code at build time. This is nice, but some of the compiler error messages suffer from missing context. In Angular Ivy, contextual details have been added to several compilation error messages. In this chapter, you will see Ivy compilation error examples compared to compilation errors from earlier generations of Angular as we discuss how it improves the developer experience.

Angular is known for great tooling and support for strong typing through TypeScript. However, we have not been able to type-check all parts of our component templates in the first or second generation of the Angular framework. The Ivy generation completes the component template type-checking story for the Angular framework.

Automated migration schematics are a wonderful piece of tooling that is part of the Angular framework. Since Ivy was released, they include messages with descriptions and an option to run them in separate commits, as we will see in this chapter.

The versions of the Angular Language Service introduced with Angular Ivy have allowed for better integration with integrated development environments (IDEs) such as Visual Studio Code (VS Code). Additional tooltips are added to component templates and additional details have been added to existing tooltips. Syntax highlighting has been added to inline templates and improved for external file templates.

In this second chapter, we are going to cover these topics:

  • Using predictable style bindings
  • Sharing metadata through directive and component inheritance
  • Outputting faster and smaller bundles with AOT compilation
  • Taking advantage of strict mode and other new configurations
  • Enhancing our Angular testing experience
  • Leveling up our developer experience

After finishing this chapter, you can boost your developer productivity by taking advantage of the latest improvements in tooling, configuration, and other Angular features.

 

Technical requirements

To support all features used in the code examples of this chapter, your application requires at least the following:

  • Angular Ivy version 12.0
  • TypeScript version 4.2

You can find complete code examples for component inheritance and style bindings in this book's companion GitHub repository at https://github.com/PacktPublishing/Accelerating-Angular-Development-with-Ivy/tree/main/projects/chapter2.

 

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.

 

Outputting faster and smaller bundles with AOT compilation

Angular Ivy is first and foremost an internal rewrite of the Angular compiler, the Angular runtime, and a few more pieces of the framework. A lot of effort was put into maintaining backward compatibility with application code written for the View Engine generation of the Angular framework (versions 4 through 8).

At the heart of this new generation is the Ivy Instruction Set, which is a runtime DOM instruction set similar to the Incremental DOM library by Google. In a nutshell, an Angular component is compiled into two lists of instructions that, when executed by Ivy, will initialize the DOM with the first list of instructions and update the DOM with the second list of instructions whenever changes are detected.

The Ivy Instruction Set is lighter than the View Engine equivalent, which the runtime had to translate before applying it. In View Engine, it used to be the case that a large Angular application would eventually have a smaller just-in-time (JIT)-compiled bundle than the AOT-compiled bundle for the same application. With Ivy, this is no longer the case.

For small Angular applications, Ivy adds the benefit that the Ivy Instruction Set and the individual parts of the runtime are tree-shakable. This means that we are not paying for the parts of the framework runtime that we are not using, in that they are removed from the output bundle—for example, Angular's animation and globalization APIs are not part of our application bundle unless we are using them. This is very important in microfrontends and web components, where we need to minimize the overhead of the Angular framework.

Angular version 11 is the final major version supporting View Engine. The Ivy Instruction Set is kept as a code-generation API, meaning that our code should not rely on it. Angular libraries supporting Angular versions 9-11 are not recommended to ship Ivy-compiled code. Instead, the Angular Compatibility Compiler is used to transpile View Engine-compiled libraries to Ivy. Unfortunately, this means that our initial build time increases as the Angular Compatibility Compiler must compile each Angular library entry point in our node_modules folder. This is especially hard to manage in managed build pipelines.

Read about the Angular Compatibility Compiler in Chapter 10, Using the Angular Compatibility Compiler.

As of Angular version 12, it is recommended for Angular libraries to ship partially Ivy-compiled bundles using the Ivy compiler. This means that our application can more fully enjoy incremental builds that speed up the overall build time. This is because of an Ivy concept known as the principle of locality. The Ivy compiler only needs to know about the public API of, for example, child components rather than having to know about their injected dependencies and mention them in the compiled output of the parent component.

In general, Ivy results in decreased build time, except for the time it takes to run the Angular Compatibility Compiler on Angular libraries, including the Angular framework's packages.

As we discussed, using the Ivy compiler also results in decreased bundle sizes overall. However, if we are using most parts of the framework runtime, our main chunk's bundle size might increase while the size of lazy-loaded chunks decreases.

With the introduction of Angular Ivy, we can now use AOT compilation during all phases of development. This removes subtle but important differences between development, testing, the build process, and production runtime.

The short explanation in this section is just enough to give us an overall idea about the effect that AOT compilation has on Angular applications. However, this book is a practical approach to developing Angular Ivy applications. In everyday development, we do not have to be familiar with the internals of the Angular compiler, rendering engine, and runtime.

Read more about the impact and limitations of Angular's AOT compiler in Chapter 12, Embracing Ahead-of-Time Compilation.

In the following section, we look at the compilation presets and configurations introduced by Ivy and its accompanying TypeScript versions.

 

Taking advantage of strict mode and other new configurations

Angular has always been big on tooling. Ivy adds and enables additional configurations that help us catch errors early and output smaller bundles.

Strict mode

When we create an Angular workspace using the ng new command, the --strict parameter flag is on by default as of Angular version 12. Using the strict workspace preset enables additional static analysis. The --strict parameter flag is also supported for project generation schematics such as ng generate application and ng generate library.

The strict preset sets the following TypeScript compiler options to true:

  • forceConsistentCasingInFileNames
  • noImplicitReturns
  • noFallthroughCasesInSwitch
  • strict

These options help us catch a lot of potential errors at compile time. Refer to the TypeScript documentation for details about the individual compiler options. The strict TypeScript compiler option is a shorthand for enabling all the following compiler options as of TypeScript version 4.2:

  • alwaysStrict
  • noImplicitAny
  • noImplicitThis
  • strictBindCallApply
  • strictFunctionTypes
  • strictNullChecks
  • strictPropertyInitialization

Additionally, future strict TypeScript compiler options will be enabled automatically when using the strict shorthand.

Bundle budgets are reduced in strict mode. The initial bundle warns at 500 kilobytes (KB) and errs at 1 megabyte (MB), while component styles warn at 2 KB and err at 4 KB.

Strict mode further enables strict template type checking, which we will cover later in this chapter.

Angular compiler options

Only a few Angular compiler option defaults have changed in Ivy. In Angular version 9, the default value of the enableIvy option was set to true for applications but false for libraries. This changed in Angular version 12, where the enableIvy option was removed entirely when View Engine support was disabled for Angular applications and the "partial" value for the compilationMode option was added for partial Ivy compilation of Angular libraries.

The strictTemplates option is introduced with Ivy. Its default value is false, but it is set to true when generating an Angular workspace by using the ng new command, which enables the --strict parameter flag. The same applies to the fullTemplateTypeCheck option, which is implicitly set by the strictTemplates option.

The default value of the strictInjectionParameters and strictInputAccessModifiers options is still false, but it is set to true when generating an Angular workspace by using the ng new command.

Now that you are aware of the many helpful configuration options introduced by Angular Ivy, you can gain more confidence about how your Angular application will behave in production runtime.

Next up is a favorite topic of ours: testing. Both Ivy and its related TypeScript versions introduce substantial improvements to the Angular testing experience.

 

Enhancing our Angular testing experience

Ivy is a major milestone for Angular tests. Besides the stronger typing in tests discussed in Chapter 1, Discovering New APIs and Language Syntax, it adds major speed improvements and useful test utilities, one of which is component testing harnesses, which we will cover in Chapter 4, Exploring Angular Components Features.

In this section, we explore how we can introduce values of unexpected types using a TypeScript annotation, which proves to be useful in tests. After that, we discuss another important aspect of AOT compilation in Angular Ivy.

Expect error compiler annotation

TypeScript version 3.9 introduces a special compiler instruction comment that is useful in tests.

The @ts-expect-error annotation comment allows values of incompatible types to be passed to functions in the following statement. As an example, let's write an add function and verify that it rejects strings—even at runtime—for robustness:

function add(left: number, right: number): number {
  assertIsNumber(left);
  assertIsNumber(right);
  return left + right;
}

The robust add function in the previous code snippet applies an assertion function for both operands. Let's test that an error is thrown if strings are passed, as follows:

describe('add', () => {
  it('rejects a string as left operand', () => {
    const textInput = '2';
    const four = 4;
    // @ts-expect-error
    expect(() => add(textInput, four)).toThrow();
  });
  it('rejects a string as right operand', () => {
    const three = 3;
    const textInput = '5';
    // @ts-expect-error
    expect(() => add(three, textInput)).toThrow();
  });
});

If we remove the @ts-expect-error comments, the TypeScript compiler throws errors because of the incompatible values we pass in the tests in the previous code block.

How is this different from @ts-ignore comments? The @ts-expect-error comments warn us if a compilation error is not thrown in the statement that follows. This raises our confidence in the code.

Faster tests with AOT compilation

Angular Ivy introduces AOT compilation to tests. This makes the test environment close to the production environment, which is a good trait as it allows us to catch errors early.

Until Ivy, Angular had a long-standing issue of relatively slow tests when they involved component tests using TestBed. The tests were slow because the test runner was reading, parsing, and compiling one or more files for every component per test case, not per test suite or per test run. Ivy introduces the principle of locality as well as a cache for compiled declarables and Angular modules, which speeds up component tests significantly. Additionally, rebuilds are faster, which improves speed when writing tests and fixing bugs.

With these pieces of valuable information, you now know how Ivy can greatly impact your developer workflow when implementing unit tests. As mentioned in the introduction of this section, we have saved one of the most exciting features for Chapter 4, Exploring Angular Components Features—namely, component testing harnesses.

The next section is all about how Ivy boosts our productivity by improving the Angular developer experience.

 

Leveling up our developer experience

Ivy improves the developer experience in many ways. In this section, we learn about the most noteworthy of them.

Improved compilation errors

Compile-time errors are a positive side effect of Angular using an AOT compiler. However, some of the build errors have needed additional context to aid in pinpointing the source of the error.

As an example, look at the error message in the following listing that is output when using an unknown element in an Angular application using View Engine:

ERROR in 'app-header' is not a known element:
1. If 'app-header' is an Angular component, then verify that it is part of this module.
2. If 'app-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message. ("[ERROR ->]<app-header></app-header>
<p>My content</p>
")

Which component has the error? How do we fix it?

The next listing shows the error message that is output for the same mistake in an Angular application using Ivy:

ERROR in src/app/app.component.html:1:1 - error NG8001: 'app-header' is not a known element:
1. If 'app-header' is an Angular component, then verify that it is part of this module.
2. If 'app-header' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the '@NgModule.schemas' of this component to suppress this message.
1 <app-header></app-header>
  ~~~~~~~~~~~~~~~~~~~~~~~~~
  src/app/app.component.ts:5:16
    5   templateUrl: './app.component.html',
                     ~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component AppComponent.

The file path of the component model and the component template files are both listed, as well as the line number and the content of those lines. It is now much clearer where the problem was encountered—a big win for developer productivity.

Let's look at another example. If we accidentally add a duplicate comma to the imports array of an Angular module, as seen in the following code block, a build error will be output:

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, , CommonModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

View Engine outputs an error message similar to the one shown in the following listing:

ERROR in src/app/app.module.ts(11,27): Error during template compile of 'AppModule'
  Expression form not supported.
src/app/app.module.ts(11,27): Error during template compile of 'AppModule'
  Expression form not supported.
Cannot determine the module for class AppComponent in C:/projects/sandbox/angular-cli8-app/src/app/app.component.ts! Add AppComponent to the NgModule to fix it.

We get the relevant file and line number, but the error description, Expression form not supported, is not very helpful. Here is the equivalent error message output by Ivy:

ERROR in projects/second-app/src/app/app.module.ts:11:3 - error TS2322: Type '(typeof CommonModule | typeof BrowserModule | undefined)[]' is not assignable to type '(any[] | Type<any> | ModuleWithProviders<{}>)[]'.
  Type 'typeof CommonModule | typeof BrowserModule | undefined' is not assignable to type 'any[] | Type<any> | ModuleWithProviders<{}>'.
    Type 'undefined' is not assignable to type 'any[] | Type<any> | ModuleWithProviders<{}>'.
11   imports: [BrowserModule, , CommonModule],
     ~~~~~~~

In the previous listing, we immediately see that there is an issue in the imports array and that somehow, an undefined value ended up in it. Context is provided and the error description is improved.

Finally, let's look at the improved description of an error triggered by a piece of code that is not statically analyzable, as follows:

import { Component } from '@angular/core';
const template = location.href;
@Component({
  selector: 'app-root',
  styleUrls: ['./app.component.css'],
  template,
})
export class AppComponent {}

The template option in the previous example is not statically determinable as it relies on information only available at runtime. Here is the error message output by View Engine:

ERROR in No template specified for component AppComponent

In the previous listing, we see that the View Engine error message is for metadata that is not statically determinable. It tells us which component has an error, but it does not help us understand how to fix it. The type information shows that location.href is of type string, so why does AppComponent not have a template option according to the compiler?

The error message output by Ivy is a lot more helpful, as seen in the following listing:

ERROR in src/app/app.component.ts:8:3 - error NG1010: template must be a string
  Value could not be determined statically.
8   template,
    ~~~~~~~~
  src/app/app.component.ts:3:18
    3 const template = location.href;
                       ~~~~~~~~~~~~~
    Unable to evaluate this expression statically.
  node_modules/typescript/lib/lib.dom.d.ts:19441:13
    19441 declare var location: Location;
                      ~~~~~~~~~~~~~~~~~~
    A value for 'location' cannot be determined statically, as it is an external declaration.
src/app/app.module.ts:8:5 - error NG6001: The class 'AppComponent' is listed in the declarations of the NgModule 'AppModule', but is not a directive, a component, or a pipe. Either remove it from the NgModule's declarations, or add an appropriate Angular decorator.
8     AppComponent
      ~~~~~~~~~~~~
  src/app/app.component.ts:10:14
    10 export class AppComponent { }
                    ~~~~~~~~~~~~
    'AppComponent' is declared here.

The error message output by Ivy doesn't just show us the line and expression that are not statically determinable. It goes one step further and shows us that the entire location object is non-deterministic at compile time.

Read more about AOT limitations in Chapter 12, Embracing Ahead-of-Time Compilation.

Strict template type checking

View Engine had basic and full modes for template type checking. Ivy introduces strict template type checking. In addition to the full-mode template type checks, strict mode enables several checks, outlined as follows:

  • Property binding types are checked against their corresponding input property type.
  • Property binding type checks are strict about null and undefined values.
  • Generic types for components and directives are inferred and checked.
  • Checks the type of template context variables, including $implicit.
  • Checks the type of the $event template reference.
  • Checks the type of template references to DOM elements.
  • Safe navigation operations are type-checked.
  • Array and object literals in component templates are type-checked.
  • Attribute bindings are type-checked.

The strict check of null for property bindings is important for property bindings using AsyncPipe as it initially emits a null value. This means that input properties being used with AsyncPipe must either have a type that includes null or use the concepts known as template guards and input setter type hints.

An even better update experience

As part of Ivy, the Angular command-line interface (CLI) adds three improvements to the update experience.

When running the ng update command, the Angular CLI first downloads the latest stable version of the Angular CLI and uses it for the update to take advantage of the most recent improvements.

As part of the ng update command, automated migrations are run by the Angular CLI. For every migration, a message is shown, such as the example shown in the following listing:

** Executing migrations of package '@angular/core' **
> Static flag migration.
  Removes the `static` flag from dynamic queries.
  As of Angular 9, the "static" flag defaults to false and is no longer required for your view and content queries.
  Read more about this here: https://v9.angular.io/guide/migration-dynamic-flag
  Migration completed.

Finally, the --create-commits parameter flag is introduced to create a Git commit per migration to make it easier to debug the update process.

We will cover the Angular update process in more detail in Chapter 11, Migrating Your Angular Application from View Engine to Ivy.

Better IDE integration

Ivy introduces remarkable improvements to the Angular Language Service, which integrates with our IDE, such as VS Code.

Template and style Uniform Resource Locators (URLs) are verified inline, as seen in the following screenshot, where an error is displayed. This is especially helpful when renaming components:

Figure 2.1 – Invalid template URL error

Figure 2.1 – Invalid template URL error

Another neat improvement is that we can use the Go to definition IDE command to navigate to a component template or style sheet file. This makes it easier to work with one specific component.

Ivy versions of the Angular Language Service give us an additional context in tooltips for component models. The following screenshot shows a tooltip for a child component element in a component template. In the tooltip, we can see the Angular module that declared the child component. In the example, we see that ChildComponent is declared by AppModule:

Figure 2.2 – NgModule annotation in the component element tooltip

Figure 2.2 – NgModule annotation in the component element tooltip

Similarly, the tooltip that appears for a component model also shows the Angular module that declared this component, as seen in the next screenshot. Here, we see that AppComponent is declared by AppModule:

Figure 2.3 – NgModule annotation in the component model tooltip

Figure 2.3 – NgModule annotation in the component model tooltip

With Ivy, we can see method signatures in a tooltip, as seen in the following screenshot. This helps us reason about event handlers:

Figure 2.4 – Component method signature in the tooltip

Figure 2.4 – Component method signature in the tooltip

Ivy's Angular Language Service enables us to see type annotations of UI properties and the $implicit template reference. This allows us to get type information about an iterable UI property used with the NgFor directive, as seen in the following screenshot:

Figure 2.5 – Iterable UI property tooltip

Figure 2.5 – Iterable UI property tooltip

The next screenshot shows how Ivy enables us to get type information about the named iterator template reference in each loop cycle:

Figure 2.6 – Named iterator template reference tooltip

Figure 2.6 – Named iterator template reference tooltip

Combined with strict template type checking, as described earlier in this chapter, this allows us to catch type errors in components early and consistently.

The improved Angular Language Service released with Ivy adds syntax highlighting to inline component templates and styles, as seen in the following screenshot. This makes single-file components easier to use:

Figure 2.7 – Syntax highlighting for inline component template and styles

Figure 2.7 – Syntax highlighting for inline component template and styles

Inline templates even benefit from the ability to add syntax highlighting to template expressions also introduced with Ivy.

Finally, Ivy adds support for style preprocessors such as Sass in inline styles by introducing the inlineStyleLanguage option to the Angular @angular-devkit/build-angular:browser and @angular-devkit/build-angular:karma builders, for example: "inlineStyleLanguage": "scss".

 

Summary

In this chapter, we learned how Angular Ivy boosts our developer productivity through even better tooling and predictable APIs. We started out by learning the many ways of binding styles to elements using Angular Ivy. Through a comprehensive example, we saw how the order of style bindings does not matter. However, we learned that the type of style binding matters as it follows a precedence or priority defined by Angular Ivy. This makes the resolution of multiple style bindings predictable, which is important for implementing certain complex use cases. Finally, we discussed that this change was necessary because Ivy does not guarantee the order in which directives and data bindings are applied.

Ivy requires Directive and Component decorators on base classes that rely on Angular features, such as input and output properties or child queries. We learned how this results in a pattern that makes it easy to share metadata through inheritance in a predictable way.

To learn how Ivy's AOT compiler is faster and, for certain Angular projects, produces smaller bundles, we discussed how a lot of the internals were rewritten between View Engine and Ivy. We learned how the Ivy Instruction Set is a good fit for tree shaking to remove the parts of the framework that are unused by our applications.

With this chapter completed, you now know how Angular's strict mode preset and other configurations allow us to catch errors sooner, resulting in a more robust code base.

We discussed how Angular Ivy significantly improves the testing experience as it introduces major speed improvements and useful test utilities. We saw examples of how TypeScript allows us to expect a type error in tests to create APIs that are more robust at runtime. We discussed how AOT compilation is enabled in tests to reduce the gap between tests and runtime. This can significantly improve developer feedback when implementing tests and application code.

Not only did Ivy improve our testing experience, but it is also a productivity booster when it comes to the overall developer experience. This chapter compared compiler error messages from View Engine to the same type of error messages in Ivy to demonstrate the additional context they include. We discussed how strict template type checking finalizes type checking in component templates.

We briefly discussed how Ivy improves the update experience through messages that are output during automated migrations and how we can use the --create-commits parameter flag to split the automated migrations into individual Git commits.

We ended by discussing most of the improvements in Angular's IDE integrations, such as the new invalid template and style URL errors, tooltips displaying and declaring Angular modules, and syntax highlighting for inline component templates and styles.

Chapter 3, Introducing CSS Custom Properties and New Provider Scopes, and Chapter 4, Exploring Angular Components Features, are going to prepare you for the Ivy features you are going to need to implement features on an existing Angular application in Part 2, Build a Real-World Application with the Angular Ivy Features You Learned. We will discuss topics such as CSS Custom Properties, the Clipboard API, component testing harnesses, and the platform provider scope, all in the context of Angular.

About the Authors
  • Lars Gyrup Brink Nielsen

    Lars Gyrup Brink Nielsen is a Co-Founder of an Open Learning non-profit called This is Learning. He's a Tech Writer at This is Angular. Lars speaks at meetups, conferences, and live streams worldwide. He's a FOSS maintainer and Open Learning contributor. Lars is also a Microsoft MVP in Developer Technologies and a GitHub Star. As a Frontend Architect at Systematic A/S, he architects and delivers cloud-native solutions for the Danish energy industry using Continuous Delivery and automation with modern tech stacks and toolchains built for scale.

    Browse publications by this author
  • Jacob Andresen

    Jacob Andresen works as a senior software developer based in Copenhagen in Denmark. He has been working as a software developer and consultant in information retrieval systems and web applications since 2002.

    Browse publications by this author
Accelerating Angular Development with Ivy
Unlock this book and the full library FREE for 7 days
Start now