Welcome to Knowledge Base!

KB at your finger tips

This is one stop global knowledge base where you can learn about all the products, solutions and support features.

Categories
All
Web-Angular
Angular - Resolving zone pollution

Resolving zone pollution link

Zone.js is a signaling mechanism that Angular uses to detect when an application state might have changed. It captures asynchronous operations like setTimeout , network requests, and event listeners. Angular schedules change detection based on signals from Zone.js

In some cases scheduled tasks or microtasks don’t make any changes in the data model, which makes running change detection unnecessary. Common examples are:

  • requestAnimationFrame , setTimeout or setInterval
  • Task or microtask scheduling by third-party libraries

This section covers how to identify such conditions, and how to run code outside the Angular zone to avoid unnecessary change detection calls.

Identifying unnecessary change detection calls link

You can detect unnecessary change detection calls using Angular DevTools. Often they appear as consecutive bars in the profiler’s timeline with source setTimeout , setInterval , requestAnimationFrame , or an event handler. When you have limited calls within your application of these APIs, the change detection invocation is usually caused by a third-party library.

In the image above, there is a series of change detection calls triggered by event handlers associated with an element. That’s a common challenge when using third-party, non-native Angular components, which do not alter the default behavior of NgZone .

Run tasks outside NgZone link

In such cases, you can instruct Angular to avoid calling change detection for tasks scheduled by a given piece of code using NgZone.

      
      import { Component, NgZone, OnInit } from '@angular/core';
@Component(...)
class AppComponent implements OnInit {
  constructor(private ngZone: NgZone) {}
  ngOnInit() {
    this.ngZone.runOutsideAngular(() => setInterval(pollForUpdates), 500);
  }
}
    

The preceding snippet instructs Angular to call setInterval outside the Angular Zone and skip running change detection after pollForUpdates runs.

Third-party libraries commonly trigger unnecessary change detection cycles because they weren't authored with Zone.js in mind. Avoid these extra cycles by calling library APIs outside the Angular zone:

      
      import { Component, NgZone, OnInit } from '@angular/core';
import * as Plotly from 'plotly.js-dist-min';

@Component(...)
class AppComponent implements OnInit {
  constructor(private ngZone: NgZone) {}
  ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
      Plotly.newPlot('chart', data);
    });
  }
}
    

Running Plotly.newPlot('chart', data); within runOutsideAngular instructs the framework that it shouldn’t run change detection after the execution of tasks scheduled by the initialization logic.

For example, if Plotly.newPlot('chart', data) adds event listeners to a DOM element, Angular does not run change detection after the execution of their handlers.

Last reviewed on Wed May 04 2022
Angular - Angular change detection and runtime optimization

Angular change detection and runtime optimization link

Change detection is the process through which Angular checks to see whether your application state has changed, and if any DOM needs to be updated. At a high level, Angular walks your components from top to bottom, looking for changes. Angular runs its change detection mechanism periodically so that changes to the data model are reflected in an application’s view. Change detection can be triggered either manually or through an asynchronous event (for example, a user interaction or an XMLHttpRequest completion).

Change detection is a highly optimized performant, but it can still cause slowdowns if the application runs it too frequently.

In this guide, you’ll learn how to control and optimize the change detection mechanism by skipping parts of your application and running change detection only when necessary.

Watch this video if you prefer to learn more about performance optimizations in a media format:

Last reviewed on Wed May 04 2022
Read article
Angular - Cheat Sheet

Cheat Sheet link

Bootstrapping Details
      
      import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
    
Import platformBrowserDynamic from @angular/platform-browser-dynamic .
      
      platformBrowserDynamic().bootstrapModule(AppModule);
    
Bootstraps the application, using the root component from the specified NgModule .
NgModules Details
      
      import { NgModule } from '@angular/core';
    
Import NgModule from @angular/core .
      
      @NgModule({ 
  declarations: …, 
  imports: …, 
  exports: …, 
  providers: …, 
  bootstrap:  
}) 
class MyModule {}
    
Defines a module that contains components, directives, pipes, and providers.
      
      declarations: [ 
  MyRedComponent, 
  MyBlueComponent, 
  MyDatePipe 
]
    
List of components, directives, and pipes that belong to this module.
      
      imports: [ 
  BrowserModule, 
  SomeOtherModule 
]
    
List of modules to import into this module. Everything from the imported modules is available to declarations of this module.
      
      exports: [ 
  MyRedComponent, 
  MyDatePipe 
]
    
List of components, directives, and pipes visible to modules that import this module.
      
      providers: [ 
  MyService, 
  { provide:  } 
]
    
List of dependency injection providers visible both to the contents of this module and to importers of this module.
      
      bootstrap: [MyAppComponent]
    
List of components to bootstrap when this module is bootstrapped.
Template syntax Details
      
      <input [value]="firstName">
    
Binds property value to the result of expression firstName .
      
      <div [attr.role]="myAriaRole">
    
Binds attribute role to the result of expression myAriaRole .
      
      <div [class.extra-sparkle]="isDelightful">
    
Binds the presence of the CSS class extra-sparkle on the element to the truthiness of the expression isDelightful .
      
      <div [style.width.px]="mySize">
    
Binds style property width to the result of expression mySize in pixels. Units are optional.
      
      <button (click)="readRainbow($event)">
    
Calls method readRainbow when a click event is triggered on this button element (or its children) and passes in the event object.
      
      <div title="Hello {{ponyName}}">
    
