TypeScript to JavaScript

Anything you can do with Angular in TypeScript, you can also do in JavaScript. Translating from one language to the other is mostly a matter of changing the way you organize your code and access Angular APIs.

TypeScript is a popular language option for Angular development. Most code examples on the Internet as well as on this site are written in TypeScript. This cookbook contains recipes for translating TypeScript code examples to ES6 and to ES5 so that JavaScript developers can read and write Angular apps in their preferred dialect.

Table of contents

TypeScript to ES6 to ES5
Modularity: imports and exports
Classes and Class Metadata
ES5 DSL
Interfaces
Input and Output Metadata
Dependency Injection
Host Binding
View and Child Decorators
AOT compilation in TypeScript Only

Run and compare the live TypeScript and JavaScript code shown in this cookbook.

TypeScript to ES6 to ES5

TypeScript is a typed superset of ES6 JavaScript.   ES6 JavaScript is a superset of ES5 JavaScript.   ES5 is the kind of JavaScript that runs natively in all modern browsers. The transformation of TypeScript code all the way down to ES5 code can be seen as "shedding" features.

The downgrade progression is

When translating from TypeScript to ES6-with-decorators, remove class property access modifiers such as public and private. Remove most of the type declarations, such as :string and :boolean but keep the constructor parameter types which are used for dependency injection.

From ES6-with-decorators to plain ES6, remove all decorators and the remaining types. You must declare properties in the class constructor (this.title = '...') rather than in the body of the class.

Finally, from plain ES6 to ES5, the main missing features are import statements and class declarations.

For plain ES6 transpilation you can start with a setup similar to the TypeScript quickstart and adjust the application code accordingly. Transpile with Babel using the es2015 preset. To use decorators and annotations with Babel, install the angular2 preset as well.

Importing and Exporting

Importing Angular Code

In both TypeScript and ES6, you import Angular classes, functions, and other members with ES6 import statements.

In ES5, you access the Angular entities of the the Angular packages through the global ng object. Anything you can import from @angular is a nested member of this ng object:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { LocationStrategy, HashLocationStrategy } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { LocationStrategy, HashLocationStrategy } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { LocationStrategy, HashLocationStrategy } from '@angular/common'; var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; var LocationStrategy = ng.common.LocationStrategy; var HashLocationStrategy = ng.common.HashLocationStrategy;

Exporting Application Code

Each file in a TypeScript or ES6 Angular application constitutes an ES6 module. When you want to make something available to other modules, you export it.

ES5 lacks native support for modules. In an Angular ES5 application, you load each file manually by adding a <script> tag to index.html.

The order of <script> tags is often significant. You must load a file that defines a public JavaScript entity before a file that references that entity.

The best practice in ES5 is to create a form of modularity that avoids polluting the global scope. Add one application namespace object such as app to the global document. Then each code file "exports" public entities by attaching them to that namespace object, e.g., app.HeroComponent. You could factor a large application into several sub-namespaces which leads to "exports" along the lines of app.heroQueries.HeroComponent.

Every ES5 file should wrap code in an Immediately Invoked Function Expression (IIFE) to limit unintentional leaking of private symbols into the global scope.

Here is a HeroComponent as it might be defined and "exported" in each of the four language variants.

export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } export class HeroComponent { constructor() { this.title = 'Hero Detail'; } getName() {return 'Windstorm'; } } app.HeroComponent = HeroComponent; // "export" HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) ]; function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.prototype.getName = function() { return 'Windstorm'; };

Importing Application Code

In TypeScript and ES6 apps, you import things that have been exported from other modules.

In ES5 you use the shared namespace object to access "exported" entities from other files.

import { HeroComponent } from './hero.component'; import { HeroComponent } from './hero.component'; import { HeroComponent } from './hero.component'; var HeroComponent = app.HeroComponent;

Alternatively, you can use a module loader such as Webpack or Browserify in an Angular JavaScript project. In such a project, you would use CommonJS modules and the require function to load Angular framework code. Then use module.exports and require to export and import application code.

Classes and Class Metadata

Classes

Most Angular TypeScript and ES6 code is written as classes.

