Injection by Type
Angular 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 Tokens
Since no type information is available in ES5 JavaScript, we must identify "injectables" in some other way.
We attach a parameters array to the constructor function. Each array item is the dependency injection token that identifies the thing to be injected. Often the token is the constructor function for the class-like dependency.
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 decorator
When the thing being injected doesn't correspond directly to a type, we use the @Inject() decorator to supply the injection token.
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 tokens
In 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 Inject method and add that to the constructor array in the annotations like this:
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 Decorators
We can attach additional decorators to constructor parameters to qualify the injection behavior. We can mark optional dependencies with the @Optional, inject host element attributes with @Attribute, inject content child queries with @Query and inject view child queries with @ViewQuery).
@Component({
selector: 'hero-title',
template: `
<h1>{{titlePrefix}} {{title}}</h1>
<button (click)="ok()">OK</button>
<ng-content></ng-content>
`
})
class TitleComponent {
constructor(
@Inject('titlePrefix')
@Optional()
private titlePrefix: string,
@Attribute('title')
private title: string,
@Query('okMsg')
private msg: QueryList<ElementRef>) {
}
ok() {
let msgEl =
this.msg.first.nativeElement;
msgEl.textContent = 'OK!';
}
}
|
Additional Injection Metadata with Nested Arrays
To 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 Attribute, Host, Optional, Self, SkipSelf, Query and ViewQuery to qualify dependency injection behavior.
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>' +
'<ng-content></ng-content>'
}).Class({
constructor: [
[
new ng.core.Optional(),
new ng.core.Inject('titlePrefix')
],
new ng.core.Attribute('title'),
[
new ng.core.Query('okMsg'),
ng.core.ElementRef
],
function(titlePrefix, title, msg) {
this.titlePrefix = titlePrefix;
this.title = title;
this.msg = msg;
}
],
ok: function() {
var msgEl =
this.msg.first.nativeElement;
msgEl.textContent = 'OK!';
}
});
We can apply other additional parameter decorators such as @Host and @SkipSelf in the same way - by adding new ng.core.Host() or ng.core.SkipSelf() in the parameters array.
|
Angular 2 - TypeScript to JavaScript
Angular by Google
TypeScript to JavaScript
Convert Angular 2 TypeScript examples into ES5 JavaScript
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
Classes and Class Metadata
Input and Output Metadata
Dependency Injection
Host and Query Metadata
Run and compare the live TypeScript and JavaScript code shown in this cookbook.
Importing and Exporting
Importing Angular 2 Code
In TypeScript code, Angular 2 classes, functions, and other members are imported with TypeScript
importstatements:import { bootstrap } from '@angular/platform-browser-dynamic'; import { LocationStrategy, HashLocationStrategy } from '@angular/common';Accessing Angular 2 Code through the ng global
In JavaScript code, when using the Angular 2 packages, we can access Angular code through the global
ngobject. In the nested members of this object we'll find everything we would import fromangular2in TypeScript:var bootstrap = ng.platformBrowserDynamic.bootstrap; var LocationStrategy = ng.common.LocationStrategy; var HashLocationStrategy = ng.common.HashLocationStrategy;Importing and Exporting Application Code
Each 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
exportit.export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } }In other modules we can then
importthings that have been exported elsewhere.import { HeroComponent } from './hero.component';Sharing Application Code
In an Angular 2 JavaScript application, we load each file to the page using a
<script>tag. Each file can make things available to other files via the shared globalwindowscope.We often introduce an application namespace object (such as
"app") ontowindowand attach everything we need to share to that namespace object. We also wrap our code in an Immediately Invoked Function Expression (IIFE). These practices together prevent our code from polluting the global scope.(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
<script>tags on the page is significant. We must load a file that defines a shared member before a file that uses that member.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
requirefunction to load Angular 2 framework code. We would then usemodule.exportsandrequireto export and import application code.Classes and Class Metadata
Classes
We put most of our Angular 2 TypeScript code into TypeScript classes.
export class HeroComponent { title = 'Hero Detail'; getName() {return 'Windstorm'; } }Constructors and Prototypes
ES5 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 Decorators
Most Angular 2 classes have one or more TypeScript decorators attached to provide configuration and metadata. For example, a component must have a
@Componentdecorator.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 Array
In JavaScript, we can attach an
annotationsarray to a constructor to provide metadata. Each item in the array corresponds to a TypeScript decorator.In the following example, we create a new instance of
Componentthat corresponds to the@ComponentTypeScript decorator.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 API
The 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
ng.core.Componentfunction, followed by a chainedClassmethod call. The argument toClassis an object that defines the constructor and the instance methods of the component: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({ ... });Interfaces
When 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
ngOnInitare one example of this pattern.ngOnInitis defined in theOnInitinterface.import { Component, OnInit } from '@angular/core'; class HeroComponent implements OnInit { name: string; ngOnInit() { this.name = 'Windstorm'; } }Implementing Methods without Interfaces
TypeScript 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
Input and Output Decorators
In TypeScript, property decorators are often used to provide additional metadata for components and directives.
For inputs and outputs, we use
@Inputand@Outputproperty decorators. They may optionally specify input and output binding names if we want them to be different from the class property names.@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
inputsandoutputsarray metadata instead of the@Inputand@Outputproperty decorators.Inputs and Outputs in Component Metadata
There is no equivalent of a property decorator in ES5 JavaScript. Instead, we add comparable information to the
Component(orDirective) metadata.In this example, we add
inputsandoutputsarray attributes containing the input and output property names. If we need a binding name that is different from the property itself, we use thepropertyName: bindingNamesyntax.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
Injection by Type
Angular 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 Tokens
Since no type information is available in ES5 JavaScript, we must identify "injectables" in some other way.
We attach a
parametersarray to the constructor function. Each array item is the dependency injection token that identifies the thing to be injected. Often the token is the constructor function for the class-like dependency.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 decorator
When the thing being injected doesn't correspond directly to a type, we use the
@Inject()decorator to supply the injection token.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 tokens
In 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
Injectmethod and add that to the constructor array in the annotations like this: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 Decorators
We can attach additional decorators to constructor parameters to qualify the injection behavior. We can mark optional dependencies with the
@Optional, inject host element attributes with@Attribute, inject content child queries with@Queryand inject view child queries with@ViewQuery).@Component({ selector: 'hero-title', template: ` <h1>{{titlePrefix}} {{title}}</h1> <button (click)="ok()">OK</button> <ng-content></ng-content> ` }) class TitleComponent { constructor( @Inject('titlePrefix') @Optional() private titlePrefix: string, @Attribute('title') private title: string, @Query('okMsg') private msg: QueryList<ElementRef>) { }ok() {let msgEl =
this.msg.first.nativeElement;
msgEl.textContent = 'OK!';
}
}
Additional Injection Metadata with Nested Arrays
To 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
Attribute,Host,Optional,Self,SkipSelf,QueryandViewQueryto qualify dependency injection behavior.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>' + '<ng-content></ng-content>' }).Class({ constructor: [ [ new ng.core.Optional(), new ng.core.Inject('titlePrefix') ], new ng.core.Attribute('title'), [ new ng.core.Query('okMsg'), ng.core.ElementRef ], function(titlePrefix, title, msg) { this.titlePrefix = titlePrefix; this.title = title; this.msg = msg; } ], ok: function() { var msgEl = this.msg.first.nativeElement; msgEl.textContent = 'OK!'; } });We can apply other additional parameter decorators such as
@Hostand@SkipSelfin the same way - by addingnew ng.core.Host()orng.core.SkipSelf()in the parameters array.Host and Query Metadata
Host Decorators
We can use host property decorators to bind a host element to a component or directive. The
@HostBindingdecorator binds host element properties to component data properties. The@HostListenerdecorator bimds host element events to component event handlers.@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
hostmetadata instead of the@HostBindingand@HostListenerproperty decorators.Host Metadata
We add a
hostattribute to the component metadata to achieve the same effect as@HostBindingand@HostListener.The
hostvalue is an object whose properties are host property and listener bindings:[property]for host bindings or(event)for host listeners.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 Decorators
There are several property decorators for querying the descendants of a component or directive.
The
@ViewChildand@ViewChildrenproperty decorators allow a component to query instances of other components that are used in its view.@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
@ContentChildand@ContentChildrenproperty decorators allow a component to query instances of other components that have been projected into its view from elsewhere.@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
queriesmetadata instead of the@ViewChildand@ContentChildproperty decorators.Query Metadata
We access a component's view children by adding a
queriesattribute to the component metadata. It should be an object where:ViewChildorViewChildren.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
queriesattribute in the same manner, using instances ofContentChildorContentChildren: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;RESOURCES
HELP
COMMUNITY
LANGUAGES
.
Tags: js, #biketrooper-dev, typescript
August 16, 2016 at 10:35AM
Open in Evernote