You are on page 1of 23

11/20/2016 TypeScript 

to JavaScript ­ ts ­ COOKBOOK

TYPESCRIPT TO JAVASCRIPT

Convert Angular TypeScript examples into ES6 and ES5 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
https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 1/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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

TypeScript to ES6-with-decorators
ES6-with-decorators to ES6-without-decorators ("plain ES6")
ES6-without-decorators to ES5

When translating from TypeScript to ES6-with-decorators, remove class property


access modi拱㵑ers 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.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 2/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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';

Exporting Application Code


Each 拱㵑le 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
拱㵑le manually by adding a <script> tag to index.html .

The order of <script> tags is often signi拱㵑cant. You must load a 拱㵑le that
de拱㵑nes a public JavaScript entity before a 拱㵑le that references that entity.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 3/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 拱㵑le "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 拱㵑le 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 de拱㵑ned and "exported" in each of the four


language variants.

1. export class HeroComponent {


2. title = 'Hero Detail';
3. getName() {return 'Windstorm'; }
4. }

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 拱㵑les.

import { HeroComponent } from './hero.component';

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 4/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 modi拱㵑ers private , internal , and public . Remove these modi拱㵑ers
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.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 5/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

1. export class HeroComponent {


2. title = 'Hero Detail';
3. getName() {return 'Windstorm'; }
4. }

Metadata
When writing in TypeScript or ES6-with-decorators, provide con拱㵑guration and
metadata by adorning a class with one or more decorators. For example, you supply
metadata to a component class by preceding its de拱㵑nition 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:

1. import { Component } from '@angular/core';


2.

3. @Component({
4. selector: 'hero-view',
5. template: '<h1>{{title}}: {{getName()}}</h1>'
6. })
7. export class HeroComponent {
8. title = 'Hero Detail';
9. getName() {return 'Windstorm'; }
10. }

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 6/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

External Template 拱㵑le

A large component template is often kept in a separate template 拱㵑le.

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


拱㵑le in its metadata templateUrl property:

@Component({
moduleId: module.id,
selector: 'hero-title',
templateUrl: 'hero-title.component.html'
})

Note that the TypeScript and both ES6 templateUrl properties identify the
location of the template 拱㵑le relative to the component module. All three metadata
con拱㵑gurations 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.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 7/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 Speci拱㵑c 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 de拱㵑ning 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:

1. app.HeroDslComponent = ng.core.Component({
2. selector: 'hero-view-dsl',
3. template: '<h1>{{title}}: {{getName()}}</h1>',
4. })
5. .Class({
6. constructor: function HeroDslComponent() {
7. this.title = "Hero Detail";
8. },
9.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 8/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

10. getName: function() { return 'Windstorm'; }


11. });

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 拱㵑nd 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 de拱㵑ned property. The ES5
DSL does not support de拱㵑ned properties directly but you can still create them by
extracting the "class" prototype and adding the de拱㵑ned 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;

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 9/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 de拱㵑ne 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.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 10/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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.

1. import { Component, OnInit } from '@angular/core';


2.

3. @Component({
4. selector: 'hero-lifecycle',
5. template: `<h1>Hero: {{name}}</h1>`
6. })
7. export class HeroComponent implements OnInit {
8. name: string;
9. ngOnInit() {
10. // todo: fetch from server async
11. setTimeout(() => this.name = 'Windstorm', 0);
12. }
13. }

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.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 11/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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.

1. @Component({
2. moduleId: module.id,
3. selector: 'app-confirm',
4. templateUrl: 'confirm.component.html'
5. })
6. export class ConfirmComponent {
7. @Input() okMsg = '';
8. @Input('cancelMsg') notOkMsg = '';
9. @Output() ok = new EventEmitter();
10. @Output('cancel') notOk = new EventEmitter();
11.

12. onOkClick() {
13. this.ok.emit(true);
14. }
15. onNotOkClick() {
16. this.notOk.emit(true);
17. }
18. }

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

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 12/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 speci拱㵑es the

service's injection token.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 13/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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:

plain ES6 — nest each constructor function in a sub-array.

ES5 — simply list the constructor functions.

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

1. @Component({
2. selector: 'hero-di',
3. template: `<h1>Hero: {{name}}</h1>`
4. })
5. export class HeroComponent {
6. name = '';
7. constructor(dataService: DataService) {
8. this.name = dataService.getHeroName();
9. }
10. }

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' .

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 14/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

The other JavaScript dialogs add a parameters array to the class contructor
function. Each item constains a new instance of Inject('heroName') :

plain ES6 — each item is a new instance of Inject(token) in a sub-array.

ES5 — simply list the string tokens.

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

1. @Component({
2. selector: 'hero-di-inject',
3. template: `<h1>Hero: {{name}}</h1>`
4. })
5. export class HeroComponent {
6. constructor(@Inject('heroName') private name: string) { }
7. }

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 quali拱㵑ers such as:

@Optional sets the parameter to null if the service is missing

@Attribute to inject a host element attribute value

@ContentChild to inject a content child

@ViewChild to inject a view child

@Host to inject a service in this component or its host

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 15/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