Binds a property to an interpolated string, for example, "Hello Seabiscuit". Equivalent to:
      
      <div [title]="'Hello ' + ponyName">
    
      
      <p> 
  Hello {{ponyName}} 
</p>
    
Binds text content to an interpolated string, for example, "Hello Seabiscuit".
      
      <my-cmp [(title)]="name">
    
Sets up two-way data binding. Equivalent to:
      
      <my-cmp [title]="name" (titleChange)="name=$event">
    
      
      <video #movieplayer></video> 
<button (click)="movieplayer.play()"> 
  Play 
</button>
    
Creates a local variable movieplayer that provides access to the video element instance in data-binding and event-binding expressions in the current template.
      
      <p *myUnless="myExpression"> 
  … 
</p>
    
The asterisk ( * ) character turns the current element into an embedded template. Equivalent to:
      
      <ng-template [myUnless]="myExpression"> 
  <p> 
    … 
  </p> 
</ng-template>
    
      
      <p> 
  Card No.: {{cardNumber | myCardNumberFormatter}} 
</p>
    
Transforms the current value of expression cardNumber using the pipe called myCardNumberFormatter .
      
      <p> 
  Employer: {{employer?.companyName}} 
</p>
    
The safe navigation operator ( ? ) means that the employer field is optional and if undefined , the rest of the expression should be ignored.
      
      <svg:rect x="0" 
          y="0" 
          width="100" 
          height="100"/>
    
An SVG snippet template needs an svg: prefix on its root element to disambiguate the SVG element from an HTML component.
      
      <svg> 
  <rect x="0" 
        y="0" 
        width="100" 
        height="100"/> 
</svg>
    
An <svg> root element is detected as an SVG element automatically, without the prefix.
Built-in directives Details
      
      import { CommonModule } from '@angular/common';
    
Import CommonModule from @angular/common .
      
      <section *ngIf="showSection">
    
Removes or recreates a portion of the DOM tree based on the showSection expression.
      
      <li *ngFor="let item of list">
    
Turns the li element and its contents into a template, and uses that to instantiate a view for each item in list.
      
      <div [ngSwitch]="conditionExpression">
  <ng-template [ngSwitchCase]="case1Exp"> 
    … 
  </ng-template>
  <ng-template ngSwitchCase="case2LiteralString"> 
    … 
  </ng-template>
  <ng-template ngSwitchDefault> 
    … 
  </ng-template> 
</div>
    
Conditionally swaps the contents of the div by selecting one of the embedded templates based on the current value of conditionExpression .
      
      <div [ngClass]="{'active': isActive, 
                 'disabled': isDisabled}">
    
Binds the presence of CSS classes on the element to the truthiness of the associated map values. The right-hand expression should return {class-name: true/false} map.
      
      <div [ngStyle]="{'property': 'value'}"> 
<div [ngStyle]="dynamicStyles()">
    
Allows you to assign styles to an HTML element using CSS. You can use CSS directly, as in the first example, or you can call a method from the component.
Forms Details
      
      import { FormsModule } from '@angular/forms';
    
Import FormsModule from @angular/forms .
      
      <input [(ngModel)]="userName">
    
Provides two-way data-binding, parsing, and validation for form controls.
Class decorators Details
      
      import { Directive,  } from '@angular/core';
    
Import Directive, &hellip; from @angular/core'; .
      
      @Component({…}) 
class MyComponent() {}
    
Declares that a class is a component and provides metadata about the component.
      
      @Directive({…}) 
class MyDirective() {}
    
Declares that a class is a directive and provides metadata about the directive.
      
      @Pipe({…}) 
class MyPipe() {}
    
Declares that a class is a pipe and provides metadata about the pipe.
      
      @Injectable() 
class MyService() {}
    
Declares that a class can be provided and injected by other classes. Without this decorator, the compiler won't generate enough metadata to allow the class to be created properly when it's injected somewhere.
Directive configuration Details
      
      @Directive({ 
  property1: value1, 
   
})
    
Add property1 property with value1 value to Directive.
      
      selector: '.cool-button:not(a)'
    
Specifies a CSS selector that identifies this directive within a template. Supported selectors include element , [attribute] , .class , and :not() .
Does not support parent-child relationship selectors.
      
      providers: [ 
  MyService, 
  { provide:  } 
]
    
List of dependency injection providers for this directive and its children.
Component configuration
@Component extends @Directive , so the @Directive configuration applies to components as well
Details
      
      moduleId: module.id
    
If set, the templateUrl and styleUrl are resolved relative to the component.
      
      viewProviders: [MyService, { provide:  }]
    
List of dependency injection providers scoped to this component's view.
      
      template: 'Hello {{name}}' 
templateUrl: 'my-component.html'
    
Inline template or external template URL of the component's view.
      
      styles: ['.primary {color: red}'] 
styleUrls: ['my-component.css']
    
List of inline CSS styles or external stylesheet URLs for styling the component's view.
Class field decorators for directives and components Details
      
      import { Input,  } from '@angular/core';
    
Import Input, ... from @angular/core .
      
      @Input() myProperty;
    
Declares an input property that you can update using property binding (example: <my-cmp [myProperty]="someExpression"> ).
      
      @Output() myEvent = new EventEmitter();
    
Declares an output property that fires events that you can subscribe to with an event binding (example: <my-cmp (myEvent)="doSomething()"> ).
      
      @HostBinding('class.valid') isValid;
    
