从 TypeScript 到 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.

在 Angular 中,TypeScript 可以做的任何事,也可以用 JavaScript 实现。 将一种语言翻译成另一种语言,主要是改变了组织代码和访问 Angular API 的方式。

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.

TypeScript 在 Angular 开发中比较流行。 互联网上和本网站中的大多数范例都是用 TypeScript 写的。

Table of contents

目录

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

运行在线例子,比较 TypeScript 版和 JavaScript 版的代码。

TypeScript to ES6 to ES5

TypeScriptES6ES5

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.

TypeScript ES6 JavaScript 类型化的超集ES6 JavaScriptES5 JavaScript 的超集。ES5 是可以在所有现代浏览器中运行的 JavaScript。

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.

TypeScript 翻译到 带装饰器的 ES6 时,移除了类属性访问修饰符,如publicprivate。 移除了大部分的类型声明,如:string:boolean。 但保留了用于依赖注入的构造函数参数类型

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.

带装饰器的 ES6 翻译到普通 ES6 时,移除了所有的装饰器和剩下的类型。 必须在构造函数中声明属性(this.title = '...'),而不是在类的代码体中。

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

最后,普通 ES6 翻译成 ES5,缺少的主要特性是importclass声明。

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.

普通 ES6 的翻译,可以从类似 TypeScript 快速开始的设置开始, 调整相应代码。然后用 Babel 进行转译,使用es2015预设值。 要在 Babel 中使用装饰器和注释,还需安装angular2预设值。

Importing and Exporting

导入和导出

Importing Angular Code

导入 Angular 代码

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

TypeScriptES6 中,可以使用 ES6 import语句导入 Angular 类、函数和其它成员。

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:

ES5 中,通过全局ng对象访问 Angular 包中的 Angular 实体。 凡是可以从@angular导入的,都是该ng对象的嵌套成员。

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.

TypeScriptES6 Angular 应用中每个文件都构成一个 ES6 模块。 当想要让某个东西对其它模块可用时,就export它。

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

ES5 不支持模块。在 Angular ES5 应用中,需要在index.html中添加<script>标签,手工加载每个文件。

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.

<script>标签的顺序通常很重要。 必须在引用实体的文件之前,加载定义该公共 JavaScript 实体的文件。

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.

ES5 中,最佳实践是,创建某种形式的模块化,避免污染全局作用域。 添加一个应用命名空间对象(如app)到全局的document。 接着,每个代码文件都通过附加到该命名空间来“导出”公共实体,例如,app.HeroComponent。 可以把一个大型应用中分解成多个子命名空间,可以象这样进行“导出”,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.

每个 ES5 文件都应包裹在立即调用函数表达式 (IIFE) 中, 防止把私有符号无意地泄漏到全局作用域。

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

下面是HeroComponent定义和“导出”的四种不同语言变种。

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.

TypeScriptES6 应用中,可以导入 (import) 其它模块已导出的东西。

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

ES5 中,使用共享的命名空间对象访问其它文件“导出”的实体。

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.

还可以在 Angular JavaScript 项目中使用模块加载器,如 Webpack 或 Browserify。 在这样的项目中,使用 CommonJS 模块和require函数来加载 Angular 框架代码。 用module.exportsrequire导入和导出应用代码。

Classes and Class Metadata

类和类的元数据

Classes

Most Angular TypeScript and ES6 code is written as classes.

大多数 Angular TypeScriptES6 代码是写成了类。

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.

TypeScript 类的属性和方法参数可以用访问修饰符privateinternalpublic标记。 当翻译成 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!

当翻译成 JavaScript 时,移除大多数类型声明(如,:string:boolean)。 当翻译成带装饰器的 ES6 时,不移除构造函数参数类型!

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.

看一下 TypeScript 属性声明中的类型。通常,最好用缺省值初始化这些属性,因为许多浏览器的 JavaScript 引擎可生成更高性能的代码。当 TypeScript 代码遵循这一建议时,它可以推导出属性类型,翻译时就不需要移除任何内容。

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

不带装饰器的 ES6 中,类的属性必须在构造函数中指定。

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

ES5 JavaScript 没有类。 使用构造函数模式,把方法添加到 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.

当用 TypeScript带装饰器的 ES6 编写代码时,使用一个或多个装饰器 (decorator) 来修饰类, 提供配置和元数据。

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.

普通 ES6 中,通过向附加一个annotations数组来提供元数据。

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

ES5中,也是提供一个annotations数组,但把它附加到构造函数,而不是类。

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:

接着,组件(这里是HeroTitleComponent)在它的元数据templateUrl属性中引用该模板文件:

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

注意,TypeScript 和两个ES6templateUrl属性相对于组件模块来标识模板文件的位置。

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.

这里的 ES5 不支持模块,因此无法计算出相对于模块的URLES5 代码中的templateUrl必须指定从项目根目录开始的路径,并且省略无关的moduleId属性。

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