Properties and method parameters of TypeScript classes may be marked with the access modifiers private, internal, and public. Remove these modifiers when translating to JavaScript.

Most type declarations (e.g, :string and :boolean) should be removed when translating to JavaScript. When translating to ES6-with-decorators, do not remove types from constructor parameters!

Look for types in TypeScript property declarations. In general it is better to initialize such properties with default values because many browser JavaScript engines can generate more performant code. When TypeScript code follows this same advice, it can infer the property types and there is nothing to remove during translation.

In ES6-without-decorators, properties of classes must be assigned inside the constructor.

ES5 JavaScript has no classes. Use the constructor function pattern instead, adding methods to the prototype.

export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } export class HeroComponent { constructor() { this.title = 'Hero Detail'; } getName() {return 'Windstorm'; } } function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.prototype.getName = function() { return 'Windstorm'; };

Metadata

When writing in TypeScript or ES6-with-decorators, provide configuration and metadata by adorning a class with one or more decorators. For example, you supply metadata to a component class by preceding its definition with a @Component decorator function whose argument is an object literal with metadata properties.

In plain ES6, you provide metadata by attaching an annotations array to the class. Each item in the array is a new instance of a metadata decorator created with a similar metadata object literal.

In ES5, you also provide an annotations array but you attach it to the constructor function rather than to a class.

See these variations side-by-side:

import { Component } from '@angular/core'; @Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } import { Component } from '@angular/core'; @Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } import { Component } from '@angular/core'; export class HeroComponent { constructor() { this.title = 'Hero Detail'; } getName() {return 'Windstorm'; } } HeroComponent.annotations = [ new Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) ]; app.HeroComponent = HeroComponent; // "export" HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) ]; function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.prototype.getName = function() { return 'Windstorm'; };

External Template file

A large component template is often kept in a separate template file.

app/hero-title.component.html

<h1>{{titlePrefix}} {{title}}</h1> <button (click)="ok()">OK</button> <p>{{ msg }}</p>

The component (HeroTitleComponent in this case) then references the template file in its metadata templateUrl property:

@Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) @Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) HeroTitleComponent.annotations = [ new Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) ]; HeroTitleComponent.annotations = [ new ng.core.Component({ selector: 'hero-title', templateUrl: 'app/hero-title.component.html' }) ];

Note that the TypeScript and both ES6 templateUrl properties identify the location of the template file relative to the component module. All three metadata configurations specify the moduleId property so that Angular can calculate the proper module address.

The ES5 approach shown here does not support modules and therefore there is no way to calculate a module-relative URL. The templateUrl for the ES5 code must specify the path from the project root and omits the irrelevant moduleId property.

With the right tooling, the moduleId may not be needed in the other JavaScript dialects either. But it's safest to provide it anyway.

ES5 DSL

This ES5 pattern of creating a constructor and annotating it with metadata is so common that Angular provides a convenience API to make it a little more compact and locates the metadata above the constructor, as you would if you wrote in TypeScript or ES6-with-decorators.

This API (Application Programming Interface) is commonly known as the ES5 DSL (Domain Specific Language).

Set an application namespace property (e.g., app.HeroDslComponent) to the result of an ng.core.Component function call. Pass the same metadata object to ng.core.Component as you did before. Then chain a call to the Class method which takes an object defining the class constructor and instance methods.

Here is an example of the HeroComponent, re-written with the DSL, next to the original ES5 version for comparison:

app.HeroComponent = ng.core.Component({ selector: 'hero-view-dsl', template: '<h1>{{title}}: {{getName()}}</h1>', }) .Class({ constructor: function HeroComponent() { this.title = "Hero Detail"; }, getName: function() { return 'Windstorm'; } }); app.HeroComponent = HeroComponent; // "export" HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-view', template: '<h1>{{title}}: {{getName()}}</h1>' }) ]; function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.prototype.getName = function() { return 'Windstorm'; };
Name the constructor

A named constructor displays clearly in the console log if the component throws a runtime error. An unnamed constructor displays as an anonymous function (e.g., class0) which is impossible to find in the source code.

Properties with getters and setters

TypeScript and ES6 support with getters and setters. Here's an example of a read-only TypeScript property with a getter that prepares a toggle-button label for the next clicked state:

