在 Angular 中,TypeScript 可以做的任何事,也可以用 JavaScript 实现。
将一种语言翻译成另一种语言,主要是改变了组织代码和访问 Angular API 的方式。
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.
TypeScriptis 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 降级到 带装饰器的 ES6
TypeScript to ES6-with-decorators
带装饰器的 ES6 降级到 没有装饰器的 ES6 (“普通 ES6”)
ES6-with-decorators to ES6-without-decorators ("plain ES6")
When translating from TypeScript to ES6-with-decorators, remove
class property access modifiers
such as public and private.
Remove most of the
type declarations,
such as :string and :boolean
but keep the constructor parameter types which are used for dependency injection.
From ES6-with-decorators to plain ES6, remove all
decorators
and the remaining types.
You must declare properties in the class constructor (this.title = '...') rather than in the body of the class.
最后,普通 ES6 翻译成 ES5,缺少的主要特性是import和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.
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:
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.
The order of <script> tags is often significant.
You must load a file that defines a public JavaScript entity before a file that references that entity.
The best practice in ES5 is to create a form of modularity that avoids polluting the global scope.
Add one application namespace object such as app to the global document.
Then each code file "exports" public entities by attaching them to that namespace object, e.g., app.HeroComponent.
You could factor a large application into several sub-namespaces
which leads to "exports" along the lines of app.heroQueries.HeroComponent.
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
大多数 Angular TypeScript 和 ES6 代码是写成了类。
Most Angular TypeScript and ES6 code is written as classes.
Properties and method parameters of TypeScript classes may be marked with the access modifiers
private, internal, and public.
Remove these modifiers when translating to JavaScript.
Most type declarations (e.g, :string and :boolean) should be removed when translating to JavaScript.
When translating to ES6-with-decorators, do not remove types from constructor parameters!
Look for types in TypeScript property declarations.
In general it is better to initialize such properties with default values because
many browser JavaScript engines can generate more performant code.
When TypeScript code follows this same advice, it can infer the property types
and there is nothing to remove during translation.
在不带装饰器的 ES6 中,类的属性必须在构造函数中指定。
In ES6-without-decorators, properties of classes must be assigned inside the constructor.
ES5 JavaScript 没有类。
使用构造函数模式,把方法添加到 prototype 中。
ES5 JavaScript has no classes.
Use the constructor function pattern instead, adding methods to the prototype.
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.
在普通 ES6 中,通过向类附加一个annotations数组来提供元数据。
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.
在ES5中,也是提供一个annotations数组,但把它附加到构造函数,而不是类。
In ES5, you also provide an annotations array but you attach it to the constructor function rather than to a class.
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.
这个 API (Application Programming Interface,应用编程接口) 通常称作 ES5 DSL (Domain Specific Language,领域专用语言)。
This API (Application Programming Interface) is commonly known as the ES5 DSL (Domain Specific Language).
Set an application namespace property (e.g., app.HeroDslComponent) to the result of an ng.core.Component function call.
Pass the same metadata object to ng.core.Component as you did before.
Then chain a call to the Class method which takes an object defining the class constructor and instance methods.
下例中的HeroComponent,用 DSL 进行了重写,跟原来的 ES5 版本进行对比一下:
Here is an example of the HeroComponent, re-written with the DSL,
next to the original ES5 version for comparison:
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.
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/src/app/hero-queries.component.ts
get buttonLabel(){returnthis.active ?'Deactivate':'Activate';}
This TypeScript "getter" property is transpiled to an ES5defined property.
The ES5 DSL does not support defined properties directly
but you can still create them by extracting the "class" prototype and
adding the defined property in raw JavaScript like this:
js/src/app/hero-queries.component.ts
// add prototype property w/ getter outside the DSLvar proto = app.heroQueries.HeroQueriesComponent.prototype;Object.defineProperty(proto,"buttonLabel",{get:function(){returnthis.active ?'Deactivate':'Activate';},
enumerable:true});
用于其它类的 DSL
DSL for other classes
其它被装饰的类也有类似的DSL,可以用ng.core.Directive定义指令:
There are similar DSLs for other decorated classes.
You can define a directive with ng.core.Directive:
A TypeScript interface helps ensure that a class implements the interface's members correctly.
We strongly recommend Angular interfaces where appropriate.
For example, the component class that implements the ngOnInit lifecycle hook method
should implement the OnInit interface.
TypeScript interfaces exist for developer convenience and are not used by Angular at runtime.
They have no physical manifestation in the generated JavaScript code.
Just implement the methods and ignore interfaces when translating code samples from TypeScript to JavaScript.
In TypeScript and ES6-with-decorators, you often add metadata to class properties with property decorators.
For example, you apply @Input and @Output property decorators
to public class properties that will be the target of data binding expressions in parent components.
There is no equivalent of a property decorator in ES5 or plain ES6.
Fortunately, every property decorator has an equivalent representation in a class decorator metadata property.
A TypeScript@Input property decorator can be represented by an item in the Component metadata's inputs array.
You already know how to add Component or Directive class metadata in any JavaScript dialect so
there's nothing fundamentally new about adding another property.
But note that what would have been separate@Input and @Output property decorators for each class property are
combined in the metadata inputs and outputsarrays.
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.
在 TypeScript 和 带装饰器的 ES6 中,在属性装饰器的参数中指定特定的绑定名。
In TypeScript and ES6-with-decorators,
you specify the special binding name in the argument to the property decorator.
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 向每个参数中注入什么。
The developer must tell Angular what to inject into each parameter.
The easiest and most popular technique in TypeScript and ES6-with-decorators is to set the constructor parameter type
to the class associated with the service to inject.
The TypeScript transpiler writes parameter type information into the generated JavaScript.
Angular reads that information at runtime and locates the corresponding service in the appropriate Injector..
The ES6-with-decorators transpiler does essentially the same thing using the same parameter-typing syntax.
ES5 and plain ES6 lack types so you must identify "injectables" by attaching a parameters array to the constructor function.
Each item in the array specifies the service's injection token.
As with TypeScript the most popular token is a class,
or rather a constructor function that represents a class in ES5 and plain ES6.
The format of the parameters array varies:
普通 ES6 — 函数构造嵌套在一个子数组中。
plain ES6 — nest each constructor function in a sub-array.
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 AngularJS developers.
import{Component}from'@angular/core';
import{DataService}from'./data.service';
@Component({
selector:'hero-di',
template:`<h1>Hero: {{name}}</h1>`
})
exportclassHeroComponent{
name ='';
constructor(dataService:DataService){
this.name = dataService.getHeroName();
}
}
import{Component}from'@angular/core';
import{DataService}from'./data.service';
@Component({
selector:'hero-di',
template:`<h1>Hero: {{name}}</h1>`
})
exportclassHeroComponent{
name ='';
constructor(dataService:DataService){
this.name = dataService.getHeroName();
}
}
import{Component}from'@angular/core';
import{DataService}from'./data.service';
exportclassHeroComponent{
constructor(dataService){
this.name = dataService.getHeroName();
}
}
HeroComponent.annotations =[
newComponent({
selector:'hero-di',
template:`<h1>Hero: {{name}}</h1>`
})
];
HeroComponent.parameters =[
[DataService]
];
app.HeroComponent=HeroComponent;
HeroComponent.annotations =[
new ng.core.Component({
selector:'hero-di',
template:'<h1>Hero: {{name}}</h1>'
})
];
HeroComponent.parameters =[ app.DataService];
functionHeroComponent(dataService){
this.name = dataService.getHeroName();
}
app.HeroComponent= ng.core.Component({
selector:'hero-di-dsl',
template:'<h1>Hero: {{name}}</h1>'
})
.Class({
constructor:[
app.DataService,
functionHeroComponent(service){
this.name = service.getHeroName();
}
]
});
用 @Inject 装饰器注入
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'.
When writing with ES5 DSL, set the Class.constructor property to a function definition
array as before. Create a new instance of ng.core.Inject(token) for each parameter.
In plain ES6 and ES5, create an instance of the equivalent injection qualifier in a nested array within the parameters array.
For example, you'd write new Optional() in plain ES6 and new ng.core.Optional() in ES5.
When writing with ES5 DSL, set the Class.constructor property to a function definition
array as before. Use a nested array to define a parameter's complete injection specification.
In the example above, there is no provider for the 'titlePrefix' token.
Without Optional, Angular would raise an error.
With Optional, Angular sets the constructor parameter to null
and the component displays the title without a prefix.
宿主绑定
Host Binding
Angular 支持绑定到宿主元素的属性和事件,
宿主元素是那些标签匹配组件选择器的元素。
Angular supports bindings to properties and events of the host element which is the
element whose tag matches the component selector.
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.
在ES5 或 普通 ES6 中,向组件元数据添加host属性可以获得同样的效果。
In plain ES6 or ES5, add a host attribute to the component metadata to achieve the
same effect as @HostBinding and @HostListener.
host的值是一个对象,它的属性是宿主属性和监听器绑定:
The host value is an object whose properties are host property and listener bindings:
Some developers prefer to specify host properties and listeners
in the component metadata.
They'd rather do it the way you must do it ES5 and plain ES6.
The following re-implementation of the HeroComponent reminds us that any property metadata decorator
can be expressed as component or directive metadata in both TypeScript and ES6-with-decorators.
These particular TypeScript and ES6 code snippets happen to be identical.
@Component({
selector:'hero-host-meta',
template:`
<h1 [class.active]="active">Hero Host in Metadata</h1>
<div>Heading clicks: {{clicks}}</div>
`,
host:{
// HostBindings to the <hero-host-meta> element
'[title]':'title',
'[class.heading]':'headingClass',
// HostListeners on the entire <hero-host-meta> element
'(click)':'clicked()',
'(mouseenter)':'enter($event)',
'(mouseleave)':'leave($event)'
},
// Styles within (but excluding) the <hero-host-meta> element
styles:['.active {background-color: coral;}']
})
exportclassHeroHostMetaComponent{
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;}']
})
exportclassHeroHostMetaComponent{
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.
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.
每个值是ViewChild或ViewChildren的实例。
each value is a new instance of either ViewChild or ViewChildren.
@Component({
selector:'hero-queries',
template:`
<view-child *ngFor="let hero of heroData" [hero]="hero">
The @ContentChild and
@ContentChildren property decorators
allow a component to query instances of other components that have been projected
into its view from elsewhere.
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.