Everything that we can do in Angular 2 in TypeScript, we can also do in JavaScript. Translating from one language to the other is mostly a matter of changing the way we organize our code and the way we access Angular 2 APIs.
Since TypeScript is a popular language option in Angular 2, many of the code examples you see on the Internet as well as on this site are written in TypeScript. This cookbook contains recipes for translating these kinds of code examples to ES5, so that they can be applied to Angular 2 JavaScript applications.
Table of contents
Modularity: imports and exports
Run and compare the live TypeScript and JavaScript code shown in this cookbook.
Importing and Exporting
TypeScript | ES5 JavaScript |
---|---|
Importing Angular 2 CodeIn TypeScript code, Angular 2 classes, functions, and other members are imported with TypeScript import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { LocationStrategy, HashLocationStrategy } from '@angular/common'; |
Accessing Angular 2 Code through the ng globalIn JavaScript code, when using the Angular 2 packages, we can access Angular code through the global var platformBrowserDynamic = ng.platformBrowserDynamic.platformBrowserDynamic; var LocationStrategy = ng.common.LocationStrategy; var HashLocationStrategy = ng.common.HashLocationStrategy; |
Importing and Exporting Application CodeEach file in an Angular 2 TypeScript application constitutes a TypeScript module. When we want to make something from a module available to other modules, we export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } In other modules we can then import { HeroComponent } from './hero.component'; |
Sharing Application CodeIn an Angular 2 JavaScript application, we load each file to the page using a We often introduce an application namespace object (such as (function(app) { function HeroComponent() { this.title = "Hero Detail"; } app.HeroComponent = HeroComponent; })(window.app = window.app || {}); We can then access anything from this shared namespace in other files. (function(app) { var HeroComponent = app.HeroComponent; })(window.app = window.app || {}); Note that the order of |
Alternatively, we can use a module loader such as Webpack or Browserify in an Angular 2 JavaScript project. In such a project, we would use CommonJS modules and the require
function to load Angular 2 framework code. We would then use module.exports
and require
to export and import application code.
Classes and Class Metadata
TypeScript | ES5 JavaScript |
---|---|
ClassesWe put most of our Angular 2 TypeScript code into TypeScript classes. export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } |
Constructors and PrototypesES5 JavaScript has no classes. We use the constructor pattern instead which works with Angular 2 as well as classes do. function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.prototype.getName = function() {return 'Windstorm';}; |
Metadata with DecoratorsMost Angular 2 classes have one or more TypeScript decorators attached to provide configuration and metadata. For example, a component must have a import { Component } from '@angular/core'; @Component({ selector: 'hero-view', template: '<h1>Hero: {{getName()}}</h1>' }) export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } } |
Metadata with the Annotations ArrayIn JavaScript, we can attach an In the following example, we create a new instance of function HeroComponent() { this.title = "Hero Detail"; } HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-view', template: '<h1>Hero: {{getName()}}</h1>' }) ]; HeroComponent.prototype.getName = function() {return 'Windstorm';}; Metadata with The Class Convenience APIThe pattern of creating a constructor and decorating it with metadata is so common that Angular provides an alternative convenience API for it. This API lets us define everything in a single expression. With this API we first call the var HeroComponent = ng.core.Component({ selector: 'hero-view-2', template: '<h1>Name: {{getName()}}</h1>', }) .Class({ constructor: function() { }, getName: function() { return 'Windstorm'; } }); Similar APIs are also available for other decorators. You can define a directive: var MyDirective = ng.core.Directive({ ... }).Class({ ... }); Or a pipe: var MyPipe = ng.core.Pipe({ name: 'myPipe' }).Class({ ... }); |
InterfacesWhen defining classes that need to implement a certain method, it is common to use TypeScript interfaces that enforce that the method signature is correct. Component lifecycle methods like import { Component, OnInit } from '@angular/core'; class HeroComponent implements OnInit { name: string; ngOnInit() { this.name = 'Windstorm'; } } |
Implementing Methods without InterfacesTypeScript interfaces are purely for developer convenience and are not used by Angular 2 at runtime. This means that in JavaScript code we don't need to substitute anything for interfaces. We can just implement the methods. function HeroComponent() {} HeroComponent.prototype.ngOnInit = function() { this.name = 'Windstorm'; }; |
Input and Output Metadata
TypeScript | ES5 JavaScript |
---|---|
Input and Output DecoratorsIn TypeScript, property decorators are often used to provide additional metadata for components and directives. For inputs and outputs, we use @Component({ selector: 'my-confirm', template: ` <button (click)="onOkClick()"> {{okMsg}} </button> <button (click)="onNotOkClick()"> {{notOkMsg}} </button> ` }) class ConfirmComponent { @Input() okMsg: string; @Input('cancelMsg') notOkMsg: string; @Output() ok = new EventEmitter(); @Output('cancel') notOk = new EventEmitter(); onOkClick() { this.ok.next(true); } onNotOkClick() { this.notOk.next(true); } } In TypeScript we can also use the |
Inputs and Outputs in Component MetadataThere is no equivalent of a property decorator in ES5 JavaScript. Instead, we add comparable information to the In this example, we add var ConfirmComponent = ng.core.Component({ selector: 'my-confirm', inputs: [ 'okMsg', 'notOkMsg: cancelMsg' ], outputs: [ 'ok', 'notOk: cancel' ], template: '<button (click)="onOkClick()">' + '{{okMsg}}' + '</button>' + '<button (click)="onNotOkClick()">' + '{{notOkMsg}}' + '</button>' }).Class({ constructor: function() { this.ok = new ng.core.EventEmitter(); this.notOk = new ng.core.EventEmitter(); }, onOkClick: function() { this.ok.next(true); }, onNotOkClick: function() { this.notOk.next(true); } }); |
Dependency Injection
TypeScript | ES5 JavaScript |
---|---|
Injection by TypeAngular 2 can often use TypeScript type information to determine what needs to be injected. @Component({ selector: 'hero-di', template: `<h1>Hero: {{name}}</h1>` }) class HeroComponent { name: string; constructor(dataService: DataService) { this.name = dataService.getHeroName(); } } |
Injection with Parameter TokensSince no type information is available in ES5 JavaScript, we must identify "injectables" in some other way. We attach a app.HeroDIComponent = HeroComponent; function HeroComponent(dataService) { this.name = dataService.getHeroName(); } HeroComponent.parameters = [ app.DataService ]; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-di', template: '<h1>Hero: {{name}}</h1>' }) ]; When using the class convenience API, we can also supply the parameter tokens by wrapping the constructor in an array. var HeroComponent = ng.core.Component({ selector: 'hero-di-inline', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: [app.DataService, function(service) { this.name = service.getHeroName(); }] }); |
Injection with the @Inject decoratorWhen the thing being injected doesn't correspond directly to a type, we use the In this example, we're injecting a string identified by the "heroName" token. @Component({ selector: 'hero-di-inject', template: `<h1>Hero: {{name}}</h1>` }) class HeroComponent { constructor( @Inject('heroName') private name: string) { } } |
Injection with plain string tokensIn JavaScript we add the token string to the injection parameters array. function HeroComponent(name) { this.name = name; } HeroComponent.parameters = [ 'heroName' ]; HeroComponent.annotations = [ new ng.core.Component({ selector: 'hero-di-inject', template: '<h1>Hero: {{name}}</h1>' }) ]; Alternatively, we can create a token with the var HeroComponent = ng.core.Component({ selector: 'hero-di-inline2', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: [new ng.core.Inject('heroName'), function(name) { this.name = name; }] }); |
Additional Injection DecoratorsWe can attach additional decorators to constructor parameters to qualify the injection behavior. We can mark optional dependencies with the @Component({ selector: 'hero-title', template: ` <h1>{{titlePrefix}} {{title}}</h1> <button (click)="ok()">OK</button> <p>{{ msg }}</p> ` }) class TitleComponent { private msg: string = ''; constructor( @Inject('titlePrefix') @Optional() private titlePrefix: string, @Attribute('title') private title: string) { } ok() { this.msg = 'OK!'; } } |
Additional Injection Metadata with Nested ArraysTo achieve the same effect in JavaScript, use the constructor array notation in which the injection information precedes the constructor function itself. Use the injection support functions Use a nested array to combine injection functions. var TitleComponent = ng.core.Component({ selector: 'hero-title', template: '<h1>{{titlePrefix}} {{title}}</h1>' + '<button (click)="ok()">OK</button>' + '<p>{{ msg }}</p>' }).Class({ constructor: [ [ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ], new ng.core.Attribute('title'), function(titlePrefix, title) { this.titlePrefix = titlePrefix; this.title = title; this.msg = ''; } ], ok: function() { this.msg = 'OK!'; } }); We can apply other additional parameter decorators such as |
Host and Query Metadata
TypeScript | ES5 JavaScript |
---|---|
Host DecoratorsWe can use host property decorators to bind a host element to a component or directive. The @Component({ selector: 'heroes-bindings', template: `<h1 [class.active]="active"> Tour of Heroes </h1>` }) class HeroesComponent { @HostBinding() title = 'Tooltip content'; @HostBinding('class.heading') hClass = true; active: boolean; constructor() {} @HostListener('click') clicked() { this.active = !this.active; } @HostListener('dblclick', ['$event']) doubleClicked(evt: Event) { this.active = true; } } In TypeScript we can also use |
Host MetadataWe add a The
var HeroesComponent = ng.core.Component({ selector: 'heroes-bindings', template: '<h1 [class.active]="active">' + 'Tour of Heroes' + '</h1>', host: { '[title]': 'title', '[class.heading]': 'hClass', '(click)': 'clicked()', '(dblclick)': 'doubleClicked($event)' } }).Class({ constructor: function() { this.title = 'Tooltip content'; this.hClass = true; }, clicked: function() { this.active = !this.active; }, doubleClicked: function(evt) { this.active = true; } }); |
Query DecoratorsThere are several property decorators for querying the descendants of a component or directive. The @Component({ selector: 'heroes-queries', template: ` <a-hero *ngFor="let hero of heroData" [hero]="hero"> <active-label></active-label> </a-hero> <button (click)="activate()"> Activate </button> ` }) class HeroesQueriesComponent { heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'Superman'} ]; @ViewChildren(HeroComponent) heroCmps: QueryList<HeroComponent>; activate() { this.heroCmps.forEach( (cmp) => cmp.activate() ); } } The @Component({ selector: 'a-hero', template: `<h2 [class.active]=active> {{hero.name}} <ng-content></ng-content> </h2>` }) class HeroComponent { @Input() hero: any; active: boolean; @ContentChild(ActiveLabelComponent) label: ActiveLabelComponent; activate() { this.active = true; this.label.activate(); } } In TypeScript we can also use the |
Query MetadataWe access a component's view children by adding a
var AppComponent = ng.core.Component({ selector: 'heroes-queries', template: '<a-hero *ngFor="let hero of heroData"' + '[hero]="hero">' + '<active-label></active-label>' + '</a-hero>' + '<button (click)="activate()">' + 'Activate' + '</button>', queries: { heroCmps: new ng.core.ViewChildren( HeroComponent) } }).Class({ constructor: function() { this.heroData = [ {id: 1, name: 'Windstorm'}, {id: 2, name: 'Superman'} ]; }, activate: function() { this.heroCmps.forEach(function(cmp) { cmp.activate(); }); } }); We add content child queries to the same var HeroComponent = ng.core.Component({ selector: 'a-hero', template: '<h2 [class.active]=active>' + '{{hero.name}} ' + '<ng-content></ng-content>' + '</h2>', inputs: ['hero'], queries: { label: new ng.core.ContentChild( ActiveLabelComponent) } }).Class({ constructor: [function() { }], activate: function() { this.active = true; this.label.activate(); } }); app.HeroQueriesComponent = HeroComponent; |
Please login to continue.