ts/app/hero-queries.component.ts

get buttonLabel() { return this.active ? 'Deactivate' : 'Activate'; }

This TypeScript "getter" property is transpiled to an ES5 defined property. The ES5 DSL does not support defined properties directly but you can still create them by extracting the "class" prototype and adding the defined property in raw JavaScript like this:

js/app/hero-queries.component.ts

// add prototype property w/ getter outside the DSL var proto = app.heroQueries.HeroQueriesComponent.prototype; Object.defineProperty(proto, "buttonLabel", { get: function () { return this.active ? 'Deactivate' : 'Activate'; }, enumerable: true });

DSL for other classes

There are similar DSLs for other decorated classes. You can define a directive with ng.core.Directive:

app.MyDirective = ng.core.Directive({ selector: '[myDirective]' }).Class({ ... });

and a pipe with ng.core.Pipe:

app.MyPipe = ng.core.Pipe({ name: 'myPipe' }).Class({ ... });

Interfaces

A TypeScript interface helps ensure that a class implements the interface's members correctly. We strongly recommend Angular interfaces where appropriate. For example, the component class that implements the ngOnInit lifecycle hook method should implement the OnInit interface.

TypeScript interfaces exist for developer convenience and are not used by Angular at runtime. They have no physical manifestation in the generated JavaScript code. Just implement the methods and ignore interfaces when translating code samples from TypeScript to JavaScript.