Binds a host element property (here, the CSS class valid ) to a directive/component property ( isValid ).
      
      @HostListener('click', ['$event']) onClick(e) {…}
    
Subscribes to a host element event ( click ) with a directive/component method ( onClick ), optionally passing an argument ( $event ).
      
      @ContentChild(myPredicate) myChildComponent;
    
Binds the first result of the component content query ( myPredicate ) to a property ( myChildComponent ) of the class.
      
      @ContentChildren(myPredicate) myChildComponents;
    
Binds the results of the component content query ( myPredicate ) to a property ( myChildComponents ) of the class.
      
      @ViewChild(myPredicate) myChildComponent;
    
Binds the first result of the component view query ( myPredicate ) to a property ( myChildComponent ) of the class. Not available for directives.
      
      @ViewChildren(myPredicate) myChildComponents;
    
Binds the results of the component view query ( myPredicate ) to a property ( myChildComponents ) of the class. Not available for directives.
Directive and component change detection and lifecycle hooks (implemented as class methods) Details
      
      constructor(myService: MyService, …) {  }
    
Called before any other lifecycle hook. Use it to inject dependencies, but avoid any serious work here.
      
      ngOnChanges(changeRecord) {  }
    
Called after every change to input properties and before processing content or child views.
      
      ngOnInit() {  }
    
Called after the constructor, initializing input properties, and the first call to ngOnChanges .
      
      ngDoCheck() {  }
    
Called every time that the input properties of a component or a directive are checked. Use it to extend change detection by performing a custom check.
      
      ngAfterContentInit() {  }
    
Called after ngOnInit when the component's or directive's content has been initialized.
      
      ngAfterContentChecked() {  }
    
Called after every check of the component's or directive's content.
      
      ngAfterViewInit() {  }
    
Called after ngAfterContentInit when the component's views and child views / the view that a directive is in has been initialized.
      
      ngAfterViewChecked() {  }
    
Called after every check of the component's views and child views / the view that a directive is in.
      
      ngOnDestroy() {  }
    
Called once, before the instance is destroyed.
Dependency injection configuration Details
      
      { provide: MyService, useClass: MyMockService }
    
Sets or overrides the provider for MyService to the MyMockService class.
      
      { provide: MyService, useFactory: myFactory }
    
Sets or overrides the provider for MyService to the myFactory factory function.
      
      { provide: MyValue, useValue: 41 }
    
Sets or overrides the provider for MyValue to the value 41 .
Routing and navigation Details
      
      import { Routes, RouterModule,  } from '@angular/router';
    
Import Routes, RouterModule, ... from @angular/router .
      
      const routes: Routes = [ 
  { path: '', component: HomeComponent }, 
  { path: 'path/:routeParam', component: MyComponent }, 
  { path: 'staticPath', component:  }, 
  { path: '**', component:  }, 
  { path: 'oldPath', redirectTo: '/staticPath' }, 
  { path: …, component: …, data: { message: 'Custom' } } 
]); 
 
const routing = RouterModule.forRoot(routes);
    
Configures routes for the application. Supports static, parameterized, redirect, and wildcard routes. Also supports custom route data and resolve.
      
      <router-outlet></router-outlet> 
<router-outlet name="aux"></router-outlet>
    
Marks the location to load the component of the active route.
      
      <a routerLink="/path"> 
<a [routerLink]="[ '/path', routeParam ]"> 
<a [routerLink]="[ '/path', { matrixParam: 'value' } ]"> 
<a [routerLink]="[ '/path' ]" [queryParams]="{ page: 1 }"> 
<a [routerLink]="[ '/path' ]" fragment="anchor">
    
Creates a link to a different view based on a route instruction consisting of a route path, required and optional parameters, query parameters, and a fragment. To navigate to a root route, use the / prefix; for a child route, use the ./ prefix; for a sibling or parent, use the ../ prefix.
      
      <a [routerLink]="[ '/path' ]" routerLinkActive="active">
    
The provided classes are added to the element when the routerLink becomes the current active route.
      
      <a [routerLink]="[ '/path' ]" routerLinkActive="active" ariaCurrentWhenActive="page">
    
The provided classes and aria-current attribute are added to the element when the routerLink becomes the current active route.
      
      function canActivateGuard: CanActivateFn = 
  ( 
    route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot 
  ) => {  } 
 
{ path: …, canActivate: [canActivateGuard] }
    
An interface for defining a function that the router should call first to determine if it should activate this component. Should return a boolean|UrlTree or an Observable/Promise that resolves to a boolean|UrlTree .
      
      function canDeactivateGuard: CanDeactivateFn<T> = 
  ( 
    component: T, 
    route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot 
  ) => {  } 
 
{ path: …, canDeactivate: [canDeactivateGuard] }
    
An interface for defining a function that the router should call first to determine if it should deactivate this component after a navigation. Should return a boolean|UrlTree or an Observable/Promise that resolves to a boolean|UrlTree .
      
      function canActivateChildGuard: CanActivateChildFn = 
  ( 
    route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot 
  ) => {  } 
 
{ path: …, canActivateChild: [canActivateGuard], children:  }
    
An interface for defining a function that the router should call first to determine if it should activate the child route. Should return a boolean|UrlTree or an Observable/Promise that resolves to a boolean|UrlTree .
      
      function resolveGuard implements ResolveFn<T> = 
  ( 
    route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot 
  ) => {  }  
 