如果使用正确的工具链,其它的 JavaScript 方言中也可能不需要moduleId。 但写上去是最安全的。

ES5 DSL

ES5 领域专用语言

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.

创建构造函数并用元数据对它进行注释,是一个常见的 ES5 模式,Angular 提供了一套方便的 API,使代码更简洁, 并且元数据也刚好位于构造函数的上方,看起来就像 TypeScript带装饰器的 ES6 写的代码。

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

这个 API (Application Programming Interface,应用编程接口) 通常称作 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.

ng.core.Component函数调用的结果设置到应用命名空间属性,如app.HeroDslComponent。 向ng.core.Component传递与之前一样的元数据对象。 接着,在调用链上调用Class函数,它接收一个对象,其中定义了类的构造函数和实例方法。

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

下例中的HeroComponent,用 DSL 进行了重写,跟原来的 ES5 版本进行对比一下:

app.HeroDslComponent = ng.core.Component({ selector: 'hero-view-dsl', template: '<h1>{{title}}: {{getName()}}</h1>', }) .Class({ constructor: function HeroDslComponent() { 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.

如果组件抛出运行时异常,命名的构造函数在控制台日志中显示得更清楚。 未命名的构造函数显示为匿名函数(如,class0),不可能在源代码中找到它。

Properties with getters and setters

具有 getter 和 setter 的属性

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:

TypeScriptES6 支持 getter 和 setter。 下面是 TypeScript 只读属性的例子,它有一个 getter,为下一次点击状态准备切换按钮的标签:

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:

这个 TypeScript "getter" 属性会翻译成 ES5 已定义属性ES5 DSL 不直接支持已定义属性,你仍可提取"类"原型,象下面这样添加已定义属性

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

用于其它类的 DSL

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

其它被装饰的类也有类似的DSL,可以用ng.core.Directive定义指令:

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

and a pipe with ng.core.Pipe:

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用于确保一个类正确地实现了接口成员。 在适当的地方,我们强烈推荐使用 Angular 接口。 例如,实现了ngOnInit生命周期钩子方法的组件类应实现OnInit接口。

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.

TypeScript 接口只是为了方便开发人员,Angular 在运行时并不使用它。 它们在生成的 JavaScript中并不存在。 当从 TypeScript 翻译成 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.HeroLifecycleComponent = 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.HeroLifecycleDslComponent = ng.core.Component({ selector: 'hero-lifecycle-dsl', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: function HeroLifecycleDslComponent() { }, 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.

TypeScript带装饰器的 ES6 中,经常会用属性装饰器往类的属性上添加元数据。 例如,向公共类属性添加@Input@Output属性装饰器 , 会使这些属性成为父组件绑定表达式的目标。

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.

ES5普通 ES6 中,没有等价的属性装饰器。 幸运的是,每个属性装饰器在类的装饰器元数据属性中有等价的表示形式。 TypeScript@Input属性装饰器可以表示为Component元数据的inputs数组中的一项。

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.

你已经知道如何用任意的 JavaScript 方言添加ComponentDirective元数据, 所以添加另一个属性也没什么新鲜的。 但要注意的是,用于每个类属性的那些分离@Input@Output属性装饰器,都合并到了inputsoutputs数组中。

@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.ConfirmDslComponent = ng.core.Component({ selector: 'app-confirm-dsl', templateUrl: 'app/confirm.component.html', inputs: [ 'okMsg', 'notOkMsg: cancelMsg' ], outputs: [ 'ok', 'notOk: cancel' ] }) .Class({ constructor: function ConfirmDslComponent() { 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.

上例中,其中一个面向公共的绑定名 (cancelMsg),不同于相应的类属性名 (notOkMsg)。 这样做没有问题,但必须把它告诉 Angular,这样 Angular 才能把cancelMsg的外部绑定映射到组件的notOkMsg属性。

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

TypeScript带装饰器的 ES6 中,在属性装饰器的参数中指定特定的绑定名。

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

ES5普通 ES6 中,用propertyName: bindingName语法表示在类的元数据中。

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.

Angular 严重依赖依赖注入来为它创建的对象提供服务。 当 Angular 创建一个新组件、指令、管道或其它服务时, 它把注入器提供的服务的实例传递给类的构造函数参数。

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

开发人员必须告诉 Angular 向每个参数中注入什么。

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.

TypeScript带装饰器的 ES6 中,最简单和流行的技术是把构造函数参数的类型设置为待注入服务的类。

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.

TypeScript 转译器把参数类型信息写进生成的 JavaScript。 Angular 在运行时读取该信息,并在适当的注入器中定位相应的服务。 带装饰器的 ES6 转译器本质上也使用同样的参数类型语法,做同样的工作。

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.

ES5普通 ES6 缺少类型,必须向构造函数附加parameters数组来标识“可注入对象”。 数组中的每一项指定一个服务的注入令牌。

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:

TypeScript 中,最常用的令牌是类,而ES5普通 ES6 使用构造函数表示一个类。 因此,parameters数组会有所不同:

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.

当用 ES5 DSL 时,把Class.constructor属性设置为一个数组,它的前面的参数是 注入的服务,最后一个参数是类构造函数本身。 Angular 1 的开发人员对这种形式应该很熟悉。

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

Injection with the @Inject decorator

用 @Inject 装饰器注入

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

TypeScript带装饰器的 ES6 中,可以在类的构造函数参数前调用@Inject()装饰器来指定注入令牌。

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

其它 JavaScript 方言是通过向类的构造函数添加parameters数组。 其中的每一项是Inject的实例。

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

当用 ES5 DSL 时,象前面那样把Class.constructor属性设置为函数定义数组。 为每个参数创建一个ng.core.Inject(token)

@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.HeroDIInjectComponent = HeroDIInjectComponent; HeroDIInjectComponent.annotations = [ new ng.core.Component({ selector: 'hero-di-inject', template: '<h1>Hero: {{name}}</h1>' }) ]; HeroDIInjectComponent.parameters = [ 'heroName' ]; function HeroDIInjectComponent(name) { this.name = name; } app.HeroDIInjectDslComponent = ng.core.Component({ selector: 'hero-di-inject-dsl', template: '<h1>Hero: {{name}}</h1>' }) .Class({ constructor: [ new ng.core.Inject('heroName'), function HeroDIInjectDslComponent(name) { this.name = name; } ] });

Additional Injection Decorators

其它注入装饰器

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

可以使用@angular/core中的注入装饰器来限定注入行为。

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

TypeScript带装饰器的 ES6 中,可以将下列注入限定符加在构造函数参数前面:

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.

ES5普通 ES6 中,通过在parameters数组中创建一个嵌套数组,创建等价的注入限定符实例。

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.

当用 ES5 DSL 时,象前面那样把Class.constructor属性设置为函数定义数组。 用嵌套数组来定义参数完整的注入规格说明。

@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.HeroTitleDslComponent = 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 HeroTitleDslComponent(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.

上例中,'titlePrefix'令牌没有提供商。 如果没有Optional,Angular 将抛出错误。 加上Optional,Angular 将构造函数参数设置为null, 组件显示没有前缀的标题。

Host Binding

宿主绑定

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

Angular 支持绑定到宿主元素的属性和事件, 宿主元素是那些标签匹配组件选择器的元素。

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.

TypeScript带装饰器的 ES6 中,可以使用宿主属性装饰器把宿主元素绑定到组件或指令。 @HostBinding装饰器把宿主元素属性绑定到组件数据属性。 @HostListener装饰器把宿主元素事件绑定到组件事件处理器。

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

ES5普通 ES6 中,向组件元数据添加host属性可以获得同样的效果。

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

host的值是一个对象,它的属性是宿主属性和监听器绑定:

@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.HeroHostComponent = HeroHostComponent; HeroHostComponent.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 HeroHostComponent() { this.clicks = 0; this.headingClass = true; this.title = 'Hero Host Tooltip content'; } HeroHostComponent.prototype.clicked = function() { this.clicks += 1; } HeroHostComponent.prototype.enter = function(event) { this.active = true; this.headingClass = false; } HeroHostComponent.prototype.leave = function(event) { this.active = false; this.headingClass = true; } app.HeroHostDslComponent = 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 HeroHostDslComponent() { 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.

一些开发人员更喜欢在组件元数据中指定宿主属性和监听器。 它们宁愿采用这种方式,也是 ES5普通 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.

下面重新实现了HeroComponent,它提醒我们,在 TypeScript带装饰器的 ES6 中, 任何属性元数据装饰器都可以表示为组件或指令元数据。

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

内容子组件与出现在组件元素标签之间的那些元素相关联, 它们被投影到组件模板的<ng-content>中。

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

@ViewChild@ViewChildren 属性装饰器允许组件查询位于其视图中的其它组件的实例。

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.

ES5ES6 中,通过向组件元数据添加queries属性来访问组件的视图子组件。 queries属性是一个映射表。

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

@ContentChild@ContentChildren 装饰器允许组件查询从其它地方投影到视图里的其它组件的实例。

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

添加它们的方式与@ViewChild@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.

TypeScript带装饰器的 ES6 中,还可以使用queries元数据代替 @ViewChild@ContentChild属性装饰器。

AoT Compilation in TypeScript only

只用于 TypeScript 的预编译

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.

Angular 模板编译有两种方式:JiT (Just-in-Time,即时编译) 和 AoT (Ahead-of-Time,预编译)。 目前,预编译只能用于 TypeScript 应用,因为(部分原因)它生成的中间结果是 TypeScript 文件。 当前,预编译不能用于纯 JavaScript 应用