import { Component, OnInit } from '@angular/core'; @Component({ selector: 'hero-lifecycle', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent implements OnInit { name: string; ngOnInit() { // todo: fetch from server async setTimeout(() => this.name = 'Windstorm', 0); } } import { Component } from '@angular/core'; @Component({ selector: 'hero-lifecycle', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent { name = ''; ngOnInit() { // todo: fetch from server async setTimeout(() => this.name = 'Windstorm', 0); } } import { Component } from '@angular/core'; export class HeroComponent { ngOnInit() { // todo: fetch from server async setTimeout(() => this.name = 'Windstorm', 0); } } HeroComponent.annotations = [ new Component({ selector: 'hero-lifecycle', template: `<h1>Hero: {{name}}</h1>` }) ]; app.HeroComponent = HeroComponent; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-lifecycle', template: '<h1>Hero: {{name}}</h1>' }) ]; function HeroComponent() { } HeroComponent.prototype.ngOnInit = function() { // todo: fetch from server async setTimeout(() => this.name = 'Windstorm', 0); }; app.HeroComponent = ng.core.Component({ selector: 'hero-lifecycle-dsl', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: function HeroComponent() { }, ngOnInit: function() { // todo: fetch from server async setTimeout(() => this.name = 'Windstorm', 0); } });

Input and Output Metadata

Input and Output Decorators

In TypeScript and ES6-with-decorators, you often add metadata to class properties with property decorators. For example, you apply @Input and @Output property decorators to public class properties that will be the target of data binding expressions in parent components.

There is no equivalent of a property decorator in ES5 or plain ES6. Fortunately, every property decorator has an equivalent representation in a class decorator metadata property. A TypeScript @Input property decorator can be represented by an item in the Component metadata's inputs array.

You already know how to add Component or Directive class metadata in any JavaScript dialect so there's nothing fundamentally new about adding another property. But note that what would have been separate @Input and @Output property decorators for each class property are combined in the metadata inputs and outputs arrays.

@Component({ moduleId: module.id, selector: 'app-confirm', templateUrl: 'confirm.component.html' }) export class ConfirmComponent { @Input() okMsg = ''; @Input('cancelMsg') notOkMsg = ''; @Output() ok = new EventEmitter(); @Output('cancel') notOk = new EventEmitter(); onOkClick() { this.ok.emit(true); } onNotOkClick() { this.notOk.emit(true); } } @Component({ moduleId: module.id, selector: 'app-confirm', templateUrl: 'confirm.component.html' }) export class ConfirmComponent { @Input() okMsg = ''; @Input('cancelMsg') notOkMsg = ''; @Output() ok = new EventEmitter(); @Output('cancel') notOk = new EventEmitter(); onOkClick() { this.ok.emit(true); } onNotOkClick() { this.notOk.emit(true); } } export class ConfirmComponent { constructor(){ this.ok = new EventEmitter(); this.notOk = new EventEmitter(); } onOkClick() { this.ok.emit(true); } onNotOkClick() { this.notOk.emit(true); } } ConfirmComponent.annotations = [ new Component({ moduleId: module.id, selector: 'app-confirm', templateUrl: 'confirm.component.html', inputs: [ 'okMsg', 'notOkMsg: cancelMsg' ], outputs: [ 'ok', 'notOk: cancel' ] }) ]; app.ConfirmComponent = ConfirmComponent; ConfirmComponent.annotations = [ new ng.core.Component({ selector: 'app-confirm', templateUrl: 'app/confirm.component.html', inputs: [ 'okMsg', 'notOkMsg: cancelMsg' ], outputs: [ 'ok', 'notOk: cancel' ] }) ]; function ConfirmComponent() { this.ok = new ng.core.EventEmitter(); this.notOk = new ng.core.EventEmitter(); } ConfirmComponent.prototype.onOkClick = function() { this.ok.emit(true); } ConfirmComponent.prototype.onNotOkClick = function() { this.notOk.emit(true); } app.ConfirmComponent = ng.core.Component({ selector: 'app-confirm-dsl', templateUrl: 'app/confirm.component.html', inputs: [ 'okMsg', 'notOkMsg: cancelMsg' ], outputs: [ 'ok', 'notOk: cancel' ] }) .Class({ constructor: function ConfirmComponent() { this.ok = new ng.core.EventEmitter(); this.notOk = new ng.core.EventEmitter(); }, onOkClick: function() { this.ok.emit(true); }, onNotOkClick: function() { this.notOk.emit(true); } });

In the previous example, one of the public-facing binding names (cancelMsg) differs from the corresponding class property name (notOkMsg). That's OK but you must tell Angular about it so that it can map an external binding of cancelMsg to the component's notOkMsg property.

In TypeScript and ES6-with-decorators, you specify the special binding name in the argument to the property decorator.

In ES5 and plain ES6 code, convey this pairing with the propertyName: bindingName syntax in the class metadata.

Dependency Injection

Angular relies heavily on Dependency Injection to provide services to the objects it creates. When Angular creates a new component, directive, pipe or another service, it sets the class constructor parameters to instances of services provided by an Injector.

The developer must tell Angular what to inject into each parameter.

Injection by Class Type

The easiest and most popular technique in TypeScript and ES6-with-decorators is to set the constructor parameter type to the class associated with the service to inject.

The TypeScript transpiler writes parameter type information into the generated JavaScript. Angular reads that information at runtime and locates the corresponding service in the appropriate Injector.. The ES6-with-decorators transpiler does essentially the same thing using the same parameter-typing syntax.

ES5 and plain ES6 lack types so you must identify "injectables" by attaching a parameters array to the constructor function. Each item in the array specifies the service's injection token.

As with TypeScript the most popular token is a class, or rather a constructor function that represents a class in ES5 and plain ES6. The format of the parameters array varies:

When writing with ES5 DSL, set the Class.constructor property to an array whose first parameters are the injectable constructor functions and whose last parameter is the class constructor itself. This format should be familiar to Angular 1 developers.

import { Component } from '@angular/core'; import { DataService } from './data.service'; @Component({ selector: 'hero-di', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent { name = ''; constructor(dataService: DataService) { this.name = dataService.getHeroName(); } } import { Component } from '@angular/core'; import { DataService } from './data.service'; @Component({ selector: 'hero-di', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent { name = ''; constructor(dataService: DataService) { this.name = dataService.getHeroName(); } } import { Component } from '@angular/core'; import { DataService } from './data.service'; export class HeroComponent { constructor(dataService) { this.name = dataService.getHeroName(); } } HeroComponent.annotations = [ new Component({ selector: 'hero-di', template: `<h1>Hero: {{name}}</h1>` }) ]; HeroComponent.parameters = [ [DataService] ]; app.HeroComponent = HeroComponent; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-di', template: '<h1>Hero: {{name}}</h1>' }) ]; HeroComponent.parameters = [ app.DataService ]; function HeroComponent(dataService) { this.name = dataService.getHeroName(); } app.HeroComponent = ng.core.Component({ selector: 'hero-di-dsl', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: [ app.DataService, function HeroComponent(service) { this.name = service.getHeroName(); } ] });

Injection with the @Inject decorator

Sometimes the dependency injection token isn't a class or constructor function.

In TypeScript and ES6-with-decorators, you precede the class constructor parameter by calling the @Inject() decorator with the injection token. In the following example, the token is the string 'heroName'.

The other JavaScript dialects add a parameters array to the class contructor function. Each item constains a new instance of Inject:

When writing with ES5 DSL, set the Class.constructor property to a function definition array as before. Create a new instance of ng.core.Inject(token) for each parameter.

@Component({ selector: 'hero-di-inject', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent { constructor(@Inject('heroName') private name: string) { } } @Component({ selector: 'hero-di-inject', template: `<h1>Hero: {{name}}</h1>` }) export class HeroComponent { constructor(@Inject('heroName') name) { this.name = name; } } export class HeroComponent { constructor(name) { this.name = name; } } HeroComponent.annotations = [ new Component({ selector: 'hero-di-inject', template: `<h1>Hero: {{name}}</h1>` }) ]; HeroComponent.parameters = [ [new Inject('heroName')] ]; app.HeroComponent = HeroComponent; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-di-inject', template: '<h1>Hero: {{name}}</h1>' }) ]; HeroComponent.parameters = [ 'heroName' ]; function HeroComponent(name) { this.name = name; } app.HeroComponent = ng.core.Component({ selector: 'hero-di-inject-dsl', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: [ new ng.core.Inject('heroName'), function HeroComponent(name) { this.name = name; } ] });

Additional Injection Decorators

You can qualify injection behavior with injection decorators from @angular/core.

In TypeScript and ES6-with-decorators, you precede the constructor parameters with injection qualifiers such as:

In plain ES6 and ES5, create an instance of the equivalent injection qualifier in a nested array within the parameters array. For example, you'd write new Optional() in plain ES6 and new ng.core.Optional() in ES5.

When writing with ES5 DSL, set the Class.constructor property to a function definition array as before. Use a nested array to define a parameter's complete injection specification.

@Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) export class HeroTitleComponent { msg: string = ''; constructor( @Inject('titlePrefix') @Optional() private titlePrefix: string, @Attribute('title') private title: string ) { } ok() { this.msg = 'OK!'; } } @Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) export class HeroTitleComponent { msg = ''; constructor( @Inject('titlePrefix') @Optional() titlePrefix, @Attribute('title') title ) { this.titlePrefix = titlePrefix; this.title = title; } ok() { this.msg = 'OK!'; } } export class HeroTitleComponent { constructor(titlePrefix, title) { this.titlePrefix = titlePrefix; this.title = title; this.msg = ''; } ok() { this.msg = 'OK!'; } } HeroTitleComponent.annotations = [ new Component({ moduleId: module.id, selector: 'hero-title', templateUrl: 'hero-title.component.html' }) ]; HeroTitleComponent.parameters = [ [new Optional(), new Inject('titlePrefix')], [new Attribute('title')] ]; app.HeroTitleComponent = HeroTitleComponent; HeroTitleComponent.annotations = [ new ng.core.Component({ selector: 'hero-title', templateUrl: 'app/hero-title.component.html' }) ]; function HeroTitleComponent(titlePrefix, title) { this.titlePrefix = titlePrefix; this.title = title; this.msg = ''; } HeroTitleComponent.prototype.ok = function() { this.msg = 'OK!'; } HeroTitleComponent.parameters = [ [new ng.core.Optional(), new ng.core.Inject('titlePrefix')], [new ng.core.Attribute('title')] ]; app.HeroTitleComponent = ng.core.Component({ selector: 'hero-title-dsl', templateUrl: 'app/hero-title.component.html' }) .Class({ constructor: [ [ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ], new ng.core.Attribute('title'), function HeroTitleComponent(titlePrefix, title) { this.titlePrefix = titlePrefix; this.title = title; this.msg = ''; } ], ok: function() { this.msg = 'OK!'; } });

In the example above, there is no provider for the 'titlePrefix' token. Without Optional, Angular would raise an error. With Optional, Angular sets the constructor parameter to null and the component displays the title without a prefix.

Host Binding

Angular supports bindings to properties and events of the host element which is the element whose tag matches the component selector.

Host Decorators

In TypeScript and ES6-with-decorators, you can use host property decorators to bind a host element to a component or directive. The @HostBinding decorator binds host element properties to component data properties. The @HostListener decorator binds host element events to component event handlers.

In plain ES6 or ES5, add a host attribute to the component metadata to achieve the same effect as @HostBinding and @HostListener.

The host value is an object whose properties are host property and listener bindings:

@Component({ selector: 'hero-host', template: ` <h1 [class.active]="active">Hero Host in Decorators</h1> <div>Heading clicks: {{clicks}}</div> `, // Styles within (but excluding) the <hero-host> element styles: ['.active {background-color: yellow;}'] }) export class HeroHostComponent { // HostBindings to the <hero-host> element @HostBinding() title = 'Hero Host in Decorators Tooltip'; @HostBinding('class.heading') headingClass = true; active = false; clicks = 0; // HostListeners on the entire <hero-host> element @HostListener('click') clicked() { this.clicks += 1; } @HostListener('mouseenter', ['$event']) enter(event: Event) { this.active = true; this.headingClass = false; } @HostListener('mouseleave', ['$event']) leave(event: Event) { this.active = false; this.headingClass = true; } } @Component({ selector: 'hero-host', template: ` <h1 [class.active]="active">Hero Host in Decorators</h1> <div>Heading clicks: {{clicks}}</div> `, // Styles within (but excluding) the <hero-host> element styles: ['.active {background-color: yellow;}'] }) export class HeroHostComponent { // HostBindings to the <hero-host> element @HostBinding() title = 'Hero Host in Decorators Tooltip'; @HostBinding('class.heading') headingClass = true; active = false; clicks = 0; // HostListeners on the entire <hero-host> element @HostListener('click') clicked() { this.clicks += 1; } @HostListener('mouseenter', ['$event']) enter(event: Event) { this.active = true; this.headingClass = false; } @HostListener('mouseleave', ['$event']) leave(event: Event) { this.active = false; this.headingClass = true; } } export class HeroHostComponent { constructor() { this.active = false; this.clicks = 0; this.headingClass = true; this.title = 'Hero Host Tooltip'; } clicked() { this.clicks += 1; } enter(event) { this.active = true; this.headingClass = false; } leave(event) { this.active = false; this.headingClass = true; } } HeroHostComponent.annotations = [ new Component({ selector: 'hero-host', template: ` <h1 [class.active]="active">Hero Host</h1> <div>Heading clicks: {{clicks}}</div> `, host: { // HostBindings to the <hero-host> element '[title]': 'title', '[class.heading]': 'headingClass', '(click)': 'clicked()', // HostListeners on the entire <hero-host> element '(mouseenter)': 'enter($event)', '(mouseleave)': 'leave($event)' }, // Styles within (but excluding) the <hero-host> element styles: ['.active {background-color: yellow;}'] }) ]; app.HeroComponent = HeroComponent; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-host', template: '<h1 [class.active]="active">Hero Host</h1>' + '<div>Heading clicks: {{clicks}}</div>', host: { // HostBindings to the <hero-host> element '[title]': 'title', '[class.heading]': 'headingClass', '(click)': 'clicked()', // HostListeners on the entire <hero-host> element '(mouseenter)': 'enter($event)', '(mouseleave)': 'leave($event)' }, // Styles within (but excluding) the <hero-host> element styles: ['.active {background-color: yellow;}'] }) ]; function HeroComponent() { this.clicks = 0; this.headingClass = true; this.title = 'Hero Host Tooltip content'; } HeroComponent.prototype.clicked = function() { this.clicks += 1; } HeroComponent.prototype.enter = function(event) { this.active = true; this.headingClass = false; } HeroComponent.prototype.leave = function(event) { this.active = false; this.headingClass = true; } app.HeroComponent = ng.core.Component({ selector: 'hero-host-dsl', template: ` <h1 [class.active]="active">Hero Host (DSL)</h1> <div>Heading clicks: {{clicks}}</div> `, host: { // HostBindings to the <hero-host-dsl> element '[title]': 'title', '[class.heading]': 'headingClass', '(click)': 'clicked()', // HostListeners on the entire <hero-host-dsl> element '(mouseenter)': 'enter($event)', '(mouseleave)': 'leave($event)' }, // Styles within (but excluding) the <hero-host-dsl> element styles: ['.active {background-color: coral;}'] }) .Class({ constructor: function HeroComponent() { this.clicks = 0; this.headingClass = true; this.title = 'Hero Host Tooltip DSL content'; }, clicked() { this.clicks += 1; }, enter(event) { this.active = true; this.headingClass = false; }, leave(event) { this.active = false; this.headingClass = true; } });

Host Metadata

Some developers prefer to specify host properties and listeners in the component metadata. They'd rather do it the way you must do it ES5 and plain ES6.

The following re-implementation of the HeroComponent reminds us that any property metadata decorator can be expressed as component or directive metadata in both TypeScript and ES6-with-decorators. These particular TypeScript and ES6 code snippets happen to be identical.

@Component({ selector: 'hero-host-meta', template: ` <h1 [class.active]="active">Hero Host in Metadata</h1> <div>Heading clicks: {{clicks}}</div> `, host: { // HostBindings to the <hero-host-meta> element '[title]': 'title', '[class.heading]': 'headingClass', // HostListeners on the entire <hero-host-meta> element '(click)': 'clicked()', '(mouseenter)': 'enter($event)', '(mouseleave)': 'leave($event)' }, // Styles within (but excluding) the <hero-host-meta> element styles: ['.active {background-color: coral;}'] }) export class HeroHostMetaComponent { title = 'Hero Host in Metadata Tooltip'; headingClass = true; active = false; clicks = 0; clicked() { this.clicks += 1; } enter(event: Event) { this.active = true; this.headingClass = false; } leave(event: Event) { this.active = false; this.headingClass = true; } } @Component({ selector: 'hero-host-meta', template: ` <h1 [class.active]="active">Hero Host in Metadata</h1> <div>Heading clicks: {{clicks}}</div> `, host: { // HostBindings to the <hero-host-meta> element '[title]': 'title', '[class.heading]': 'headingClass', // HostListeners on the entire <hero-host-meta> element '(click)': 'clicked()', '(mouseenter)': 'enter($event)', '(mouseleave)': 'leave($event)' }, // Styles within (but excluding) the <hero-host-meta> element styles: ['.active {background-color: coral;}'] }) export class HeroHostMetaComponent { title = 'Hero Host in Metadata Tooltip'; headingClass = true; active = false; clicks = 0; clicked() { this.clicks += 1; } enter(event: Event) { this.active = true; this.headingClass = false; } leave(event: Event) { this.active = false; this.headingClass = true; } }

View and Child Decorators

Several property decorators query a component's nested view and content components.

View children are associated with element tags that appear within the component's template.

Content children are associated with elements that appear between the component's element tags; they are projected into an <ng-content> slot in the component's template.

The @ViewChild and @ViewChildren property decorators allow a component to query instances of other components that are used in its view.

In ES5 and ES6, you access a component's view children by adding a queries property to the component metadata. The queries property value is a hash map.

@Component({ selector: 'hero-queries', template: ` <view-child *ngFor="let hero of heroData" [hero]="hero"> <content-child></content-child> </view-child> <button (click)="activate()">{{buttonLabel}} All</button> ` }) export class HeroQueriesComponent { active = false; heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'LaughingGas'} ]; @ViewChildren(ViewChildComponent) views: QueryList<ViewChildComponent>; activate() { this.active = !this.active; this.views.forEach( view => view.activate() ); } get buttonLabel() { return this.active ? 'Deactivate' : 'Activate'; } } @Component({ selector: 'hero-queries', template: ` <view-child *ngFor="let hero of heroData" [hero]="hero"> <content-child></content-child> </view-child> <button (click)="activate()">{{buttonLabel}} All</button> ` }) export class HeroQueriesComponent { active = false; heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'LaughingGas'} ]; @ViewChildren(ViewChildComponent) views; activate() { this.active = !this.active; this.views.forEach( view => view.activate() ); } get buttonLabel() { return this.active ? 'Deactivate' : 'Activate'; } } export class HeroQueriesComponent { constructor(){ this.active = false; this.heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'LaughingGas'} ]; } activate() { this.active = !this.active; this.views.forEach( view => view.activate() ); } get buttonLabel() { return this.active ? 'Deactivate' : 'Activate'; } } HeroQueriesComponent.annotations = [ new Component({ selector: 'hero-queries', template: ` <view-child *ngFor="let hero of heroData" [hero]="hero"> <content-child></content-child> </view-child> <button (click)="activate()">{{buttonLabel}} All</button> `, queries: { views: new ViewChildren(ViewChildComponent) } }) ]; app.heroQueries.HeroQueriesComponent = ng.core.Component({ selector: 'hero-queries', template: '<view-child *ngFor="let hero of heroData" [hero]="hero">' + '<content-child></content-child>' + '</view-child>' + '<button (click)="activate()">{{buttonLabel}} All</button>', queries: { views: new ng.core.ViewChildren(app.heroQueries.ViewChildComponent) } }) .Class({ constructor: function HeroQueriesComponent() { this.active = false; this.heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'LaughingGas'} ]; }, activate: function() { this.active = !this.active; this.views.forEach(function(view) { view.activate(); }); }, }); // add prototype property w/ getter outside the DSL var proto = app.heroQueries.HeroQueriesComponent.prototype; Object.defineProperty(proto, "buttonLabel", { get: function () { return this.active ? 'Deactivate' : 'Activate'; }, enumerable: true });

The @ContentChild and @ContentChildren property decorators allow a component to query instances of other components that have been projected into its view from elsewhere.

They can be added in the same way as @ViewChild and @ViewChildren.

@Component({ selector: 'view-child', template: ` <h2 [class.active]=active> {{hero.name}} <ng-content></ng-content> </h2>`, styles: ['.active {font-weight: bold; background-color: skyblue;}'] }) export class ViewChildComponent { @Input() hero: any; active = false; @ContentChild(ContentChildComponent) content: ContentChildComponent; activate() { this.active = !this.active; this.content.activate(); } } @Component({ selector: 'view-child', template: ` <h2 [class.active]=active> {{hero.name}} <ng-content></ng-content> </h2>`, styles: ['.active {font-weight: bold; background-color: skyblue;}'] }) export class ViewChildComponent { @Input() hero; active = false; @ContentChild(ContentChildComponent) content; activate() { this.active = !this.active; this.content.activate(); } } export class ViewChildComponent { constructor() { this.active = false; } activate() { this.active = !this.active; this.content.activate(); } } ViewChildComponent.annotations = [ new Component({ selector: 'view-child', template: `<h2 [class.active]=active> {{hero.name}} <ng-content></ng-content> </h2>`, styles: ['.active {font-weight: bold; background-color: skyblue;}'], inputs: ['hero'], queries: { content: new ContentChild(ContentChildComponent) } }) ]; app.heroQueries.ViewChildComponent = ng.core.Component({ selector: 'view-child', template: '<h2 [class.active]=active>' + '{{hero.name}} ' + '<ng-content></ng-content>' + '</h2>', styles: ['.active {font-weight: bold; background-color: skyblue;}'], inputs: ['hero'], queries: { content: new ng.core.ContentChild(app.heroQueries.ContentChildComponent) } }) .Class({ constructor: function HeroQueriesHeroComponent() { this.active = false; }, activate: function() { this.active = !this.active; this.content.activate(); } });

In TypeScript and ES6-with-decorators you can also use the queries metadata instead of the @ViewChild and @ContentChild property decorators.

AOT Compilation in TypeScript only

Angular offers two modes of template compilation, JIT (Just-in-Time) and AOT (Ahead-of-Time). Currently the AOT compiler only works with TypeScript applications because, in part, it generates TypeScript files as an intermediate result. AOT is not an option for pure JavaScript applications at this time.