{ path: …, resolve: [resolveGuard] }
    
An interface for defining a function that the router should call first to resolve route data before rendering the route. Should return a value or an Observable/Promise that resolves to a value.
      
      function canLoadGuard: CanLoadFn = 
  ( 
    route: Route 
  ) => {  } 
 
{ path: …, canLoad: [canLoadGuard], loadChildren:  }
    
An interface for defining a function that the router should call first to check if the lazy loaded module should be loaded. Should return a boolean|UrlTree or an Observable/Promise that resolves to a boolean|UrlTree .
Last reviewed on Mon Feb 28 2022
Read article
Angular - Class and style binding

Class and style binding link

Use class and style bindings to add and remove CSS class names from an element's class attribute and to set styles dynamically.

Prerequisites link

  • Property binding

Binding to a single CSS class link

To create a single class binding, type the following:

[class.sale]="onSale"

Angular adds the class when the bound expression, onSale is truthy, and it removes the class when the expression is falsy—with the exception of undefined . See styling delegation for more information.

Binding to multiple CSS classes link

To bind to multiple classes, type the following:

[class]="classExpression"

The expression can be one of:

  • A space-delimited string of class names.
  • An object with class names as the keys and truthy or falsy expressions as the values.
  • An array of class names.

With the object format, Angular adds a class only if its associated value is truthy.

With any object-like expression—such as object , Array , Map , or Set —the identity of the object must change for Angular to update the class list. Updating the property without changing object identity has no effect.

If there are multiple bindings to the same class name, Angular uses styling precedence to determine which binding to use.

The following table summarizes class binding syntax.

Binding Type Syntax Input Type Example Input Values
Single class binding [class.sale]="onSale" boolean | undefined | null true , false
Multi-class binding [class]="classExpression" string "my-class-1 my-class-2 my-class-3"
Multi-class binding [class]="classExpression" Record<string, boolean | undefined | null> {foo: true, bar: false}
Multi-class binding [class]="classExpression" Array<string> ['foo', 'bar']

Binding to a single style link

To create a single style binding, use the prefix style followed by a dot and the name of the CSS style.

For example, to set the width style, type the following: [style.width]="width"

Angular sets the property to the value of the bound expression, which is usually a string. Optionally, you can add a unit extension like em or % , which requires a number type.

  1. To write a style in dash-case, type the following:

          
          <nav [style.background-color]="expression"></nav>
        
  2. To write a style in camelCase, type the following:

          
          <nav [style.backgroundColor]="expression"></nav>
        

Binding to multiple styles link

To toggle multiple styles, bind to the [style] attribute—for example, [style]="styleExpression" . The styleExpression can be one of:

  • A string list of styles such as "width: 100px; height: 100px; background-color: cornflowerblue;" .
  • An object with style names as the keys and style values as the values, such as {width: '100px', height: '100px', backgroundColor: 'cornflowerblue'} .

Note that binding an array to [style] is not supported.

When binding [style] to an object expression, the identity of the object must change for Angular to update the class list. Updating the property without changing object identity has no effect.

Single and multiple-style binding example link

nav-bar.component.ts
      
      @Component({
  selector: 'app-nav-bar',
  template: `
<nav [style]='navStyle'>
  <a [style.text-decoration]="activeLinkStyle">Home Page</a>
  <a [style.text-decoration]="linkStyle">Login</a>
</nav>`
})
export class NavBarComponent {
  navStyle = 'font-size: 1.2rem; color: cornflowerblue;';
  linkStyle = 'underline';
  activeLinkStyle = 'overline';
  /* . . . */
}
    

If there are multiple bindings to the same style attribute, Angular uses styling precedence to determine which binding to use.

The following table summarizes style binding syntax.

Binding Type Syntax Input Type Example Input Values
Single style binding [style.width]="width" string | undefined | null "100px"
Single style binding with units [style.width.px]="width" number | undefined | null 100
Multi-style binding [style]="styleExpression" string "width: 100px; height: 100px"
Multi-style binding [style]="styleExpression" Record<string, string | undefined | null> {width: '100px', height: '100px'}

Styling precedence link

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

What’s next link

  • Component styles
  • Introduction to Angular animations
Last reviewed on Mon May 09 2022
Read article
Angular - Angular CLI builders

Angular CLI builders link

A number of Angular CLI commands run a complex process on your code, such as linting, building, or testing. The commands use an internal tool called Architect to run CLI builders , which apply another tool to accomplish the wanted task.

With Angular version 8, the CLI Builder API is stable and available to developers who want to customize the Angular CLI by adding or modifying commands. For example, you could supply a builder to perform an entirely new task, or to change which third-party tool is used by an existing command.

This document explains how CLI builders integrate with the workspace configuration file, and shows how you can create your own builder.

Find the code from the examples used here in this GitHub repository.

CLI builders link

The internal Architect tool delegates work to handler functions called builders . A builder handler function receives two arguments; a set of input options (a JSON object), and a context (a BuilderContext object).

The separation of concerns here is the same as with schematics, which are used for other CLI commands that touch your code (such as ng generate ).

  • The options object is provided by the CLI user, while the context object is provided by the CLI Builder API
  • In addition to the contextual information, the context object, which is an instance of the BuilderContext , also provides access to a scheduling method, context.scheduleTarget() . The scheduler executes the builder handler function with a given target configuration.