@SkipSelf to inject a service provided in an ancestor of this component

In plain ES6 and ES5, create an instance of the equivalent injection quali拱㵑er 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
de拱㵑nition array as before. Use a nested array to de拱㵑ne a parameter's complete
injection speci拱㵑cation.

1. @Component({
2. moduleId: module.id,
3. selector: 'hero-title',
4. templateUrl: 'hero-title.component.html'
5. })
6. export class HeroTitleComponent {
7. msg: string = '';
8. constructor(
9. @Inject('titlePrefix') @Optional() private titlePrefix: string,
10. @Attribute('title') private title: string
11. ) { }
12.

13. ok() {
14. this.msg = 'OK!';
15. }
16. }

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 pre拱㵑x.
https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 16/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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:

Each key follows regular Angular binding syntax: [property] for host
bindings or (event) for host listeners.
Each value identi拱㵑es the corresponding component property or method.

1. @Component({
2. selector: 'hero-host',
3. template: `
4. <h1 [class.active]="active">Hero Host in Decorators</h1>
5. <div>Heading clicks: {{clicks}}</div>
6. `,
7. // Styles within (but excluding) the <hero-host> element
8. styles: ['.active {background-color: yellow;}']
https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 17/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

9. })
10. export class HeroHostComponent {
11. // HostBindings to the <hero-host> element
12. @HostBinding() title = 'Hero Host in Decorators Tooltip';
13. @HostBinding('class.heading') headingClass = true;
14.

15. active = false;


16. clicks = 0;
17.

18. // HostListeners on the entire <hero-host> element


19. @HostListener('click')
20. clicked() {
21. this.clicks += 1;
22. }
23.

24. @HostListener('mouseenter', ['$event'])


25. enter(event: Event) {
26. this.active = true;
27. this.headingClass = false;
28. }
29.

30. @HostListener('mouseleave', ['$event'])


31. leave(event: Event) {
32. this.active = false;
33. this.headingClass = true;
34. }
35. }

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

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 18/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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.

1. @Component({
2. selector: 'hero-host-meta',
3. template: `
4. <h1 [class.active]="active">Hero Host in Metadata</h1>
5. <div>Heading clicks: {{clicks}}</div>
6. `,
7. host: {
8. // HostBindings to the <hero-host-meta> element
9. '[title]': 'title',
10. '[class.heading]': 'headingClass',
11.

12. // HostListeners on the entire <hero-host-meta> element


13. '(click)': 'clicked()',
14. '(mouseenter)': 'enter($event)',
15. '(mouseleave)': 'leave($event)'
16. },
17. // Styles within (but excluding) the <hero-host-meta> element
18. styles: ['.active {background-color: coral;}']
19. })
20. export class HeroHostMetaComponent {
21. title = 'Hero Host in Metadata Tooltip';
22. headingClass = true;
23.

24. active = false;


25. clicks = 0;
26.

27. clicked() {
28. this.clicks += 1;
29. }
30.

31. enter(event: Event) {


32. this.active = true;

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 19/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

33. this.headingClass = false;


34. }
35.

36. leave(event: Event) {


37. this.active = false;
38. this.headingClass = true;
39. }
40. }

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.

each key is the name of a component property that will hold the view child or
children.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 20/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

each value is a new instance of either ViewChild or ViewChildren .

1. @Component({
2. selector: 'hero-queries',
3. template: `
4. <view-child *ngFor="let hero of heroData" [hero]="hero">
5. <content-child></content-child>
6. </view-child>
7. <button (click)="activate()">{{buttonLabel}} All</button>
8. `
9. })
10. export class HeroQueriesComponent {
11. active = false;
12. heroData = [
13. {id: 1, name: 'Windstorm'},
14. {id: 2, name: 'LaughingGas'}
15. ];
16.

17. @ViewChildren(ViewChildComponent) views:


QueryList<ViewChildComponent>;
18.

19. activate() {
20. this.active = !this.active;
21. this.views.forEach(
22. view => view.activate()
23. );
24. }
25.

26. get buttonLabel() {


27. return this.active ? 'Deactivate' : 'Activate';
28. }
29. }

The @ContentChild and @ContentChildren property decorators allow a


https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 21/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 .

1. @Component({
2. selector: 'view-child',
3. template: `
4. <h2 [class.active]=active>
5. {{hero.name}}
6. <ng-content></ng-content>
7. </h2>`,
8. styles: ['.active {font-weight: bold; background-color:
skyblue;}']
9. })
10. export class ViewChildComponent {
11. @Input() hero: any;
12. active = false;
13.

14. @ContentChild(ContentChildComponent) content:


ContentChildComponent;
15.

16. activate() {
17. this.active = !this.active;
18. this.content.activate();
19. }
20. }

In TypeScript and ES6-with-decorators you can also use the queries


metadata instead of the @ViewChild and @ContentChild property
decorators.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 22/23
11/20/2016 TypeScript to JavaScript ­ ts ­ COOKBOOK

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 拱㵑les as an intermediate result. AoT is not
an option for pure JavaScript applications at this time.

https://angular.io/docs/ts/latest/cookbook/ts­to­js.html 23/23

You might also like