The builder handler function can be synchronous (return a value) or asynchronous (return a Promise), or it can watch and return multiple values (return an Observable). The return value or values must always be of type BuilderOutput . This object contains a Boolean success field and an optional error field that can contain an error message.

Angular provides some builders that are used by the CLI for commands such as ng build and ng test . Default target configurations for these and other built-in CLI builders can be found (and customized) in the "architect" section of the workspace configuration file, angular.json . Also, extend and customize Angular by creating your own builders, which you can run using the ng run CLI command.

Builder project structure link

A builder resides in a "project" folder that is similar in structure to an Angular workspace, with global configuration files at the top level, and more specific configuration in a source folder with the code files that define the behavior. For example, your myBuilder folder could contain the following files.

Files Purpose
src/my-builder.ts Main source file for the builder definition.
src/my-builder.spec.ts Source file for tests.
src/schema.json Definition of builder input options.
builders.json Builders definition.
package.json Dependencies. See https://docs.npmjs.com/files/package.json.
tsconfig.json TypeScript configuration.

Publish the builder to npm (see Publishing your Library). If you publish it as @example/my-builder , install it using the following command.

      
      npm install @example/my-builder
    

Creating a builder link

As an example, create a builder that copies a file. To create a builder, use the createBuilder() CLI Builder function, and return a Promise<BuilderOutput> object.

src/my-builder.ts (builder skeleton)
      
      import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
}
    

Now let's add some logic to it. The following code retrieves the source and destination file paths from user options and copies the file from the source to the destination (using the Promise version of the built-in NodeJS copyFile() function). If the copy operation fails, it returns an error with a message about the underlying problem.

src/my-builder.ts (builder)
      
      import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    return {
      success: false,
      error: err.message,
    };
  }

  return { success: true };
}
    

Handling output link

By default, copyFile() does not print anything to the process standard output or error. If an error occurs, it might be difficult to understand exactly what the builder was trying to do when the problem occurred. Add some additional context by logging additional information using the Logger API. This also lets the builder itself be executed in a separate process, even if the standard output and error are deactivated (as in an Electron app).

You can retrieve a Logger instance from the context.

src/my-builder.ts (handling output)
      
      import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    return {
      success: false,
      error: err.message,
    };
  }

  return { success: true };
}
    

Progress and status reporting link

The CLI Builder API includes progress and status reporting tools, which can provide hints for certain functions and interfaces.

To report progress, use the context.reportProgress() method, which takes a current value, (optional) total, and status string as arguments. The total can be any number; for example, if you know how many files you have to process, the total could be the number of files, and current should be the number processed so far. The status string is unmodified unless you pass in a new string value.

You can see an example of how the tslint builder reports progress.

In our example, the copy operation either finishes or is still executing, so there's no need for a progress report, but you can report status so that a parent builder that called our builder would know what's going on. Use the context.reportStatus() method to generate a status string of any length.

NOTE :
There's no guarantee that a long string will be shown entirely; it could be cut to fit the UI that displays it.

Pass an empty string to remove the status.

src/my-builder.ts (progress reporting)
      
      import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect';
import { JsonObject } from '@angular-devkit/core';
import { promises as fs } from 'fs';

interface Options extends JsonObject {
  source: string;
  destination: string;
}

export default createBuilder(copyFileBuilder);

async function copyFileBuilder(
  options: Options,
  context: BuilderContext,
): Promise<BuilderOutput> {
  context.reportStatus(`Copying ${options.source} to ${options.destination}.`);
  try {
    await fs.copyFile(options.source, options.destination);
  } catch (err) {
    context.logger.error('Failed to copy file.');
    return {
      success: false,
      error: err.message,
    };
  }

  context.reportStatus('Done.');
  return { success: true };
}
    

Builder input link

You can invoke a builder indirectly through a CLI command, or directly with the Angular CLI ng run command. In either case, you must provide required inputs, but can let other inputs default to values that are pre-configured for a specific target , provide a pre-defined, named override configuration, and provide further override option values on the command line.

Input validation link

You define builder inputs in a JSON schema associated with that builder. The Architect tool collects the resolved input values into an options object, and validates their types against the schema before passing them to the builder function. (The Schematics library does the same kind of validation of user input.)

For our example builder, you expect the options value to be a JsonObject with two keys: A source and a destination , each of which are a string.

You can provide the following schema for type validation of these values.

src/schema.json
      
      {
  "$schema": "http://json-schema.org/schema",
  "type": "object",
  "properties": {
    "source": {
      "type": "string"
    },
    "destination": {
      "type": "string"
    }
  }
}
    

This is a very simple example, but the use of a schema for validation can be very powerful. For more information, see the JSON schemas website.

To link our builder implementation with its schema and name, you need to create a builder definition file, which you can point to in package.json .

Create a file named builders.json that looks like this:

builders.json
      
      {
  "builders": {
    "copy": {
      "implementation": "./dist/my-builder.js",
      "schema": "./src/schema.json",
      "description": "Copies a file."
    }
  }
}
    

In the package.json file, add a builders key that tells the Architect tool where to find our builder definition file.

package.json
      
      {
  "name": "@example/copy-file",
  "version": "1.0.0",
  "description": "Builder for copying files",
  "builders": "builders.json",
  "dependencies": {
    "@angular-devkit/architect": "~0.1200.0",
    "@angular-devkit/core": "^12.0.0"
  }
}
    

The official name of our builder is now @example/copy-file:copy . The first part of this is the package name (resolved using node resolution), and the second part is the builder name (resolved using the builders.json file).

Using one of our options is very straightforward. You did this in the previous section when you accessed options.source and options.destination .

src/my-builder.ts (report status)
      
      context.reportStatus(`Copying ${options.source} to ${options.destination}.`);
try {
  await fs.copyFile(options.source, options.destination);
} catch (err) {
  context.logger.error('Failed to copy file.');
  return {
    success: false,
    error: err.message,
  };
}

context.reportStatus('Done.');
return { success: true };
    

Target configuration link

A builder must have a defined target that associates it with a specific input configuration and project.

Targets are defined in the angular.json CLI configuration file. A target specifies the builder to use, its default options configuration, and named alternative configurations. The Architect tool uses the target definition to resolve input options for a given run.

The angular.json file has a section for each project, and the "architect" section of each project configures targets for builders used by CLI commands such as 'build', 'test', and 'lint'. By default, for example, the build command runs the builder @angular-devkit/build-angular:browser to perform the build task, and passes in default option values as specified for the build target in angular.json .

angular.json
      
      {
  "myApp": {
    
    "architect": {
      "build": {
        "builder": "@angular-devkit/build-angular:browser",
        "options": {
          "outputPath": "dist/myApp",
          "index": "src/index.html",
          
        },
        "configurations": {
          "production": {
            "fileReplacements": [
              {
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
              }
            ],
            "optimization": true,
            "outputHashing": "all",
            
          }
        }
      },
      
    

The command passes the builder the set of default options specified in the "options" section. If you pass the --configuration=production flag, it uses the override values specified in the production alternative configuration. Specify further option overrides individually on the command line. You might also add more alternative configurations to the build target, to define other environments such as stage or qa .

Target strings link

The generic ng run CLI command takes as its first argument a target string of the following form.

      
      project:target[:configuration]
    
Details
project The name of the Angular CLI project that the target is associated with.
target A named builder configuration from the architect section of the angular.json file.
configuration (optional) The name of a specific configuration override for the given target, as defined in the angular.json file.

If your builder calls another builder, it might need to read a passed target string. Parse this string into an object by using the targetFromTargetString() utility function from @angular-devkit/architect .

Schedule and run link

Architect runs builders asynchronously. To invoke a builder, you schedule a task to be run when all configuration resolution is complete.

The builder function is not executed until the scheduler returns a BuilderRun control object. The CLI typically schedules tasks by calling the context.scheduleTarget() function, and then resolves input options using the target definition in the angular.json file.

Architect resolves input options for a given target by taking the default options object, then overwriting values from the configuration used (if any), then further overwriting values from the overrides object passed to context.scheduleTarget() . For the Angular CLI, the overrides object is built from command line arguments.

Architect validates the resulting options values against the schema of the builder. If inputs are valid, Architect creates the context and executes the builder.

For more information see Workspace Configuration.

You can also invoke a builder directly from another builder or test by calling context.scheduleBuilder() . You pass an options object directly to the method, and those option values are validated against the schema of the builder without further adjustment.

Only the context.scheduleTarget() method resolves the configuration and overrides through the angular.json file.

Default architect configuration link

Let's create a simple angular.json file that puts target configurations into context.

You can publish the builder to npm (see Publishing your Library), and install it using the following command:

      
      npm install @example/copy-file
    

If you create a new project with ng new builder-test , the generated angular.json file looks something like this, with only default builder configurations.

angular.json
      
      {
  // 
  "projects": {
    // 
    "builder-test": {
      // 
      "architect": {
        // 
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            //  more options
            "outputPath": "dist/builder-test",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json"
          },
          "configurations": {
            "production": {
              //  more options
              "optimization": true,
              "aot": true,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  }
  // 
}
    

Adding a target link

Add a new target that will run our builder to copy a file. This target tells the builder to copy the package.json file.

You need to update the angular.json file to add a target for this builder to the "architect" section of our new project.

  • We'll add a new target section to the "architect" object for our project

  • The target named "copy-package" uses our builder, which you published to @example/copy-file . (See Publishing your Library.)

  • The options object provides default values for the two inputs that you defined; source , which is the existing file you are copying, and destination , the path you want to copy to

  • The configurations key is optional, we'll leave it out for now

angular.json
      
      {
  "projects": {
    "builder-test": {
      "architect": {
        "copy-package": {
          "builder": "@example/copy-file:copy",
          "options": {
            "source": "package.json",
            "destination": "package-copy.json"
          }
        },
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/builder-test",
            "index": "src/index.html",
            "main": "src/main.ts",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.app.json"
          },
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "aot": true,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  }
}
    

Running the builder link

To run our builder with the new target's default configuration, use the following CLI command.

      
      ng run builder-test:copy-package
    

This copies the package.json file to package-copy.json .

Use command-line arguments to override the configured defaults. For example, to run with a different destination value, use the following CLI command.

      
      ng run builder-test:copy-package --destination=package-other.json
    

This copies the file to package-other.json instead of package-copy.json . Because you did not override the source option, it will copy from the package.json file (the default value provided for the target).

Testing a builder link

Use integration testing for your builder, so that you can use the Architect scheduler to create a context, as in this example.

  • In the builder source directory, you have created a new test file my-builder.spec.ts . The code creates new instances of JsonSchemaRegistry (for schema validation), TestingArchitectHost (an in-memory implementation of ArchitectHost ), and Architect .

  • We've added a builders.json file next to the builder's package.json file, and modified the package file to point to it.

Here's an example of a test that runs the copy file builder. The test uses the builder to copy the package.json file and validates that the copied file's contents are the same as the source.

src/my-builder.spec.ts
      
      import { Architect } from '@angular-devkit/architect';
import { TestingArchitectHost } from '@angular-devkit/architect/testing';
import { schema } from '@angular-devkit/core';
import { promises as fs } from 'fs';

describe('Copy File Builder', () => {
  let architect: Architect;
  let architectHost: TestingArchitectHost;

  beforeEach(async () => {
    const registry = new schema.CoreSchemaRegistry();
    registry.addPostTransform(schema.transforms.addUndefinedDefaults);

    // TestingArchitectHost() takes workspace and current directories.
    // Since we don't use those, both are the same in this case.
    architectHost = new TestingArchitectHost(__dirname, __dirname);
    architect = new Architect(architectHost, registry);

    // This will either take a Node package name, or a path to the directory
    // for the package.json file.
    await architectHost.addBuilderFromPackage('..');
  });

  it('can copy files', async () => {
    // A "run" can have multiple outputs, and contains progress information.
    const run = await architect.scheduleBuilder('@example/copy-file:copy', {
      source: 'package.json',
      destination: 'package-copy.json',
    });

    // The "result" member (of type BuilderOutput) is the next output.
    const output = await run.result;

    // Stop the builder from running. This stops Architect from keeping
    // the builder-associated states in memory, since builders keep waiting
    // to be scheduled.
    await run.stop();

    // Expect that the copied file is the same as its source.
    const sourceContent = await fs.readFile('package.json', 'utf8');
    const destinationContent = await fs.readFile('package-copy.json', 'utf8');
    expect(destinationContent).toBe(sourceContent);
  });
});
    

When running this test in your repo, you need the ts-node package. You can avoid this by renaming my-builder.spec.ts to my-builder.spec.js .

Watch mode link

Architect expects builders to run once (by default) and return. This behavior is not entirely compatible with a builder that watches for changes (like Webpack, for example). Architect can support watch mode, but there are some things to look out for.

  • To be used with watch mode, a builder handler function should return an Observable. Architect subscribes to the Observable until it completes and might reuse it if the builder is scheduled again with the same arguments.

  • The builder should always emit a BuilderOutput object after each execution. Once it's been executed, it can enter a watch mode, to be triggered by an external event. If an event triggers it to restart, the builder should execute the context.reportRunning() function to tell Architect that it is running again. This prevents Architect from stopping the builder if another run is scheduled.

When your builder calls BuilderRun.stop() to exit watch mode, Architect unsubscribes from the builder's Observable and calls the builder's teardown logic to clean up. (This behavior also allows for long-running builds to be stopped and cleaned up.)

In general, if your builder is watching an external event, you should separate your run into three phases.

Phases Details
Running For example, webpack compiles. This ends when webpack finishes and your builder emits a BuilderOutput object.
Watching Between two runs, watch an external event stream. For example, webpack watches the file system for any changes. This ends when webpack restarts building, and context.reportRunning() is called. This goes back to step 1.
Completion Either the task is fully completed (for example, webpack was supposed to run a number of times), or the builder run was stopped (using BuilderRun.stop() ). Your teardown logic is executed, and Architect unsubscribes from your builder's Observable.

Summary link

The CLI Builder API provides a new way of changing the behavior of the Angular CLI by using builders to execute custom logic.

  • Builders can be synchronous or asynchronous, execute once or watch for external events, and can schedule other builders or targets

  • Builders have option defaults specified in the angular.json configuration file, which can be overwritten by an alternate configuration for the target, and further overwritten by command line flags

  • We recommend that you use integration tests to test Architect builders. Use unit tests to validate the logic that the builder executes.

  • If your builder returns an Observable, it should clean up in the teardown logic of that Observable

Last reviewed on Mon Feb 28 2022
Read article
Angular - Observables compared to other techniques

Observables compared to other techniques link

You can often use observables instead of promises to deliver values asynchronously. Similarly, observables can take the place of event handlers. Finally, because observables deliver multiple values, you can use them where you might otherwise build and operate on arrays.

Observables behave somewhat differently from the alternative techniques in each of these situations, but offer some significant advantages. Here are detailed comparisons of the differences.

Observables compared to promises link

Observables are often compared to promises. Here are some key differences:

  • Observables are declarative; computation does not start until subscription. Promises execute immediately on creation. This makes observables useful for defining recipes that can be run whenever you need the result.

  • Observables provide many values. Promises provide one. This makes observables useful for getting multiple values over time.

  • Observables differentiate between chaining and subscription. Promises only have .then() clauses. This makes observables useful for creating complex transformation recipes to be used by other part of the system, without causing the work to be executed.

  • Observables subscribe() is responsible for handling errors. Promises push errors to the child promises. This makes observables useful for centralized and predictable error handling.

Creation and subscription link

  • Observables are not executed until a consumer subscribes. The subscribe() executes the defined behavior once, and it can be called again. Each subscription has its own computation. Resubscription causes recomputation of values.

    src/observables.ts (observable)
          
          // declare a publishing operation
    const observable = new Observable<number>(observer => {
      // Subscriber fn...
    });
    
    // initiate execution
    observable.subscribe(value => {
      // observer handles notifications
    });
        
  • Promises execute immediately, and just once. The computation of the result is initiated when the promise is created. There is no way to restart work. All then clauses (subscriptions) share the same computation.

    src/promises.ts (promise)
          
          // initiate execution
    let promise = new Promise<number>(resolve => {
      // Executer fn...
    });
    promise.then(value => {
      // handle result here
    });
        

Chaining link

  • Observables differentiate between transformation function such as a map and subscription. Only subscription activates the subscriber function to start computing the values.

    src/observables.ts (chain)
          
          observable.pipe(map(v => 2 * v));
        
  • Promises do not differentiate between the last .then clauses (equivalent to subscription) and intermediate .then clauses (equivalent to map).

    src/promises.ts (chain)
          
          promise.then(v => 2 * v);
        

Cancellation link

  • Observable subscriptions are cancellable. Unsubscribing removes the listener from receiving further values, and notifies the subscriber function to cancel work.

    src/observables.ts (unsubscribe)
          
          const subscription = observable.subscribe(() => {
      // observer handles notifications
    });
    
    subscription.unsubscribe();
        
  • Promises are not cancellable.

Error handling link

  • Observable execution errors are delivered to the subscriber's error handler, and the subscriber automatically unsubscribes from the observable.

    src/observables.ts (error)
          
          observable.subscribe(() => {
      throw new Error('my error');
    });
        
  • Promises push errors to the child promises.

    src/promises.ts (error)
          
          promise.then(() => {
      throw new Error('my error');
    });
        

Cheat sheet link

The following code snippets illustrate how the same kind of operation is defined using observables and promises.

Operation Observable Promise
Creation
      
      new Observable((observer) => { 
  observer.next(123); 
});
    
      
      new Promise((resolve, reject) => { 
  resolve(123); 
});
    
Transform
      
      obs.pipe(map((value) => value * 2));
    
      
      promise.then((value) => value * 2);
    
Subscribe
      
      sub = obs.subscribe((value) => { 
  console.log(value) 
});
    
      
      promise.then((value) => { 
  console.log(value); 
});
    
Unsubscribe
      
      sub.unsubscribe();
    
Implied by promise resolution.

Observables compared to events API link

Observables are very similar to event handlers that use the events API. Both techniques define notification handlers, and use them to process multiple values delivered over time. Subscribing to an observable is equivalent to adding an event listener. One significant difference is that you can configure an observable to transform an event before passing the event to the handler.

Using observables to handle events and asynchronous operations can have the advantage of greater consistency in contexts such as HTTP requests.

Here are some code samples that illustrate how the same kind of operation is defined using observables and the events API.

Observable Events API
Creation & cancellation
      
      // Setup 
const clicks$ = fromEvent(buttonEl, 'click'); 
// Begin listening 
const subscription = clicks$ 
  .subscribe(e => console.log('Clicked', e)) 
// Stop listening 
subscription.unsubscribe();
    
      
      function handler(e) { 
  console.log('Clicked', e); 
} 
// Setup & begin listening 
button.addEventListener('click', handler); 
// Stop listening 
button.removeEventListener('click', handler);
    
Subscription
      
      observable.subscribe(() => { 
  // notification handlers here 
});
    
      
      element.addEventListener(eventName, (event) => { 
  // notification handler here 
});
    
Configuration Listen for keystrokes, but provide a stream representing the value in the input.
      
      fromEvent(inputEl, 'keydown').pipe( 
  map(e => e.target.value) 
);
    
Does not support configuration.
      
      element.addEventListener(eventName, (event) => { 
  // Cannot change the passed Event into another 
  // value before it gets to the handler 
});
    

Observables compared to arrays link

An observable produces values over time. An array is created as a static set of values. In a sense, observables are asynchronous where arrays are synchronous. In the following examples, implies asynchronous value delivery.

Values Observable Array
Given
      
      obs: 12357
    
      
      obsB: 'a''b''c'
    
      
      arr: [1, 2, 3, 5, 7]
    
      
      arrB: ['a', 'b', 'c']
    
concat()
      
      concat(obs, obsB)
    
      
      12357'a''b''c'
    
      
      arr.concat(arrB)
    
      
      [1,2,3,5,7,'a','b','c']
    
filter()
      
      obs.pipe(filter((v) => v>3))
    
      
      57
    
      
      arr.filter((v) => v>3)
    
      
      [5, 7]
    
find()
      
      obs.pipe(find((v) => v>3))
    
      
      5
    
      
      arr.find((v) => v>3)
    
      
      5
    
findIndex()
      
      obs.pipe(findIndex((v) => v>3))
    
      
      3
    
      
      arr.findIndex((v) => v>3)
    
      
      3
    
forEach()
      
      obs.pipe(tap((v) => { 
  console.log(v); 
})) 
1 
2 
3 
5 
7
    
      
      arr.forEach((v) => { 
  console.log(v); 
}) 
1 
2 
3 
5 
7
    
map()
      
      obs.pipe(map((v) => -v))
    
      
      →-1→-2→-3→-5→-7
    
      
      arr.map((v) => -v)
    
      
      [-1, -2, -3, -5, -7]
    
reduce()
      
      obs.pipe(reduce((s,v)=> s+v, 0))
    
      
      18
    
      
      arr.reduce((s,v) => s+v, 0)
    
      
      18
    
Last reviewed on Mon Feb 28 2022
Read article