Merge branch 'master' of https://github.com/angular-live/angular.io
This commit is contained in:
commit
c18b193288
|
@ -561,7 +561,7 @@ figure
|
|||
They tend to appear within an element tag like attributes,
|
||||
sometimes by name but more often as the target of an assignment or a binding.
|
||||
|
||||
它们往往像Attribute一样出现在元素标签中,偶尔会以名字的形式出现(译注:如`<my-hero-detail name="superman"></my-hero-detail>`,表示把hero-detail组件的name属性赋值为"superman",并且永不再变 —— 也就是一次性赋值,但不绑定),但多数时候还是作为赋值目标或绑定目标。
|
||||
它们往往像Attribute一样出现在元素标签中,偶尔会以名字的形式出现(参见[一次性绑定](./template-syntax.html#one-time-initialization)),但多数时候还是作为赋值目标或绑定目标。
|
||||
|
||||
**Structural** directives alter layout by adding, removing, and replacing elements in DOM.
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
"bold": [
|
||||
"[attr.role]"
|
||||
],
|
||||
"description": "<p>把Attribute <code>role</code> 绑定到表达式 <code>myAriaRole</code>的结果。</p>\n"
|
||||
"description": "<p>把<code>role</code>这个Attribute绑定到表达式 <code>myAriaRole</code>的结果。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "<div [class.extra-sparkle]=\"isDelightful\">",
|
||||
|
@ -75,7 +75,7 @@
|
|||
"bold": [
|
||||
"{{ponyName}}"
|
||||
],
|
||||
"description": "<p>把属性绑定到一个插值表达式字符串,比如 "Hello Seabiscuit". 等价于:\n<code><div [title]="'Hello' + ponyName"></code></p>\n"
|
||||
"description": "<p>把属性绑定到一个插值表达式字符串,比如 "Hello Seabiscuit"。它等价于:\n<code><div [title]="'Hello' + ponyName"></code></p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "<p>Hello {{ponyName}}</p>",
|
||||
|
@ -118,7 +118,7 @@
|
|||
"bold": [
|
||||
"{{employer?.companyName}}"
|
||||
],
|
||||
"description": "<p>安全导航运算符(<code>?</code>)表示<code>employer</code>字段是可选的,如果它是<code>undefined</code>表达式剩下的部分将被忽略</p>\n"
|
||||
"description": "<p>安全导航运算符(<code>?.</code>)表示<code>employer</code>字段是可选的,如果它是<code>undefined</code>,表达式剩下的部分将被忽略</p>\n"
|
||||
}
|
||||
],
|
||||
"index": 1
|
||||
|
@ -198,7 +198,7 @@
|
|||
"bold": [
|
||||
"@Pipe({...})"
|
||||
],
|
||||
"description": "<p>声明当前类是一个管道,并且提供该管道的元数据。</p>\n"
|
||||
"description": "<p>声明当前类是一个管道,并且提供关于该管道的元数据。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "@Injectable()\nclass MyService() {}",
|
||||
|
@ -319,7 +319,7 @@
|
|||
"bold": [
|
||||
"@ContentChildren(myPredicate)"
|
||||
],
|
||||
"description": "<p>把组件内容查询(MyPredicate)的全部结果,绑定到类的myChildComponents属性。</p>\n"
|
||||
"description": "<p>把组件内容查询(myPredicate)的全部结果,绑定到类的myChildComponents属性。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "@ViewChild(myPredicate) myChildComponent;",
|
||||
|
@ -333,7 +333,7 @@
|
|||
"bold": [
|
||||
"@ViewChildren(myPredicate)"
|
||||
],
|
||||
"description": "<p>把组件视图查询(myPredicate)的所有结果绑定到类的myChildComponents属性。对指令无效。</p>\n"
|
||||
"description": "<p>把组件视图查询(myPredicate)的全部结果绑定到类的myChildComponents属性。对指令无效。</p>\n"
|
||||
}
|
||||
],
|
||||
"index": 7
|
||||
|
@ -361,7 +361,7 @@
|
|||
"bold": [
|
||||
"ngOnInit()"
|
||||
],
|
||||
"description": "<p>在构造函数、初始化输入属性、第一次调用完ngOnChanges之后调用。</p>\n"
|
||||
"description": "<p>在执行构造函数、初始化输入属性、第一次调用完ngOnChanges之后调用。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "ngDoCheck() { ... }",
|
||||
|
@ -490,21 +490,21 @@
|
|||
"bold": [
|
||||
"routerOnReuse"
|
||||
],
|
||||
"description": "<p>当路由器可以复用一个组件实例时,它调用组件的routerOnReuse方法(如已定义)。</p>\n"
|
||||
"description": "<p>当路由器发现可以复用一个组件实例时,它调用组件的routerOnReuse方法(如已定义)。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "routerCanDeactivate(nextInstruction, prevInstruction) { ... }",
|
||||
"bold": [
|
||||
"routerCanDeactivate"
|
||||
],
|
||||
"description": "<p>路由器会调用要完成导航所需移除的每个组件的routerCanDeactivate方法(如已定义)。当且仅当所有的该方法都返回true或者承诺(Promise)被解析(Resolve)时,导航才会继续。</p>\n"
|
||||
"description": "<p>路由器会调用如果要完成导航就得移除的每个组件的routerCanDeactivate方法(如已定义)。当且仅当所有的该方法都返回true或者承诺(Promise)被解决时,导航才会继续。</p>\n"
|
||||
},
|
||||
{
|
||||
"syntax": "routerOnDeactivate(nextInstruction, prevInstruction) { ... }",
|
||||
"bold": [
|
||||
"routerOnDeactivate"
|
||||
],
|
||||
"description": "<p>在每个指令因为路由变更而被移除之前调用。可以返回一个承诺,来阻止移除本指令 —— 直到承诺(Promise)被解析(Resolve)。</p>\n"
|
||||
"description": "<p>在每个指令因为路由变更而被移除之前调用。可以返回一个承诺,来阻止移除本指令 —— 直到承诺(Promise)被解决。</p>\n"
|
||||
}
|
||||
],
|
||||
"index": 10
|
||||
|
|
|
@ -7,9 +7,9 @@ include ../_util-fns
|
|||
we really can't build an Angular application without it.
|
||||
It's used so widely that almost everyone just calls it _DI_.
|
||||
|
||||
**依赖注入** 是一个很重要的程序设计模式。
|
||||
**依赖注入**是一个很重要的程序设计模式。
|
||||
Angular有自己的依赖注入框架,离开了它,我们几乎没法构建Angular应用。
|
||||
它使用得非常广泛,以至于几乎每个人都会把它简称为 _DI_ 。
|
||||
它使用得非常广泛,以至于几乎每个人都会把它简称为_DI_。
|
||||
|
||||
In this chapter we'll learn what DI is and why we want it.
|
||||
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
||||
|
@ -45,14 +45,14 @@ include ../_util-fns
|
|||
|
||||
The problem is that our `Car` class is brittle, inflexible, and hard to test.
|
||||
|
||||
问题是,我们这个`Car`类过于脆弱、缺乏弹性并且难以测试。
|
||||
问题在于,我们这个`Car`类过于脆弱、缺乏弹性并且难以测试。
|
||||
|
||||
Our `Car` needs an engine and tires. Instead of asking for them,
|
||||
the `Car` constructor creates its own copies by "new-ing" them from
|
||||
the very specific classes, `Engine` and `Tires`.
|
||||
|
||||
我们的汽车(`Car`)类需要一个引擎(`Engine`)和轮胎(`Tires`),它没有去请求一个现成的实例,
|
||||
而是在构造函数中用具体的类`Engine`和`Tires`新创建了一份只给自己用的副本。
|
||||
而是在构造函数中用具体的类`Engine`和`Tires`新创建了一份只供自己用的副本。
|
||||
|
||||
What if the `Engine` class evolves and its constructor requires a parameter?
|
||||
Our `Car` is broken and stays broken until we rewrite it along the lines of
|
||||
|
@ -65,7 +65,7 @@ include ../_util-fns
|
|||
|
||||
如果`Engine`类升级了,并且它的构造函数要求传入一个参数了怎么办?
|
||||
我们这个`Car`类就被破坏了,并且直到我们把创建代码重写为`#{prefix}engine = new Engine(theNewParameter)`之前,它都坏着。
|
||||
当我们首次写`Car`类时,我们不会在乎`Engine`构造函数的参数。其实现在我们也不想在乎。
|
||||
当我们首次写`Car`类时,我们不会在乎`Engine`构造函数的参数。就算现在我们也不想在乎。
|
||||
但当`Engine`类的定义发生变化时,我们就不得不在乎了,`Car`类也不得不跟着改变。
|
||||
这就会让`Car`类过于脆弱。
|
||||
|
||||
|
@ -101,14 +101,14 @@ include ../_util-fns
|
|||
How do we confirm that it actually does flash a warning
|
||||
if we can't swap in low-pressure tires during the test?
|
||||
|
||||
如果我们的`Car`应该在轮胎气压低的时候闪一个警示灯该怎么办?
|
||||
如果我们没法在测试期间换上一个低气压的轮胎,我们该如何确认它确实能正确的闪警示灯?
|
||||
如果我们的`Car`应该在轮胎气压低的时候闪动一个警示灯该怎么办?
|
||||
如果我们没法在测试期间换上一个低气压的轮胎,我们该如何确认它能正确的闪警示灯?
|
||||
|
||||
We have no control over the car's hidden dependencies.
|
||||
When we can't control the dependencies, a class becomes difficult to test.
|
||||
|
||||
我们没法控制这辆车背后隐藏的依赖。
|
||||
而如果我们不能控制这些依赖,类就会变得难于测试。
|
||||
而如果不能控制这些依赖,类就会变得难以测试。
|
||||
|
||||
How can we make `Car` more robust, flexible, and testable?
|
||||
|
||||
|
@ -132,14 +132,14 @@ include ../_util-fns
|
|||
|
||||
发生了什么?我们把依赖的定义移到了构造函数中。
|
||||
我们的`Car`类不再创建引擎或者轮胎。
|
||||
它仅仅“消费”它们。(译注:指直接使用成品)
|
||||
它仅仅“消费”它们。
|
||||
// #enddocregion why-3-1
|
||||
// TypeScript only
|
||||
.l-sub-section
|
||||
:marked
|
||||
We also leverage TypeScript's constructor syntax for declaring parameters and properties simultaneously.
|
||||
|
||||
我们还借助了TypeScript的构造语法来同时定义参数和属性。
|
||||
我们又一次借助TypeScript的构造器语法来同时定义参数和属性。
|
||||
// #docregion why-3-2
|
||||
:marked
|
||||
Now we create a car by passing the engine and tires to the constructor.
|
||||
|
@ -156,12 +156,12 @@ include ../_util-fns
|
|||
We can pass in any kind of engine or tires we like, as long as they
|
||||
conform to the general API requirements of an engine or tires.
|
||||
|
||||
酷!引擎和轮胎这两个依赖的定义从`Car`类本身解耦开了。
|
||||
只要喜欢,我们就可以传入任何类型的引擎或轮胎,只要它们能满足引擎或轮胎的普遍API需求。
|
||||
酷!引擎和轮胎这两个“依赖”的定义从`Car`类本身解耦开了。
|
||||
只要喜欢,我们就可以传入任何类型的引擎或轮胎,只要它们能满足引擎或轮胎的通用API需求。
|
||||
|
||||
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||
|
||||
如果有人扩展了`Engine`类,那就不再是`Car`类的烦恼。
|
||||
如果有人扩展了`Engine`类,那就不再是`Car`类的烦恼了。
|
||||
// #enddocregion why-4
|
||||
// Must copy the following, due to indented +make.
|
||||
.l-sub-section
|
||||
|
@ -169,7 +169,7 @@ include ../_util-fns
|
|||
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||
something like this:
|
||||
|
||||
`Car`的 _消费者_ 也有这个问题。消费者必须更新创建这辆车的代码,就像这样:
|
||||
`Car`的_消费者_也有这个问题。消费者必须更新创建这辆车的代码,就像这样:
|
||||
|
||||
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
|
||||
|
@ -200,7 +200,7 @@ include ../_util-fns
|
|||
It's a coding pattern in which a class receives its dependencies from external
|
||||
sources rather than creating them itself.
|
||||
|
||||
它是一个编程模式,该模式可以让一个类从外部源中获得它的依赖,而不必自己创建它们。
|
||||
它是一种编程模式,该模式可以让一个类从外部源中获得它的依赖,而不必亲自创建它们。
|
||||
|
||||
Cool! But what about that poor consumer?
|
||||
Anyone who wants a `Car` must now
|
||||
|
@ -227,20 +227,20 @@ include ../_util-fns
|
|||
|
||||
现在只需要三个创建方法,这还不算太坏。
|
||||
但是当应用规模变大之后,维护它将变得惊险重重。
|
||||
这个工厂类将变成一个由相互依赖的工厂方法构成的矩形蜘蛛网。
|
||||
这个工厂类将变成一个由相互依赖的工厂方法构成的巨型蜘蛛网。
|
||||
|
||||
Wouldn't it be nice if we could simply list the things we want to build without
|
||||
having to define which dependency gets injected into what?
|
||||
|
||||
如果我们能简单的列出我们想建造的东西,而不用定义要把哪些依赖注入哪些对象,是不是会很美妙?
|
||||
如果我们能简单的列出我们想建造的东西,而不用定义要把哪些依赖注入哪些对象,那该多好!
|
||||
|
||||
This is where the dependency injection framework comes into play.
|
||||
Imagine the framework had something called an _injector_.
|
||||
We register some classes with this injector, and it figures out how to create them.
|
||||
|
||||
到了让依赖注入框架一展身手的时候了!
|
||||
想象框架中有一个叫做 _注入器(injector)_ 的东西。
|
||||
我们使用这个注入器注册一些类,还会指出该如何创建它们。
|
||||
想象框架中有一个叫做_注入器Injector_的东西。
|
||||
我们使用这个注入器注册一些类,它会指出该如何创建它们。
|
||||
|
||||
When we need a `Car`, we simply ask the injector to get it for us and we're good to go.
|
||||
|
||||
|
@ -257,11 +257,11 @@ include ../_util-fns
|
|||
多方皆赢的方式。`Car`不需要知道如何创建`Engine`和`Tires`的任何事。
|
||||
消费者不知道如何创建`Car`的任何事。
|
||||
我们不需要一个巨大的工厂类来维护它们。
|
||||
`Car`和消费者只要简单的说出它们想要什么,注入器就会交付给它们。
|
||||
`Car`和消费者只要简单的说出它们想要什么,注入器就会交给它们。
|
||||
|
||||
This is what a **dependency injection framework** is all about.
|
||||
|
||||
这就是关于 **依赖注入框架** 的一切。
|
||||
这就是“什么是**依赖注入框架**”问题的答案。
|
||||
|
||||
Now that we know what dependency injection is and appreciate its benefits,
|
||||
let's see how it is implemented in Angular.
|
||||
|
@ -278,7 +278,7 @@ include ../_util-fns
|
|||
Angular ships with its own dependency injection framework. This framework can also be used
|
||||
as a standalone module by other applications and frameworks.
|
||||
|
||||
Angular自带了它自己的依赖注入框架。此框架也能被当做独立模块用于其它应用和框架中。
|
||||
Angular自带了它自己的依赖注入框架。但此框架也能被当做独立模块用于其它应用和框架中。
|
||||
|
||||
That sounds nice. What does it do for us when building components in Angular?
|
||||
Let's see, one step at a time.
|
||||
|
@ -289,7 +289,7 @@ include ../_util-fns
|
|||
We'll begin with a simplified version of the `HeroesComponent`
|
||||
that we built in the [The Tour of Heroes](../tutorial/).
|
||||
|
||||
我们从曾在[英雄指南](../tutorial/)中构建过的`HeroesComponent`的一个简化版本开始。
|
||||
我们从当初在[英雄指南](../tutorial/)中构建过的`HeroesComponent`的一个简化版本开始。
|
||||
// #enddocregion di-1
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.1.ts,
|
||||
|
@ -308,7 +308,7 @@ include ../_util-fns
|
|||
Our stripped down version has only one child, `HeroListComponent`,
|
||||
which displays a list of heroes.
|
||||
|
||||
`HeroesComponent`是 *英雄* 特性分区中的根组件。它管理着本分区的所有子组件。
|
||||
`HeroesComponent`是*英雄*特性分区中的根组件。它管理着本分区的所有子组件。
|
||||
我们简化后的版本只有一个子组件`HeroListComponent`,它显示一个英雄列表。
|
||||
// #enddocregion di-2
|
||||
// #docregion di-3
|
||||
|
@ -322,11 +322,11 @@ include ../_util-fns
|
|||
|
||||
现在`HeroListComponent`从`HEROES`获得英雄数据,一个在另一个文件中定义的内存数据集。
|
||||
它在开发的早期阶段可能还够用,但离完美就差得远了。
|
||||
一旦我们开始测试此组件,或者想从远端服务器获得英雄数据,我们将不得不修改`heroes`的实现,并要修改每个用到了`HEROES`模拟数据的地方。
|
||||
一旦我们开始测试此组件,或者想从远端服务器获得英雄数据,我们就不得不修改`heroes`的实现,并要修改每个用到了`HEROES`模拟数据的地方。
|
||||
|
||||
Let's make a service that hides how we get hero data.
|
||||
|
||||
我们来制作一个服务,来把获取英雄数据的代码封装起来。
|
||||
我们来制作一个服务,把获取英雄数据的代码封装起来。
|
||||
// #enddocregion di-3
|
||||
|
||||
// Unnecessary for Dart
|
||||
|
@ -341,7 +341,7 @@ include ../_util-fns
|
|||
Our `HeroService` exposes a `getHeroes` method that returns
|
||||
the same mock data as before, but none of its consumers need to know that.
|
||||
|
||||
我们的`HeroService`暴露了`getHeroes`方法,用于返回跟以前一样的模拟数据,但是它的消费者不需要了解这一点。
|
||||
我们的`HeroService`暴露了`getHeroes`方法,用于返回跟以前一样的模拟数据,但它的消费者不需要知道这一点。
|
||||
// #enddocregion di-4
|
||||
// #docregion di-5
|
||||
.l-sub-section
|
||||
|
@ -370,13 +370,12 @@ include ../_util-fns
|
|||
### Configuring the injector
|
||||
### 配置注入器
|
||||
|
||||
<a id="bootstrap"></a>
|
||||
We don't have to create an Angular injector.
|
||||
Angular creates an application-wide injector for us during the bootstrap process.
|
||||
|
||||
<a id="bootstrap"></a>
|
||||
我们并不需要自己创建一个Angular注入器。
|
||||
Angular在启动期间会自动为我们创建一个应用级注入器。
|
||||
Angular在启动期间会自动为我们创建一个全应用级注入器。
|
||||
// #enddocregion di-configure-injector-1
|
||||
+makeExample('dependency-injection/ts/app/main.ts', 'bootstrap', 'app/main.ts (节选)')(format='.')
|
||||
// #docregion di-configure-injector-2
|
||||
|
@ -386,9 +385,9 @@ include ../_util-fns
|
|||
We'll explain what [providers](#providers) are later in this chapter.
|
||||
Before we do, let's see an example of provider registration during bootstrapping:
|
||||
|
||||
我们必须先注册**供应商**来配置好注入器,这在创建应用所需的服务时会用到。
|
||||
我们必须先注册**供应商Provider**来配置好注入器,这在创建应用所需的服务时会用到。
|
||||
我们将在本章的稍后部分解释什么是[供应商](#providers)。
|
||||
在此之前,我们先来看一个启动期间的注册供应商的例子。
|
||||
在此之前,我们先来看一个在启动期间注册供应商的例子。
|
||||
// #enddocregion di-configure-injector-2
|
||||
+makeExample('dependency-injection/ts/app/main.1.ts', 'bootstrap')(format='.')
|
||||
// #docregion di-configure-injector-3
|
||||
|
@ -412,7 +411,7 @@ include ../_util-fns
|
|||
Because the `HeroService` is used within the *Heroes* feature area —
|
||||
and nowhere else — the ideal place to register it is in the top-level `HeroesComponent`.
|
||||
|
||||
首选的方式是在应用组件中注册应用级的供应商。
|
||||
首选的方式是在应用的组件中注册供应商。
|
||||
因为`HeroService`是用于*英雄*功能区的 —— 并且没别处用它 —— 所以注册它的理想地点就是`HeroesComponent`的顶层。
|
||||
// #enddocregion di-configure-injector-3
|
||||
// #docregion di-register-providers-1
|
||||
|
@ -516,9 +515,9 @@ include ../_util-fns
|
|||
If we let Angular do its job, we'll enjoy the benefits of automated dependency injection.
|
||||
|
||||
但无论在《英雄指南》还是其它范例中,我们都没有发现这样的代码。
|
||||
在必要时,我们 *可以* 写[使用显式注入器的代码](#explicit-injector),但却很少这样做。
|
||||
在必要时,我们*可以*写[使用显式注入器的代码](#explicit-injector),但却很少这样做。
|
||||
当Angular为我们创建组件时 —— 无论通过像`<hero-list></hero-list>`这样的HTML标签还是通过[路由](./router.html)导航到组件 —— 它都会自己管理好注入器的创建和调用。
|
||||
只要让Angular做好它自己的工作,我们就能安心享受自动依赖注入带来的好处。
|
||||
只要让Angular做好它自己的工作,我们就能安心享受“自动依赖注入”带来的好处。
|
||||
// #enddocregion di-create-injector-implicitly-2
|
||||
// #docregion di-singleton-services
|
||||
:marked
|
||||
|
@ -561,7 +560,7 @@ include ../_util-fns
|
|||
:marked
|
||||
Learn more in [Testing](../testing/index.html).
|
||||
|
||||
学习更多知识,参见[测试](../testing/index.html)。
|
||||
要学习更多知识,参见[测试](../testing/index.html)。
|
||||
// #enddocregion di-testing-component-2
|
||||
// #docregion di-service-service-1
|
||||
:marked
|
||||
|
@ -576,7 +575,7 @@ include ../_util-fns
|
|||
adding a constructor that takes a `Logger` parameter.
|
||||
|
||||
如果它有依赖呢?如果它需要通过一个日志服务来汇报自己的活动呢?
|
||||
我们同样用 *构造函数注入* 模式,来添加一个带有`Logger`参数的构造函数。
|
||||
我们同样用*构造函数注入*模式,来添加一个带有`Logger`参数的构造函数。
|
||||
|
||||
Here is the revision compared to the original.
|
||||
|
||||
|
@ -593,7 +592,7 @@ include ../_util-fns
|
|||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `_logger`.
|
||||
We call that property within our `getHeroes` method when anyone asks for heroes.
|
||||
|
||||
这个构造函数现在会要求一个`Logger`类的实例注入进来,并且把它存在一个名叫`_logger`的私有属性中。
|
||||
现在,这个构造函数会要求一个`Logger`类的实例注入进来,并且把它存到一个名为`_logger`的私有属性中。
|
||||
当别人要求获得英雄数据时,我们会在`getHeroes`方法中使用这个属性。
|
||||
// #enddocregion di-service-service-2
|
||||
// #docregion di-injectable-1
|
||||
|
@ -605,7 +604,7 @@ include ../_util-fns
|
|||
:marked
|
||||
<a id="injectable"></a>
|
||||
### Why @Injectable?
|
||||
### 为什么要@Injectable?
|
||||
### 为什么要加@Injectable?
|
||||
Notice the `@Injectable()` #{decoration} above the service class.
|
||||
We haven't seen `@Injectable()` before.
|
||||
As it happens, we could have added it to our first version of `HeroService`.
|
||||
|
@ -618,7 +617,7 @@ include ../_util-fns
|
|||
We need it now... now that our service has an injected dependency.
|
||||
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`. !{tsmetadata}
|
||||
|
||||
现在,我们需要了 …… 现在,该服务有了一个需要被注入的依赖。
|
||||
现在,我们需要了…… 现在,该服务有了一个需要被注入的依赖。
|
||||
之所以需要它,是因为Angular需要关于构造函数参数的元数据,有了元数据才能注入`Logger`。!{tsmetadataCn}
|
||||
// #enddocregion di-injectable-1
|
||||
|
||||
|
@ -629,7 +628,7 @@ include ../_util-fns
|
|||
- var decorated = lang == 'dart' ? 'annotated' : 'decorated'
|
||||
- var decoratedCn = lang == 'dart' ? '注解' : '装饰器'
|
||||
- var any_decorator = lang == 'dart' ? '' : 'TypeScript generates metadata for any class with a decorator, and any decorator will do.'
|
||||
- var any_decoratorCn = lang == 'dart' ? '' : 'TypeScript会为任何带有装饰器的类生成元数据,并且会为任何装饰器都生成。'
|
||||
- var any_decoratorCn = lang == 'dart' ? '' : 'TypeScript会为任何带有装饰器的类生成元数据,而且会为任何装饰器都生成。'
|
||||
.callout.is-helpful
|
||||
header Suggestion: add @Injectable() to every service class
|
||||
header 建议:为每一个服务类添加@Injectable()
|
||||
|
@ -637,10 +636,10 @@ include ../_util-fns
|
|||
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||
and, therefore, do not technically require it. Here's why:
|
||||
|
||||
我们建议为每个服务类都添加`@Injectable()`装饰器,即使它们因为还没有任何依赖,而在技术上并不需要它。这是因为:
|
||||
我们建议为每个服务类都添加`@Injectable()`装饰器,即使它们因为目前没有任何依赖,而在技术上并不需要它。这是因为:
|
||||
ul(style="font-size:inherit")
|
||||
li <b>Future proofing:</b> No need to remember <code>@Injectable()</code> when we add a dependency later.
|
||||
li <b>面向未来:</b> 在我们将来添加依赖时,不必记着添加<code>@Injectable()</code>。
|
||||
li <b>面向未来:</b> 在我们将来添加依赖时,不用怕忘了添加<code>@Injectable()</code>。
|
||||
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why #{a_decorator} is missing.
|
||||
li <b>统一性:</b> 所有服务都遵循着同样的规则,我们就不必困惑为什么#{a_decoratorCn}被省略了。
|
||||
|
||||
|
@ -649,7 +648,7 @@ include ../_util-fns
|
|||
Some developers prefer to add it only where needed and that's a reasonable policy too.
|
||||
|
||||
虽然我们建议给所有服务类都添加`@Injectable()`,但你也不必受此约束。
|
||||
有些开发人员就喜欢只在需要的时候才添加,那也同样是一个合理的准则。
|
||||
有些开发人员就是喜欢在需要的时候才添加,那也同样是一个合理的准则。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -664,13 +663,13 @@ include ../_util-fns
|
|||
// #enddocregion di-injectable-2
|
||||
.callout.is-critical
|
||||
header Always include the parentheses
|
||||
header 总是要带着括号
|
||||
header 总要带着括号
|
||||
:marked
|
||||
Always use `@Injectable()`, not just `@Injectable`.
|
||||
Our application will fail mysteriously if we forget the parentheses.
|
||||
|
||||
总是使用`@Injectable()`的形式,不能只用`@Injectable`。
|
||||
如果忘了括号,我们的应用就是神不知鬼不觉的失败!
|
||||
如果忘了括号,我们的应用就会神不知鬼不觉的失败!
|
||||
|
||||
// #docregion logger-service-1
|
||||
.l-main-section
|
||||
|
@ -678,7 +677,8 @@ include ../_util-fns
|
|||
## Creating and registering a logger service
|
||||
## 创建和注册日志服务
|
||||
We're injecting a logger into our `HeroService` in two steps:
|
||||
我们要把日志服务注入到`HeroService`中需要两步:
|
||||
|
||||
要把日志服务注入到`HeroService`中需要两步:
|
||||
1. Create the logger service.
|
||||
1. 创建日志服务。
|
||||
1. Register it with the application.
|
||||
|
@ -697,8 +697,8 @@ include ../_util-fns
|
|||
so we put it at the root level of the application in the `app/` folder, and
|
||||
we register it in the `providers` array of the metadata for our application root component, `AppComponent`.
|
||||
|
||||
我们很可能在应用的任何地方都使用同一个日志服务实例。
|
||||
所以,我们把它放到`app/`目录下,也就是应用的顶级,并且把它注册到我们的根组件`AppComponent`上,放到元数据中的`providers`数组里。
|
||||
我们很可能在应用的任何地方都使用同一个日志服务的实例。
|
||||
所以,我们把它放到`app/`目录下,也就是应用的顶级,并把它注册到我们的根组件`AppComponent`上,放到元数据中的`providers`数组里。
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger', 'app/app.component.ts (节选)')
|
||||
// #docregion logger-service-3
|
||||
:marked
|
||||
|
@ -728,9 +728,8 @@ code-example(format, language="html").
|
|||
这个“创建链”始于`Logger`的供应商。这个*供应商*就是我们下一节的主题。
|
||||
|
||||
But wait! What if the logger is optional?
|
||||
<a id="optional"></a>
|
||||
|
||||
但,等一下,如果这个日志服务是可选的呢?
|
||||
但是,等一下,如果这个日志服务是可选的呢?
|
||||
<a id="optional"></a>
|
||||
### Optional dependencies
|
||||
### 可选的依赖
|
||||
|
@ -770,12 +769,12 @@ code-example(format, language="html").
|
|||
the injector will inject `null`. We have a method that logs.
|
||||
What can we do to avoid a null reference exception?
|
||||
|
||||
准备一个空的日志服务。如果我们在代码中不注册一个,注入器就会注入`null`。
|
||||
假设我们有一个带日志的方法,该如何消除空指针错误呢?
|
||||
准备迎接一个空的日志服务。如果我们在代码中不注册一个,注入器就会注入`null`。
|
||||
我们有一个需要记日志的方法,这时该如何消除空指针错误呢?
|
||||
|
||||
We could substitute a *do-nothing* logger stub so that calling methods continue to work:
|
||||
|
||||
我们可以用一个 *什么也不做* 的日志服务桩对象来代替,以便调用它的方法可以照常工作:
|
||||
我们可以用一个*什么也不做*的日志服务桩对象来代替,以便调用它的方法可以照常工作:
|
||||
// #enddocregion logger-service-6
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-logger')(format='.')
|
||||
// #docregion logger-service-7
|
||||
|
@ -805,7 +804,7 @@ code-example(format, language="html").
|
|||
The injector relies on **providers** to create instances of the services
|
||||
that the injector injects into components and other services.
|
||||
|
||||
供应商*提供*所依赖值的一个具体的运行期版本。
|
||||
供应商*供应*所依赖值的一个具体的运行期版本。
|
||||
注入器依靠**供应商们**来创建服务的实例,它会被注入器注入到组件或其它服务中。
|
||||
|
||||
We must register a service *provider* with the injector, or it won't know how to create the service.
|
||||
|
@ -822,9 +821,9 @@ code-example(format, language="html").
|
|||
- var implements = lang == 'dart' ? 'implements' : 'looks and behaves like a '
|
||||
- var implementsCn = lang == 'dart' ? '实现' : '表现和行为像'
|
||||
- var objectlike = lang == 'dart' ? '' : 'an object that behaves like '
|
||||
- var objectlikeCn = lang == 'dart' ? '' : '一个对象,它的行为像'
|
||||
- var objectlikeCn = lang == 'dart' ? '' : '一个对象,其行为像'
|
||||
- var loggerlike = lang == 'dart' ? '' : 'We could provide a logger-like object. '
|
||||
- var loggerlikeCn = lang == 'dart' ? '' : '我们可以提供一个像logger的对象。'
|
||||
- var loggerlikeCn = lang == 'dart' ? '' : '也就是可以提供一个像logger的对象。'
|
||||
:marked
|
||||
The `providers` array appears to hold a service class.
|
||||
In reality it holds an instance of the [Provider](../api/core/Provider-class.html) class that can create that service.
|
||||
|
@ -836,23 +835,23 @@ code-example(format, language="html").
|
|||
The `Logger` class itself is an obvious and natural provider — it has the right shape and it's designed to be created.
|
||||
But it's not the only way.
|
||||
|
||||
有很多方式可以 *提供* 一些#{implementsCn} `Logger`类的东西。
|
||||
有很多方式可以*提供*一些#{implementsCn} `Logger`类的东西。
|
||||
`Logger`类本身是一个显而易见而且自然而然的供应商 —— 它有正确的形态,并且它设计出来就是等着被创建的。
|
||||
但它不是唯一的方式。
|
||||
但它不是唯一的选项。
|
||||
|
||||
We can configure the injector with alternative providers that can deliver #{objectlike} a `Logger`.
|
||||
We could provide a substitute class. #{loggerlike}
|
||||
We could give it a provider that calls a logger factory function.
|
||||
Any of these approaches might be a good choice under the right circumstances.
|
||||
|
||||
我们可以使用另外的各种供应商来配置这个注入器,只要它们能交付#{objectlikeCn}`Logger`。
|
||||
我们可以使用另外的各种供应商来配置这个注入器,只要它们能交付#{objectlikeCn}`Logger`就可以了。
|
||||
我们可以提供一个替身类。#{loggerlikeCn}
|
||||
我们可以给它一个供应商,让它调用一个可以创建日志服务的工厂函数。
|
||||
所有这些方法,只要在正确的情境下,都可能是一个好的选择。
|
||||
所有这些方法,只要用在正确的场合,都可能是一个好的选择。
|
||||
|
||||
What matters is that the injector has a provider to go to when it needs a `Logger`.
|
||||
|
||||
重点是:当注入器需要一个`Logger`时,它得先有一个供应商。
|
||||
问题是:当注入器需要一个`Logger`时,它得先有一个供应商。
|
||||
// #enddocregion providers-2
|
||||
// #docregion providers-provide-1
|
||||
:marked
|
||||
|
@ -910,18 +909,18 @@ code-example(format, language="html").
|
|||
|
||||
第二个是一个供应商定义对象。
|
||||
我们可以把它看做一个指导如何创建依赖值的*配方*。
|
||||
有很多方式创建依赖值…… 也有很多方式可以写菜谱。
|
||||
有很多方式创建依赖值…… 也有很多方式可以写配方。
|
||||
// #docregion providers-alternative-1
|
||||
:marked
|
||||
<a id="class-provider"></a>
|
||||
### Alternative class providers
|
||||
### 另外的类供应商
|
||||
### 另外的“类”供应商
|
||||
|
||||
Occasionally we'll ask a different class to provide the service.
|
||||
The following code tells the injector
|
||||
to return a `BetterLogger` when something asks for the `Logger`.
|
||||
|
||||
某些时候,我们会请求另一个类来提供此服务。
|
||||
某些时候,我们会请求另一个类来供应此服务。
|
||||
下列代码告诉注入器:当有人请求一个`Logger`时,请返回一个`BetterLogger`。
|
||||
// #enddocregion providers-alternative-1
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-4')
|
||||
|
@ -959,13 +958,13 @@ code-example(format, language="html").
|
|||
When the *old* component logs a message with `OldLogger`,
|
||||
we want the singleton instance of `NewLogger` to handle it instead.
|
||||
|
||||
当 *老的* 组件想使用`OldLogger`记录消息时,我们希望改用`NewLogger`的单例对象来记录。
|
||||
当*老的*组件想使用`OldLogger`记录消息时,我们希望改用`NewLogger`的单例对象来记录。
|
||||
|
||||
The dependency injector should inject that singleton instance
|
||||
when a component asks for either the new or the old logger.
|
||||
The `OldLogger` should be an alias for `NewLogger`.
|
||||
|
||||
当组件请求无论是新的还是老的日志服务时,依赖注入器注入的都应该是这个单例对象。
|
||||
当组件请求无论是新的还是老的日志服务时,依赖注入器注入的都应该是同一个单例对象。
|
||||
也就是说,`OldLogger`应该是`NewLogger`的一个别名。
|
||||
|
||||
We certainly do not want two different `NewLogger` instances in our app.
|
||||
|
@ -993,7 +992,7 @@ code-example(format, language="html").
|
|||
:marked
|
||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||
|
||||
有时,提供一个只读模式的对象会比请求注入器从类创建它更容易。
|
||||
有时,提供一个预先做好的对象会比请求注入器从类中创建它更容易。
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','silent-logger')(format=".")
|
||||
:marked
|
||||
Then we register a provider with the `useValue` option,
|
||||
|
@ -1028,7 +1027,7 @@ code-example(format, language="html").
|
|||
Only authorized users should see secret heroes.
|
||||
|
||||
我们通过添加一个新的业务需求来说明这一点:
|
||||
HeroService必须对普通用户里隐藏 *秘密* 英雄。
|
||||
HeroService必须对普通用户隐藏掉*秘密*英雄。
|
||||
只有获得授权的用户才能看到秘密英雄。
|
||||
|
||||
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||
|
@ -1054,7 +1053,7 @@ code-example(format, language="html").
|
|||
:marked
|
||||
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||
|
||||
让`HeroService`的构造函数带上一个逻辑型的标志,来控制是否显示隐藏的英雄。
|
||||
让`HeroService`的构造函数带上一个布尔型的标志,来控制是否显示隐藏的英雄。
|
||||
// #enddocregion providers-factory-1
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.ts','internals', 'app/heroes/hero.service.ts (节选)')(format='.')
|
||||
// #docregion providers-factory-2
|
||||
|
@ -1093,9 +1092,9 @@ code-example(format, language="html").
|
|||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
|
||||
`deps`属性是一个[供应商token](#token)的数组。
|
||||
`deps`属性是一个[供应商Token](#token)的数组。
|
||||
`Logger`和`UserService`类作为它们自身供应商的token。
|
||||
注入器解析这些token,并且把相应的服务注入到工厂函数参数中所对应的参数中去。
|
||||
注入器解析这些Token,并且把相应的服务注入到工厂函数参数中所对应的参数中去。
|
||||
// #enddocregion providers-factory-4
|
||||
// #docregion providers-factory-5
|
||||
- var lang = current.path[1]
|
||||
|
@ -1138,16 +1137,16 @@ code-example(format, language="html").
|
|||
The injector maintains an internal *token-provider* map that it references when
|
||||
asked for a dependency. The token is the key to the map.
|
||||
|
||||
当我们使用注入器注册一个供应商时,实际上是把这个供应商和一个DI token关联起来了。
|
||||
注入器维护一个内部的*token-供应商*映射表,这个映射表会在请求一个依赖时被引用到。
|
||||
token就是这个映射表中的键值(key)。
|
||||
当我们使用注入器注册一个供应商时,实际上是把这个供应商和一个DI Token关联起来了。
|
||||
注入器维护一个内部的*Token-供应商*映射表,这个映射表会在请求一个依赖时被引用到。
|
||||
Token就是这个映射表中的键值(key)。
|
||||
|
||||
In all previous examples, the dependency value has been a class *instance*, and
|
||||
the class *type* served as its own lookup key.
|
||||
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token:
|
||||
|
||||
在以前的所有范例中,依赖值都是一个类 *实例* ,并且类的 *类型* 作为它自己的查找键值。
|
||||
这种情况下,我们实际上是直接从注入器中以`HeroService`类型作为token,来获取一个`HeroService` 实例。
|
||||
在以前的所有范例中,依赖值都是一个类*实例*,并且类的*类型*作为它自己的查找键值。
|
||||
这种情况下,我们实际上是直接从注入器中以`HeroService`类型作为Token,来获取一个`HeroService` 实例。
|
||||
// #enddocregion tokens-1
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.')
|
||||
// #docregion tokens-2
|
||||
|
@ -1158,7 +1157,7 @@ code-example(format, language="html").
|
|||
service associated with that `HeroService` class token:
|
||||
|
||||
当我们写一个请求注入基于类的依赖的构造函数时,我们是幸运的。
|
||||
我们只要以`HeroService`类为类型,定义一个构造函数参数,Angular就会知道把跟`HeroService`类这个token关联的服务注入进来:
|
||||
我们只要以`HeroService`类为类型,定义一个构造函数参数,Angular就会知道把跟`HeroService`类这个Token关联的服务注入进来:
|
||||
// #enddocregion tokens-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".")
|
||||
// #docregion tokens-3
|
||||
|
@ -1202,8 +1201,8 @@ code-example(format, language="html").
|
|||
|
||||
我们想让这个`config`对象在注入时可用。
|
||||
我们已经知道可以使用一个[值供应商](#value-provider)来注册一个对象。
|
||||
但是这种情况下我们要把什么用作token呢?
|
||||
我们没办法找一个类来当做token,因为没有`Config`类。
|
||||
但是这种情况下我们要把什么用作Token呢?
|
||||
我们没办法找一个类来当做Token,因为没有`Config`类。
|
||||
|
||||
// Typescript only
|
||||
<a id="interface"></a>
|
||||
|
@ -1241,8 +1240,7 @@ h3 OpaqueToken
|
|||
p.
|
||||
The solution is to define and use an !{opaquetoken}.
|
||||
The definition looks like this:
|
||||
|
||||
解决方案是定义和使用用一个!{opaquetoken}(不透明的Token)。定义方式类似于这样:
|
||||
p 解决方案是定义和使用用一个!{opaquetoken}(不透明的Token)。定义方式类似于这样:
|
||||
// #enddocregion tokens-opaque-1
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
||||
:marked
|
||||
|
@ -1260,7 +1258,8 @@ p.
|
|||
Now we can inject the #{configuration} object into any constructor that needs it, with
|
||||
the help of an `@Inject` #{decorator} that tells Angular how to find the #{configuration} dependency value.
|
||||
|
||||
现在,我们可以把这个#{configurationCn}对象注入到任何需要它的构造函数中,在`@Inject`#{decoratorCn}的帮助下,告诉Angular如何找到这个#{configuration}依赖的值。
|
||||
现在,我们可以把这个#{configurationCn}对象注入到任何需要它的构造函数中,在`@Inject`#{decoratorCn}的帮助下,
|
||||
告诉Angular如何找到这个#{configurationCn}依赖的值。
|
||||
// #enddocregion tokens-opaque-2
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9b-ctor')(format=".")
|
||||
|
||||
|
@ -1293,7 +1292,7 @@ p.
|
|||
adding a parameter to a constructor.
|
||||
|
||||
在本章中,我们学会了Angular依赖注入的基础。
|
||||
我们可以注册很多种类的供应商,而且我们知道了该如何通过添加构造函数的参数来请求一个被注入对象(比如服务)。
|
||||
我们可以注册很多种类的供应商,还知道了该如何通过添加构造函数的参数来请求一个被注入对象(比如服务)。
|
||||
|
||||
Angular dependency injection is more capable than we've described.
|
||||
We can learn more about its advanced features, beginning with its support for
|
||||
|
@ -1301,7 +1300,7 @@ p.
|
|||
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
||||
|
||||
Angular依赖注入比我们描述的更能干。
|
||||
我们还可以学到它的更多高级特性,从它对嵌套注入器的支持开始,就在[分层依赖注入](hierarchical-dependency-injection.html)一章。
|
||||
我们还可以学到它的更多高级特性,从它对嵌套注入器的支持开始,参见[分层依赖注入](hierarchical-dependency-injection.html)一章。
|
||||
// #enddocregion summary
|
||||
|
||||
// #docregion appendix-explicit-injector-1
|
||||
|
@ -1327,7 +1326,7 @@ p.
|
|||
The component then asks the injected injector for the services it wants.
|
||||
|
||||
在这个例子中,Angular把组件自身的`Injector`注入到了组件的构造函数中。
|
||||
然后组件像注入进来的这个注入器请求它所需的服务。
|
||||
然后组件向注入进来的这个注入器请求它所需的服务。
|
||||
|
||||
Note that the services themselves are not injected into the component.
|
||||
They are retrieved by calling `injector.get`.
|
||||
|
@ -1340,14 +1339,14 @@ p.
|
|||
to retrieve a service (`ROUS`) that isn't registered with this or any ancestor injector.
|
||||
|
||||
`get`方法如果解析不出所请求的服务,它就会抛出一个异常。
|
||||
我们还可以带上第二个参数(如果服务没找到,就返回它)调用`get`,
|
||||
在该例子中,我们获取一个服务(`ROUS`),它没有在这个注入器或它的任何祖先中注册过。
|
||||
我们还可以带上第二个参数(如果服务没找到,就把它作为默认值返回)调用`get`,
|
||||
在该例子中,我们获取了一个服务(`ROUS`),它没有在这个注入器或它的任何祖先中注册过。
|
||||
.l-sub-section
|
||||
:marked
|
||||
The technique we just described is an example of the
|
||||
[service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
|
||||
我们只通过一个范例描述了这项技术:[服务定位器模式](https://en.wikipedia.org/wiki/Service_locator_pattern)。
|
||||
我们只通过一个范例描述过这项技术:[服务定位器模式](https://en.wikipedia.org/wiki/Service_locator_pattern)。
|
||||
|
||||
We **avoid** this technique unless we genuinely need it.
|
||||
It encourages a careless grab-bag approach such as we see here.
|
||||
|
@ -1356,7 +1355,7 @@ p.
|
|||
It could acquire services from any ancestor component, not just its own.
|
||||
We're forced to spelunk the implementation to discover what it does.
|
||||
|
||||
我们要 **避免使用** 此技术,除非我们确实需要它。
|
||||
我们要**避免使用**此技术,除非我们确实需要它。
|
||||
它会鼓励鲁莽的方法,就像我们在这里看到的。
|
||||
它难以解释、理解和测试。
|
||||
仅通过阅读构造函数,我们没法知道这个类需要什么或者它将做什么。
|
||||
|
@ -1366,7 +1365,7 @@ p.
|
|||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
|
||||
框架开发人员可能需要此方法 —— 当他们不得不以通用和动态的方式获取服务时。
|
||||
框架的开发人员可能会需要此方法 —— 当他们不得不以通用和动态的方式获取服务时。
|
||||
// #enddocregion appendix-explicit-injector-2
|
||||
|
||||
// TypeScript only? Unnecessary for Dart
|
||||
|
@ -1388,8 +1387,8 @@ p.
|
|||
If we define the component before the service,
|
||||
we'll get a runtime null reference error.
|
||||
|
||||
如果我们蔑视这个建议,并且 —— 比如说 —— 把`HeroService`和`HeroesComponent`组合在同一个文件里,**就得把组件定义在后面!**
|
||||
如果我们把组件定义在服务的前面,我们就会在运行时获得一个空指针错。
|
||||
如果我们蔑视这个建议,并且 —— 比如说 —— 把`HeroService`和`HeroesComponent`组合在同一个文件里,**就得把组件定义放在后面!**
|
||||
如果我们把组件定义在了服务的前面,就会在运行时获得一个空指针错误。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
|
|
@ -5,25 +5,26 @@ include ../_util-fns
|
|||
schedule a meeting and perform countless other data entry tasks.
|
||||
Forms are the mainstay of business applications.
|
||||
|
||||
我们全都使用表单来执行登录、求助、下单、预订机票、发起会议,以及不计其数的其它数据录入任务。
|
||||
我们全都用过表单来执行登录、求助、下单、预订机票、发起会议,以及不计其数的其它数据录入任务。
|
||||
表单是商业应用的主体。
|
||||
|
||||
Any seasoned web developer can slap together an HTML form with all the right tags.
|
||||
It's more challenging to create a cohesive data entry experience that guides the
|
||||
user efficiently and effectively through the workflow behind the form.
|
||||
|
||||
不管什么样的Web开发者,都能使用正确的标签“捏”出一个HTML。
|
||||
但要想做出一个能整合数据输入体验的表单,以指导用户明晰、高效的走通表单背后的工作流,就是一个挑战了。
|
||||
但要想做出一个优秀的表单,让它具有贴心的数据输入体验,以指导用户明晰、高效的走通表单背后的工作流,这个挑战就大多了。
|
||||
|
||||
*That* takes design skills that are, to be frank, well out of scope for this chapter.
|
||||
|
||||
*这当中* 所需要的设计技能,坦白讲,确实超出了本章的范围。
|
||||
*这当中*所需要的设计技能,坦白讲,确实超出了本章的范围。
|
||||
|
||||
It also takes framework support for
|
||||
**two-way data binding, change tracking, validation, and error handling**
|
||||
... which we shall cover in this chapter on Angular forms.
|
||||
|
||||
它也需要框架支持,来实现 **双向数据绑定、变更跟踪、有效性验证和错误处理**
|
||||
这些Angular表单相关的内容,是本章将会覆盖的。
|
||||
但它也需要框架支持,来实现**双向数据绑定、变更跟踪、有效性验证和错误处理**……
|
||||
这些Angular表单相关的内容,属于本章的范围。
|
||||
|
||||
We will build a simple form from scratch, one step at a time. Along the way we'll learn
|
||||
|
||||
|
@ -36,16 +37,16 @@ include ../_util-fns
|
|||
- 使用`[(ngModel)]`语法实现双向数据绑定,以便从输入控件中读取和写入值
|
||||
|
||||
- using `ngControl` to track the change state and validity of form controls
|
||||
- 使用`ngControl`来跟踪变更状态以及对表单控件做验证
|
||||
- 使用`ngControl`来跟踪变更状态并对表单控件做验证
|
||||
|
||||
- the special CSS classes that `ngControl` adds to form controls and how we can use them to provide strong visual feedback
|
||||
- 那些`ngControl`往表单控件上添加的特殊的CSS类,以及我们该如何使用它们来提供强烈的视觉反馈
|
||||
- `ngControl`添加到表单控件上的那些特殊的CSS类,以及我们该如何使用它们来提供强烈的视觉反馈
|
||||
|
||||
- displaying validation errors to users and enable/disable form controls
|
||||
- 给用户显示有效性验证的错误信息,以及禁用/使能表单控件
|
||||
- 向用户显示有效性验证的错误提示,以及禁用/使能表单控件
|
||||
|
||||
- sharing information among controls with template reference variables
|
||||
- 在控件和模板引用变量之间共享信息
|
||||
- 通过模板引用变量,在控件之间共享信息
|
||||
|
||||
[Live Example](/resources/live-examples/forms/ts/plnkr.html)
|
||||
|
||||
|
@ -58,7 +59,7 @@ include ../_util-fns
|
|||
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
|
||||
the form-specific directives and techniques described in this chapter.
|
||||
|
||||
大多数人都可以使用表单特有指令和本章所描述的技术,在模板中按照Angular[模板语法](./template-syntax.html)来构建表单。
|
||||
大多数人都可以使用表单特有的指令和本章所描述的技术,在模板中按照Angular[模板语法](./template-syntax.html)来构建表单。
|
||||
.l-sub-section
|
||||
:marked
|
||||
That's not the only way to create a form but it's the way we'll cover in this chapter.
|
||||
|
@ -69,7 +70,7 @@ include ../_util-fns
|
|||
We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
|
||||
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
|
||||
|
||||
我们可以构建需要在Angular模板中使用的几乎所有的表单 —— 登录表单、联系人表单 …… 大量的各种商务表单。
|
||||
我们可以构建要用在Angular模板中的几乎所有表单 —— 登录表单、联系人表单…… 大量的各种商务表单。
|
||||
我们可以创造性的摆放各种控件、把它们绑定到数据、指定校验规则、显示校验错误、有条件的禁用/使能特定的控件、触发内置的视觉反馈等等,不胜枚举。
|
||||
|
||||
It will be pretty easy because Angular handles many of the repetitive, boiler plate tasks we'd
|
||||
|
@ -88,7 +89,7 @@ figure.image-display
|
|||
Here at the *Hero Employment Agency* we use this form to maintain personal information about the
|
||||
heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis!
|
||||
|
||||
这里是 *英雄职业介绍所* ,我们使用这个表单来维护我们候选英雄们的个人信息。每个英雄都需要一个工作。我们公司的任务就是让正确的英雄去解决他/她所擅长的危机!
|
||||
这里是*英雄职介中心*,我们使用这个表单来维护我们候选英雄们的个人信息。每个英雄都需要一份工作。我们公司的任务就是让正确的英雄去解决他/她所擅长的危机!
|
||||
|
||||
Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.
|
||||
|
||||
|
@ -125,13 +126,13 @@ figure.image-display
|
|||
1. Bind data properties to each form input control with the `ngModel` two-way data binding syntax
|
||||
1. 使用`ngModel`双向数据绑定语法把数据属性绑定到每个表单输入控件
|
||||
1. Add the **ngControl** directive to each form input control
|
||||
1. 往每个表单输入控件上添加 **ngControl** 指令
|
||||
1. 往每个表单输入控件上添加**ngControl**指令
|
||||
1. Add custom CSS to provide visual feedback
|
||||
1. 添加自定义CSS来显示视觉反馈
|
||||
1. Show and hide validation error messages
|
||||
1. 显示和隐藏有效性验证的错误信息
|
||||
1. Handle form submission with **ngSubmit**
|
||||
1. 使用 **ngSubmit** 处理表单提交
|
||||
1. 使用**ngSubmit**处理表单提交
|
||||
1. Disable the form’s submit button until the form is valid
|
||||
1. 禁用此表单的提交按钮,直到表单变为有效的
|
||||
|
||||
|
@ -150,15 +151,15 @@ include ../_quickstart_repo
|
|||
As users enter form data, we capture their changes and update an instance of a model.
|
||||
We can't layout the form until we know what the model looks like.
|
||||
|
||||
当用户输入表单数据时,我们捕获他们的更改,并且更新到模型的一个实例中。
|
||||
除非我们知道了模型里有什么,否则没办法开始设计表单。
|
||||
当用户输入表单数据时,我们要捕获他们的更改,并更新到模型的一个实例中。
|
||||
除非我们知道模型里有什么,否则没法设计表单。
|
||||
|
||||
A model can be as simple as a "property bag" that holds facts about a thing of application importance.
|
||||
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
|
||||
and one optional field (`alterEgo`).
|
||||
|
||||
最简单的模型就是一个“属性包儿”,用来存放关于应用中一个重要事物的那些事实。
|
||||
这里我们使用三个必备字段(`id`、`name`、`power`),和一个可选字段(`alterEgo`,中文含义:第二人格)。
|
||||
最简单的模型就是一个“属性包”,用来存放关于应用中一件重要事物的某些事实。
|
||||
这里我们使用三个必备字段(`id`、`name`、`power`),和一个可选字段(`alterEgo`,译注:中文含义:第二人格,比如X战警中的Jean/黑凤凰)。
|
||||
|
||||
Create a new file in the app folder called `hero.ts` and give it the following class definition:
|
||||
|
||||
|
@ -169,16 +170,16 @@ include ../_quickstart_repo
|
|||
:marked
|
||||
It's an anemic model with few requirements and no behavior. Perfect for our demo.
|
||||
|
||||
这是一个贫血模型,没几条规定也没什么行为。对我们的演示来说很完美。
|
||||
这是一个贫血模型,没什么要求,也没有行为。对我们的演示来说很完美。
|
||||
|
||||
The TypeScript compiler generates a public field for each `public` constructor parameter and
|
||||
assigns the parameter’s value to that field automatically when we create new heroes.
|
||||
|
||||
TypeScript编译器为每个`public`的构造函数参数创建一个公有字段,并且在创建新的英雄实例时,会把参数值自动赋给这些公有字段。
|
||||
TypeScript编译器为构造函数中每个标为`public`的参数创建一个公有字段,并在创建新的英雄实例时,把参数值自动赋给这些公有字段。
|
||||
|
||||
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
|
||||
|
||||
`alterEgo`是可选的,并且构造函数让我们可以省略掉它,注意`alterEgo?`中的问号(?)。
|
||||
`alterEgo`是可选的,构造函数让我们可以省略掉它,注意`alterEgo?`中的问号(?)。
|
||||
|
||||
We can create a new hero like this:
|
||||
|
||||
|
@ -215,13 +216,13 @@ code-example(format="").
|
|||
|
||||
Understanding this component requires only the Angular 2 concepts we’ve learned in previous chapters
|
||||
|
||||
要理解这个组件,只需要我们在前面的章节中已经学过的那些概念:
|
||||
要理解这个组件,只会用到前面章节中已经学过的那些概念:
|
||||
|
||||
1. We import the `Component` decorator from the Angular library as we usually do.
|
||||
1. 像往常一样,我们从Angular库中导入`Component`装饰器。
|
||||
|
||||
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
|
||||
1. `@Component`选择器的值`hero-form`表示我们将把此表单扔进父模板中的一个`<hero-form>`标签中。
|
||||
1. `@Component`选择器的值"hero-form"表示我们将把此表单扔进父模板中的一个`<hero-form>`标签中。
|
||||
|
||||
1. The `templateUrl` property points to a separate file for template HTML called `hero-form.component.html`.
|
||||
1. `templateUrl`属性指向一个独立的HTML模板文件,名叫`hero-form.component.html`。
|
||||
|
@ -231,18 +232,19 @@ code-example(format="").
|
|||
or perhaps expose these properties as [inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a
|
||||
parent component. None of this concerns us now and these future changes won't affect our form.
|
||||
1. 我们为`model`和`powers`定义了供演示用的假数据。
|
||||
接下来,我们可以注入一个用于获取和保存真实数据的服务,或者把这些属性暴露为[输入与输出属性](./template-syntax.html#inputs-outputs),以便绑定到父组件上。
|
||||
注意,我们现在所关注的这些变化,即使将来真的发生了,也不会影响到我们的表单。
|
||||
接下来,我们可以注入一个用于获取和保存真实数据的服务,
|
||||
或者把这些属性暴露为[输入与输出属性](./template-syntax.html#inputs-outputs),以绑定到父组件上。
|
||||
我们现在所关注的这些变更点,即使将来真的发生了,也不会影响到我们的表单。
|
||||
|
||||
1. We threw in a `diagnostic` property at the end to return a JSON representation of our model.
|
||||
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
|
||||
1. 我们在最后增加一个`diagnostic`属性,它返回我们这个模型的JSON形式。
|
||||
它会帮我们看到在开发中所做的那些事,以后会在清理时丢弃它。
|
||||
1. 我们在最后增加一个`diagnostic`属性,它返回这个模型的JSON形式。
|
||||
它会帮我们看清开发过程中发生的事,等最后做清理时我们会丢弃它。
|
||||
|
||||
Why don't we write the template inline in the component file as we often do
|
||||
elsewhere in the Developer Guide?
|
||||
|
||||
这次我们为什么不像开发人员指南中的其它地方那样写成组件的行内模板呢?
|
||||
这次我们为什么不像开发人员指南中的其它地方那样写成组件的内联模板呢?
|
||||
|
||||
There is no “right” answer for all occasions. We like inline templates when they are short.
|
||||
Most form templates won't be short. TypeScript and JavaScript files generally aren't the best place to
|
||||
|
@ -264,7 +266,7 @@ code-example(format="").
|
|||
.l-main-section
|
||||
:marked
|
||||
## Revise the *app.component.ts*
|
||||
## 修改 *app.component.ts* 文件
|
||||
## 修改*app.component.ts*文件
|
||||
|
||||
`app.component.ts` is the application's root component. It will host our new `HeroFormComponent`.
|
||||
|
||||
|
@ -286,7 +288,7 @@ code-example(format="").
|
|||
1. 导入了新的`HeroFormComponent`组件。
|
||||
|
||||
1. The `template` is simply the new element tag identified by the component's `selector` property.
|
||||
1. `template`只是简单地换成了`HeroFormComponent`的`selector`属性中指定的新元素标签。
|
||||
1. 直接把`template`的内容改成`HeroFormComponent`的`selector`属性中指定的新元素标签。
|
||||
|
||||
1. The `directives` array tells Angular that our template depends upon the `HeroFormComponent`
|
||||
which is itself a Directive (as are all Components).
|
||||
|
@ -312,15 +314,15 @@ code-example(format="").
|
|||
The *Name* `<input>` control has the HTML5 `required` attribute;
|
||||
the *Alter Ego* `<input>` control does not because `alterEgo` is optional.
|
||||
|
||||
*姓名Name* `<input>`控件具有HTML5的`required`属性;但 *Alter Ego* `<input>`控件没有,因为`alterEgo`字段是可选的。
|
||||
*Name*`<input>`控件具有HTML5的`required`属性;但 *Alter Ego* `<input>`控件没有,因为`alterEgo`字段是可选的。
|
||||
|
||||
We've got a *Submit* button at the bottom with some classes on it.
|
||||
|
||||
我们在底部有一个 *Submit* 按钮,它有一些CSS类。
|
||||
我们在底部有一个*Submit*按钮,它有一些CSS类。
|
||||
|
||||
**We are not using Angular yet**. There are no bindings. No extra directives. Just layout.
|
||||
|
||||
**我们还没有用到Angular** 。没有绑定。没有额外的指令。只做了个布局。
|
||||
**我们还没有用到Angular**。没有绑定。没有额外的指令。只做了个布局。
|
||||
|
||||
The `container`,`form-group`, `form-control`, and `btn` classes
|
||||
come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic.
|
||||
|
@ -329,7 +331,7 @@ code-example(format="").
|
|||
|
||||
`container`、`form-group`、`form-control`和`btn`类来自[Twitter Bootstrap](http://getbootstrap.com/css/)。纯粹是装饰。
|
||||
我们使用Bootstrap来打扮我们的表单。
|
||||
嘿,没有一点儿小个性的表单算个啥!
|
||||
嘿,一点儿样式儿都没有的表单算个啥!
|
||||
|
||||
.callout.is-important
|
||||
header Angular Forms Do Not Require A Style Library
|
||||
|
@ -339,7 +341,7 @@ code-example(format="").
|
|||
the styles of any external library. Angular apps can use any CSS library
|
||||
... or none at all.
|
||||
|
||||
Angular不需要`container`、`form-group`、`form-control`和`btn`类,或者来自任何第三方库的任何样式,Angular应用可以使用任何CSS库 …… 或者啥也不用。
|
||||
Angular不需要`container`、`form-group`、`form-control`和`btn`类,或者来自任何第三方库的任何样式,Angular应用可以使用任何CSS库 …… 或者啥都不用。
|
||||
|
||||
:marked
|
||||
Let's add the stylesheet.
|
||||
|
@ -358,23 +360,23 @@ ol
|
|||
.l-main-section
|
||||
:marked
|
||||
## Add Powers with ***ngFor**
|
||||
## 用 ***ngFor*** 添加超能力
|
||||
## 用***ngFor***添加超能力
|
||||
Our hero may choose one super power from a fixed list of Agency-approved powers.
|
||||
We maintain that list internally (in `HeroFormComponent`).
|
||||
|
||||
我们的英雄可以从经过职业介绍所认证过的固定列表中选择一项超能力。
|
||||
我们的英雄可以从由职介中心认证过的固定列表中选择一项超能力。
|
||||
我们先在`HeroFormComponent`中内部维护这个列表。
|
||||
|
||||
We'll add a `select` to our
|
||||
form and bind the options to the `powers` list using `ngFor`,
|
||||
a technique we might have seen before in the [Displaying Data](./displaying-data.html) chapter.
|
||||
|
||||
我们将添加一个`select`到表单中,并且通过`ngFor`把`powers`列表绑定到候选项(options)。
|
||||
我们将添加一个`select`到表单中,并且用`ngFor`把`powers`列表绑定到`option`中。
|
||||
前面我们应该在[显示数据](./displaying-data.html)一章中见过`ngFor`。
|
||||
|
||||
Add the following HTML *immediately below* the *Alter Ego* group.
|
||||
|
||||
在 *Alter Ego* 的紧下方添加如下的HTML:
|
||||
在*Alter Ego*的紧下方添加如下HTML:
|
||||
+makeExample('forms/ts/app/hero-form.component.html', 'powers', 'app/hero-form.component.html (节选)')(format=".")
|
||||
|
||||
:marked
|
||||
|
@ -382,17 +384,17 @@ ol
|
|||
The `p` template input variable is a different power in each iteration;
|
||||
we display its name using the interpolation syntax with the double-curly-braces.
|
||||
|
||||
我们为列表中的每一项超能力重复渲染`<option>`标签。
|
||||
`#p`局部模板变量在每个迭代中都代表一个不同的超能力;我们使用双花括号中的插值表达式语法来显示它的名称。
|
||||
我们为列表中的每一项超能力重复渲染出`<option>`标签。
|
||||
模板输入变量`p`在每个迭代中都代表一个不同的超能力,我们使用双花括号中的插值表达式语法来显示它的名称。
|
||||
|
||||
<a id="ngModel"></a>
|
||||
.l-main-section
|
||||
:marked
|
||||
## Two-way data binding with **ngModel**
|
||||
## 使用 **ngModel** 进行双向数据绑定
|
||||
## 使用**ngModel**进行双向数据绑定
|
||||
Running the app right now would be disappointing.
|
||||
|
||||
现在就运行此应用将会失败。
|
||||
如果立即运行此应用,你将会失望。
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/forms/hero-form-3.png" width="400px" alt="没有数据绑定的早期表单")
|
||||
|
@ -403,21 +405,21 @@ figure.image-display
|
|||
[User Input](./user-input.html) showed us how to listen for DOM events with an
|
||||
Event Binding and how to update a component property with the displayed value.
|
||||
|
||||
我们没有看到英雄的数据,因为我们还没有绑定到`Hero`。
|
||||
从以前的章节中,我们知道该怎么做。
|
||||
我们没有看到英雄的数据,这是因为还没有绑定到`Hero`。
|
||||
从以前的章节中,我们知道该怎么解决。
|
||||
[显示数据](./displaying-data.html)教会我们属性绑定。
|
||||
[用户输入](./user-input.html)告诉我们如何通过事件绑定来监听DOM事件,以及如何用所显示的值更新组件的属性。
|
||||
|
||||
Now we need to display, listen, and extract at the same time.
|
||||
|
||||
现在,我们需要在同样的时机进行显示、监听和提取。
|
||||
现在,我们需要同时进行显示、监听和提取。
|
||||
|
||||
We could use those techniques again in our form.
|
||||
Instead we'll introduce something new, the `[(ngModel)]` syntax, that
|
||||
makes binding our form to the model super-easy.
|
||||
|
||||
我们可以再次在表单中使用这些技术。
|
||||
但我们也可以引入一些新东西 —— `[(ngModel)]`语法,它使用一种超级简单的方式把我们的表单绑定到模型。
|
||||
虽然可以在表单中再次使用这些技术。
|
||||
但这次我们将引入一些新东西 —— `[(ngModel)]`语法,它使用一种超简单的方式把我们的表单绑定到模型。
|
||||
|
||||
Find the `<input>` tag for the "Name" and update it like this
|
||||
|
||||
|
@ -431,8 +433,8 @@ figure.image-display
|
|||
so we can see what we're doing.
|
||||
We left ourselves a note to throw it away when we're done.
|
||||
|
||||
我们在input标签后添加一个诊断用的插值表达式,以便看清楚我们正在做什么。
|
||||
等做完之后,我们别忘了移除它。
|
||||
我们在input标签后添加一个诊断用的插值表达式,以看清正在发生什么事。
|
||||
等做完之后,别忘了移除它。
|
||||
|
||||
:marked
|
||||
Focus on the binding syntax: `[(ngModel)]="..."`.
|
||||
|
@ -444,7 +446,7 @@ figure.image-display
|
|||
from the interpolated text.
|
||||
At some point it might look like this.
|
||||
|
||||
如果我们现在运行这个应用,并且开始在 *姓名* 输入框中键入、添加和删除字符,我们将看到它们从插值结果中显示和消失。
|
||||
如果我们现在运行这个应用,并且开始在*姓名*输入框中键入、添加和删除字符,我们将看到它们从插值结果中显示和消失。
|
||||
某一瞬间,它看起来可能是这样。
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="操作中的ngModel")
|
||||
|
@ -452,21 +454,21 @@ figure.image-display
|
|||
The diagnostic is evidence that we really are flowing values from the input box to the model and
|
||||
back again. **That's two-way data binding!**
|
||||
|
||||
诊断信息是一个证据,用来表明数据从输入框流动到模型,再反向流动回来的过程。 **这就是双向数据绑定**
|
||||
诊断信息是一个证据,用来表明数据从输入框流动到模型,再反向流动回来的过程。**这就是双向数据绑定!**
|
||||
|
||||
Let's add similar `[(ngModel)]` bindings to *Alter Ego* and *Hero Power*.
|
||||
We'll ditch the input box binding message
|
||||
and add a new binding at the top to the component's `diagnostic` property.
|
||||
Then we can confirm that two-way data binding works *for the entire Hero model*.
|
||||
|
||||
让我们用类似的方式添加`[(ngModel)]`到 *alterEgo* 和 *power* 属性。
|
||||
我们将抛弃输入框的绑定消息,并在组件顶部的`diagnostic`属性上添加一个新的绑定。
|
||||
然后我们能确认双向数据绑定 *在整个Hero模型上* 都是能工作的。
|
||||
让我们用类似的方式把`[(ngModel)]`绑定添加到*第二人格*和*超能力*属性。
|
||||
我们将抛弃输入框的绑定消息,并在组件顶部添加一个到`diagnostic`的新绑定。
|
||||
这样我们能确认双向数据绑定*在整个Hero模型上*都能正常工作了。
|
||||
|
||||
After revision the core of our form should have three `[(ngModel)]` bindings that
|
||||
look much like this:
|
||||
|
||||
修改了核心之后的表单有三个`[(ngModel)]`绑定,看起来像这样:
|
||||
修改之后的表单,其核心是三个`[(ngModel)]`绑定,看起来像这样:
|
||||
|
||||
+makeExample('forms/ts/app/hero-form.component.html', 'ngModel-2', 'app/hero-form.component.html (节选)')
|
||||
|
||||
|
@ -484,7 +486,7 @@ figure.image-display
|
|||
|
||||
**Delete** the `{{diagnostic}}` binding at the top as it has served its purpose.
|
||||
|
||||
表单顶部的`{{diagnostic}}`绑定表达式已经完成了它的使命, **删除** 它。
|
||||
表单顶部的`{{diagnostic}}`绑定表达式已经完成了它的使命,**删除**它。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -503,7 +505,7 @@ figure.image-display
|
|||
|
||||
在属性绑定中,一个值从模型中传到屏幕上的目标属性。
|
||||
我们通过把名字括在方括号中来标记出目标属性,<span style="font-family:courier"><b>[]</b></span>。
|
||||
这是一个 **从模型到视图** 的单向数据绑定。
|
||||
这是一个**从模型到视图**的单向数据绑定。
|
||||
|
||||
In an Event Binding, we flow the value from the target property on screen to the model.
|
||||
We identify that target property by surrounding its name in parentheses, <span style="font-family:courier"><b>()</b></span>.
|
||||
|
@ -511,7 +513,7 @@ figure.image-display
|
|||
|
||||
在事件绑定中,值从屏幕上的目标属性传到模型中。
|
||||
我们通过把名字括在圆括号中来标记出目标属性,<span style="font-family:courier"><b>()</b></span>。
|
||||
这是一个 **从视图到模型** 的反向单向数据绑定。
|
||||
这是一个**从视图到模型**的反向单向数据绑定。
|
||||
|
||||
No wonder Angular chose to combine the punctuation as <span style="font-family:courier"><b>[()]</b></span>
|
||||
to signify a two-way data binding and a **flow of data in both directions**.
|
||||
|
@ -527,14 +529,14 @@ figure.image-display
|
|||
:marked
|
||||
<br>The Property Binding should feel familiar. The Event Binding might seem strange.
|
||||
|
||||
<br>这个属性绑定看起来有点熟。这个事件绑定看起来有点怪。
|
||||
<br>这个属性绑定看起来很眼熟,但事件绑定看起来有点怪。
|
||||
|
||||
The `ngModelChange` is not an `<input>` element event.
|
||||
It is actually an event property of the `NgModel` directive.
|
||||
When Angular sees a binding target in the form <span style="font-family:courier">[(x)]</span>,
|
||||
it expects the `x` directive to have an `x` input property and an `xChange` output property.
|
||||
|
||||
`ngModelChange`不是一个`<input>`元素的事件。
|
||||
`ngModelChange`并不是`<input>`元素的事件。
|
||||
它实际上是一个来自`ngModel`指令的事件属性。
|
||||
当Angular在表单中看到一个<span style="font-family:courier">[(x)]</span>的绑定目标时,
|
||||
它会期待这个`x`指令有一个名为`x`的输入属性,和一个名为`xChange`的输出属性。
|
||||
|
@ -546,27 +548,27 @@ figure.image-display
|
|||
we should assign to the model's `name` property.
|
||||
|
||||
模板表达式中的另一个古怪之处是`model.name = $event`。
|
||||
我们以前看到的$event变量是来自DOM事件的。
|
||||
但`ngModelChange`属性不处理DOM事件;它是一个Angular`EventEmitter`类型的属性,当它触发时,
|
||||
我们以前看到的`$event`变量是来自DOM事件的。
|
||||
但`ngModelChange`属性不处理DOM事件 —— 它是一个Angular `EventEmitter`类型的属性,当它触发时,
|
||||
它返回的是输入框的值 —— 它恰好和我们希望赋给模型上`name`属性的值一样。
|
||||
|
||||
Nice to know but is it practical? We almost always prefer `[(ngModel)]`.
|
||||
We might split the binding if we had to do something special in
|
||||
the event handling such as debounce or throttle the key strokes.
|
||||
|
||||
很高兴知道这些,但现实中呢?实践上我们几乎总是优先使用 `[(ngModel)]` 形式的双向绑定。
|
||||
很高兴知道这些,但现实中呢?实践上我们几乎总是优先使用`[(ngModel)]`形式的双向绑定。
|
||||
只有当我们不得不在事件处理函数中做一些特别的事情(比如合并或限制按键频率)时,才需要拆分出独立的事件处理函数。
|
||||
|
||||
Learn more about `NgModel` and other template syntax in the
|
||||
[Template Syntax](./template-syntax.html) chapter.
|
||||
|
||||
要学习更多关于`ngModel`和其它模板语法的知识,请参见
|
||||
要学习关于`ngModel`和其它模板语法的更多知识,请参见
|
||||
[模板语法](./template-syntax.html)一章。
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Track change-state and validity with **ngControl**
|
||||
## 通过 **ngControl** 跟踪修改状态与有效性验证
|
||||
## 通过**ngControl**跟踪修改状态与有效性验证
|
||||
|
||||
A form isn't just about data binding. We'd also like to know the state of the controls on our form.
|
||||
|
||||
|
@ -582,19 +584,19 @@ figure.image-display
|
|||
We can leverage those class names to change the appearance of the
|
||||
control and make messages appear or disappear.
|
||||
|
||||
这个指令不仅仅跟踪状态,它还会使用上面所列的特殊CSS类更新此控件。
|
||||
这个指令不仅仅跟踪状态,它还会使用上面列出的这些特殊CSS类更新此控件。
|
||||
我们可以通过定制这些CSS类的样式来更改控件的外观,以及让消息被显示或隐藏。
|
||||
|
||||
We'll explore those effects soon. Right now
|
||||
we should **add `ngControl` to all three form controls**,
|
||||
starting with the *Name* input box
|
||||
|
||||
我们很快就会看到那些效果。现在,我们得先 **把`ngControl`添加到所有这三个表单控件中** ,就从 *姓名* 输入框开始吧:
|
||||
我们很快就会看到那些效果。现在,我们得先**把`ngControl`添加到所有这三个表单控件中**,就从*姓名*输入框开始吧:
|
||||
+makeExample('forms/ts/app/hero-form.component.html', 'ngControl-1', 'app/hero-form.component.html (节选)')(format=".")
|
||||
:marked
|
||||
We set this particular `ngControl` to "name" which makes sense for our app. Any unique value will do.
|
||||
|
||||
对本应用来说,把这个`ngControl`设置为"name"会更容易理解。但也可以设置为任何值,只要它是唯一的。
|
||||
对本应用来说,把这个`ngControl`设置为"name"会更容易理解。但也可以设置成任何唯一的值。
|
||||
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -602,7 +604,7 @@ figure.image-display
|
|||
with an `NgForm` directive that Angular attached to the `<form>` tag.
|
||||
We'll talk about `NgForm` [later in the chapter](#ngForm).
|
||||
|
||||
Angular会在内部创建`Controls`,并以`ngControl`的值作为名字,注册到`NgForm`指令中,这个指令被Angular自动附加到了`<form>`标签上。
|
||||
Angular会在内部创建`Controls`,并以`ngControl`的值为名字注册到`NgForm`指令中,这个指令被Angular自动附加到了`<form>`标签上。
|
||||
我们将在[稍后的章节](#ngForm)中展开讨论`NgForm`。
|
||||
|
||||
The `ngControl` *attribute* in our template actually maps to the
|
||||
|
@ -610,17 +612,17 @@ figure.image-display
|
|||
There is also a `NgControl` *abstract* directive which is *not the same thing*.
|
||||
We often ignore this technical distinction and refer to `NgControlName` more conveniently (albeit incorrectly) as the *NgControl* directive.
|
||||
|
||||
我们模板中的这个`ngControl` *属性* 实际上被映射到了 [NgControlName](../api/common/NgControlName-directive.html)指令。
|
||||
虽然还有另一个叫`NgControl`的 *抽象* 指令,但和这个属性不是一回事。
|
||||
我们通常会忽略这点技术上的差异。为了更方便(尽管不正确),我们提起`NgControlName`时还是会把它叫做 *NgControl* 指令。
|
||||
我们模板中的这个`ngControl`*属性*实际上被映射到了 [NgControlName](../api/common/NgControlName-directive.html)指令。
|
||||
虽然还有另一个叫`NgControl`的*抽象*指令,但和这个属性不是一回事。
|
||||
我们通常会忽略这点技术上的差异。为了更方便(尽管不正确),我们提起`NgControlName`时还是会把它叫做*NgControl*指令。
|
||||
|
||||
While we're under the hood, we might as well note that the `ngModel` in the
|
||||
two-way binding syntax is now a property of the `NgControlName` directive.
|
||||
The `NgModel` directive is no longer involved. We only need one directive to manage the DOM element
|
||||
and there is no practical difference in the way either directive handles data binding.
|
||||
|
||||
既然我们已经来到了技术底层,我们可能还注意到了双向绑定语法中的`ngModel`实际上是`NgControlName`指令的一个属性。
|
||||
`NgModel`指令不再需要了。我们只需要一个指令来管理DOM元素就够了,而且这两个指令处理数据绑定时都没有实质性差异。
|
||||
既然已经来到了技术底层,我们可能还注意到了双向绑定语法中的`ngModel`实际上是`NgControlName`指令的一个属性。
|
||||
`NgModel`指令不再需要了。我们只需要一个指令来管理DOM元素就够了,而且这两个指令在处理数据绑定时并没有实质性的差异。
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -630,30 +632,52 @@ figure.image-display
|
|||
The *NgControl* directive doesn't just track state.
|
||||
It updates the control with three classes that reflect the state.
|
||||
|
||||
*NgControl* 指令不止跟踪状态。它还使用三个CSS类来更新控件,以便反映当前状态。
|
||||
*NgControl*指令不止跟踪状态。它还使用三个CSS类来更新控件,以便反映当前状态。
|
||||
|
||||
table
|
||||
tr
|
||||
th State<br/>状态
|
||||
th Class if true<br/>有此CSS类
|
||||
th Class if false<br/>无此CSS类
|
||||
th
|
||||
p State
|
||||
p 状态
|
||||
th
|
||||
p Class if true
|
||||
p 有此CSS类
|
||||
th
|
||||
p Class if false
|
||||
p 无此CSS类
|
||||
tr
|
||||
td Control has been visited<br/>控件已经被访问过
|
||||
td <code>ng-touched</code>
|
||||
td <code>ng-untouched</code>
|
||||
td
|
||||
p Control has been visited
|
||||
p 控件已经被访问过
|
||||
td
|
||||
p <code>ng-touched</code>
|
||||
p <code>ng-touched</code>
|
||||
td
|
||||
p <code>ng-untouched</code>
|
||||
tr
|
||||
td Control's value has changed<br/>控件值已经变化
|
||||
td <code>ng-dirty</code>
|
||||
td <code>ng-pristine</code>
|
||||
td
|
||||
p Control's value has changed
|
||||
p 控件值已经变化
|
||||
td
|
||||
p <code>ng-dirty</code>
|
||||
p <code>ng-dirty</code>
|
||||
td
|
||||
p <code>ng-pristine</code>
|
||||
p <code>ng-pristine</code>
|
||||
tr
|
||||
td Control's value is valid<br/>控件值是有效的
|
||||
td <code>ng-valid</code>
|
||||
td <code>ng-invalid</code>
|
||||
td
|
||||
p Control's value is valid
|
||||
p 控件值是有效的
|
||||
td
|
||||
p <code>ng-valid</code>
|
||||
p <code>ng-valid</code>
|
||||
td
|
||||
p <code>ng-invalid</code>
|
||||
:marked
|
||||
Let's add a temporary [template reference variable](./template-syntax.html#ref-vars) named **spy**
|
||||
to the "Name" `<input>` tag and use the spy to display those classes.
|
||||
|
||||
我们往姓名`<input>`标签上添加一个名叫 **spy** 的临时[模板引用变量](./template-syntax.html#local-vars),然后用这个spy来显示它上面的所有css类。
|
||||
我们往姓名`<input>`标签上添加一个名叫**spy**的临时[模板引用变量](./template-syntax.html#local-vars),然后用这个spy来显示它上面的所有css类。
|
||||
|
||||
+makeExample('forms/ts/app/hero-form.component.html', 'ngControl-2','app/hero-form.component.html (节选)')(format=".")
|
||||
|
||||
|
@ -661,8 +685,8 @@ table
|
|||
Now run the app and focus on the *Name* input box.
|
||||
Follow the next four steps *precisely*
|
||||
|
||||
现在,运行本应用,并且让 *姓名* 输入框获得焦点。
|
||||
并严格按照下面四个步骤来做:
|
||||
现在,运行本应用,并让*姓名*输入框获得焦点。
|
||||
然后严格按照下面四个步骤来做:
|
||||
|
||||
1. Look but don't touch
|
||||
1. 查看输入框,但别碰它
|
||||
|
@ -681,7 +705,7 @@ figure.image-display
|
|||
:marked
|
||||
We should be able to see the following four sets of class names and their transitions:
|
||||
|
||||
我们应该能看到下列四组类名和它们的变化:
|
||||
我们应该能看到下列四组类名以及它们的变迁:
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" width="400px" alt="控件状态转换")
|
||||
|
||||
|
@ -693,7 +717,7 @@ figure.image-display
|
|||
|
||||
We realize we can do both at the same time with a colored bar on the left of the input box:
|
||||
|
||||
我们意识到其实能同时满足这两个要求,只要在输入框的左侧添加一个带颜色的竖条就可以了。
|
||||
我们意识到,其实能同时满足这两个要求,只要在输入框的左侧添加一个带颜色的竖条就可以了。
|
||||
|
||||
figure.image-display
|
||||
img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="无效表单")
|
||||
|
@ -708,7 +732,7 @@ figure.image-display
|
|||
:marked
|
||||
These styles select for the two Angular validity classes and the HTML 5 "required" attribute.
|
||||
|
||||
这些样式的选择器是这两个Angular有效性类和HTML5的“required”属性。
|
||||
这些样式的选择器是这两个Angular有效性类和HTML5的“required” Attribute。
|
||||
|
||||
We update the `<head>` of the `index.html` to include this style sheet.
|
||||
|
||||
|
@ -726,7 +750,7 @@ figure.image-display
|
|||
don't know *what* is wrong or what to do about it.
|
||||
We can leverage the `ng-invalid` class to reveal a helpful message.
|
||||
|
||||
姓名输入框是必填的,清空它会让左侧的条变红。这表示 *某些东西* 是错的,但我们不知道错在哪里,或者如何纠正。
|
||||
“姓名”输入框是必填的,清空它会让左侧的条变红。这表示*某些东西*是错的,但我们不知道错在哪里,或者如何纠正。
|
||||
我们可以借助`ng-invalid`类来给出一个更有用的消息。
|
||||
|
||||
Here's the way it should look when the user deletes the name:
|
||||
|
@ -754,7 +778,7 @@ figure.image-display
|
|||
We need a template reference variable to access the input box's Angular control from within the template.
|
||||
Here we created a variable called `name` and gave it the value "ngForm".
|
||||
|
||||
我们需要一个局部模板变量来访问模板中输入框的Angular控件。
|
||||
我们需要一个模板引用变量来访问模板中输入框的Angular控件。
|
||||
这里,我们创建了一个名叫`name`的变量,并且把它赋值为"ngForm"。
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -772,14 +796,14 @@ figure.image-display
|
|||
and we only ever apply *one* of these directives to an element tag.
|
||||
Consistency rules!
|
||||
|
||||
起初这看起来不直观,直到我们发现Angular表单一族的 *所有* 控件指令 —— 包括`NgForm`、`NgModel`、`NgControlName`和`NgControlGroup` —— 都
|
||||
*exportAs* 了 “ngForm”,
|
||||
原来我们刚才不过是把这些指令 *之一* 用在了一个元素标签上。
|
||||
起初这看起来不够直观,直到我们看到Angular表单家族下的*所有*控件指令 —— 包括`NgForm`、
|
||||
`NgModel`、`NgControlName`和`NgControlGroup` —— 都*exportAs*了 “ngForm”就会明白,
|
||||
原来我们刚才不过是把这些指令*中的一个*用在了一个元素标签上。
|
||||
一致性统治一切!
|
||||
|
||||
Now we can control visibility of the "name" error message by binding properties of the `name` control to the message `<div>` element's `hidden` property.
|
||||
|
||||
现在,通过把用于显示消息的 `div` 元素的`hidden`属性绑定到`name`控件的这些属性,我们就可以控制“姓名”字段错误信息的可见性了。
|
||||
Now we can control visibility of the "name" error message by binding properties of the `name` control to the message `<div>` element's `hidden` property.
|
||||
|
||||
现在,通过把`div`元素的`hidden`属性绑定到`name`控件的属性,我们就可以控制“姓名”字段错误信息的可见性了。
|
||||
+makeExample('forms/ts/app/hero-form.component.html',
|
||||
'hidden-error-msg',
|
||||
'app/hero-form.component.html (节选)')
|
||||
|
@ -787,36 +811,36 @@ figure.image-display
|
|||
In this example, we hide the message when the control is valid or pristine;
|
||||
pristine means the user hasn't changed the value since it was displayed in this form.
|
||||
|
||||
这个范例中,当控件是有效或全新(pristine)的时,我们要隐藏消息;全新意味着从它被显示在表单中开始,用户还从未修改过它的值。
|
||||
这个范例中,当控件是有效或全新(pristine)的时,我们要隐藏消息。“全新”意味着从它被显示在表单中开始,用户还从未修改过它的值。
|
||||
|
||||
This user experience is the developer's choice. Some folks want to see the message at all times.
|
||||
If we ignore the `pristine` state, we would hide the message only when the value is valid.
|
||||
If we arrive in this component with a new (blank) hero or an invalid hero,
|
||||
we'll see the error message immediately, before we've done anything.
|
||||
|
||||
这种用户体验取决于开发人员的选择。有些人希望任何时候都显示这条消息。
|
||||
如果我们忽略了`pristine`状态,我们就会只在值有效时隐藏此消息。
|
||||
如果往这个组件传入了一个全新(空白)的英雄,或者一个无效的英雄,我们将立刻看到错误信息 —— 虽然我们还啥也没做。
|
||||
这种用户体验取决于开发人员的选择。有些人会希望任何时候都显示这条消息。
|
||||
如果忽略了`pristine`状态,我们就会只在值有效时隐藏此消息。
|
||||
如果往这个组件中传入一个全新(空白)的英雄,或者一个无效的英雄,我们将立刻看到错误信息 —— 虽然我们还啥都没做。
|
||||
|
||||
Some folks find that behavior disconcerting. They only want to see the message when the user makes an invalid change.
|
||||
Hiding the message while the control is "pristine" achieves that goal.
|
||||
We'll see the significance of this choice when we [add a new hero](#new-hero) to the form.
|
||||
|
||||
有些人会为这种行为感到不安。他们希望只有在用户做出一个无效的更改时才显示这个消息。如果当控件是全新的时也隐藏消息就能达到这个目标。
|
||||
我们将在往表单中[添加一个新英雄](#new-hero)时看到这种选择的重要性。
|
||||
有些人会为这种行为感到不安。他们希望只有在用户做出一个无效的更改时才显示这个消息。如果当控件是“全新”状态时也隐藏消息,就能达到这个目的。
|
||||
在往表单中[添加一个新英雄](#new-hero)时,我们将看到这种选择的重要性。
|
||||
|
||||
The Hero *Alter Ego* is optional so we can leave that be.
|
||||
|
||||
英雄的 *另一面* 是可选项,所以我们不填它。
|
||||
英雄的*第二人格*是可选项,所以我们不填它。
|
||||
|
||||
Hero *Power* selection is required.
|
||||
We can add the same kind of error handling to the `<select>` if we want
|
||||
but it's not imperative because the selection box already constrains the
|
||||
power to valid value.
|
||||
|
||||
英雄的 *超能力* 选项是必填的。
|
||||
英雄的*超能力*选项是必填的。
|
||||
可以往`<select>`上添加相同的错误处理,只要我们希望如此。
|
||||
但是这没有那么迫切,因为这个选择框已经把“超能力”约束成了有效值。
|
||||
但是这没有那么迫切,因为这个选择框已经足够把“超能力”约束成有效值了。
|
||||
|
||||
<a id="new-hero"></a>
|
||||
<a id="reset"></a>
|
||||
|
@ -842,22 +866,22 @@ figure.image-display
|
|||
That's understandable as these are required fields.
|
||||
The error messages are hidden because the form is pristine; we haven't changed anything yet.
|
||||
|
||||
再次运行应用,点击 *新增英雄* 按钮,表单被清空了。
|
||||
输入框左侧的 *必填项* 竖条是红色的,表示`name`和`power`属性是无效的。
|
||||
再次运行应用,点击*新增英雄*按钮,表单被清空了。
|
||||
输入框左侧的*必填项*竖条是红色的,表示`name`和`power`属性是无效的。
|
||||
对三个必填字段来说,这种方式清晰易懂。
|
||||
错误信息是隐藏的,这是因为表单还是全新的,我们还没有修改任何东西。
|
||||
|
||||
Enter a name and click *New Hero* again.
|
||||
This time we see an error message! Why? We don't want that when we display a new (empty) hero.
|
||||
|
||||
输入一个名字,并且再次点击 *新增英雄* 按钮。
|
||||
输入一个名字,并再次点击*新增英雄*按钮。
|
||||
这次,我们看到了错误信息!为什么?当我们显示一个新(空白)的英雄时,我们不希望如此。
|
||||
|
||||
Inspecting the element in the browser tools reveals that the *name* input box is no longer pristine.
|
||||
Replacing the hero *did not restore the pristine state* of the control.
|
||||
|
||||
使用浏览器工具审查这个元素就会发现,这个 *姓名* 输入框并不是全新的。
|
||||
更换了英雄 *并不会重置控件的“全新”状态* 。
|
||||
使用浏览器工具审查这个元素就会发现,这个*姓名*输入框并不是全新的。
|
||||
更换了英雄*并不会重置控件的“全新”状态*。
|
||||
.l-sub-section
|
||||
:marked
|
||||
Upon reflection, we realize that Angular cannot distinguish between
|
||||
|
@ -889,33 +913,33 @@ figure.image-display
|
|||
clicking "New Hero" removes the form from the DOM and recreates it in a blink of an eye.
|
||||
The re-created form is in a pristine state. The error message is hidden.
|
||||
|
||||
在通过`NgIf`绑定到`active`标志之后,点击“新增英雄”将从DOM中移除这个表单,并且在一眨眼的功夫重建它。
|
||||
在通过`NgIf`绑定到`active`标志之后,点击“新增英雄”将从DOM中移除这个表单,并在一眨眼的功夫重建它。
|
||||
重新创建的表单处于“全新”状态。错误信息被隐藏了。
|
||||
.l-sub-section
|
||||
:marked
|
||||
This is a temporary workaround while we await a proper form reset feature.
|
||||
|
||||
这只是一个临时的变通方案,将来我们会有一个更合适的方案来重置表单。
|
||||
这只是一个临时的变通方案,将来我们还会有一个更合适的方案来重置表单。
|
||||
:marked
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
## Submit the form with **ngSubmit**
|
||||
## 通过 **ngSubmit** 来提交表单
|
||||
## 通过**ngSubmit**来提交表单
|
||||
The user should be able to submit this form after filling it in.
|
||||
The Submit button at the bottom of the form
|
||||
does nothing on its own but it will
|
||||
trigger a form submit because of its type (`type="submit"`).
|
||||
|
||||
在填写完之后,用户还应该能提交这个表单。
|
||||
提交按钮位于表单的底部,它自己不会做任何事,但因为它特殊的type值(`type="submit"`),它会触发一次表单提交。
|
||||
“提交”按钮位于表单的底部,它自己不会做任何事,但因为具有特殊的type值(`type="submit"`),所以它会触发表单提交。
|
||||
|
||||
A "form submit" is useless at the moment.
|
||||
To make it useful, we'll update the `<form>` tag with another Angular directive, `NgSubmit`,
|
||||
and bind it to the `HeroFormComponent.submit()` method with an event binding
|
||||
|
||||
仅仅触发“表单提交”目前是没用的。
|
||||
要让它有用,我们还要使用另一个Angular指令更新`<form>`标签 —— `NgSubmit`,
|
||||
仅仅触发“表单提交”在目前是没用的。
|
||||
要让它有用,我们还要用另一个Angular指令更新`<form>`标签 —— `NgSubmit`,
|
||||
并且通过事件绑定机制把它绑定到`HeroFormComponent.submit()`方法上。
|
||||
+makeExample('forms/ts/app/hero-form.component.html', 'ngSubmit')(format=".")
|
||||
|
||||
|
@ -923,11 +947,11 @@ figure.image-display
|
|||
We slipped in something extra there at the end! We defined a
|
||||
template reference variable, **`#heroForm`**, and initialized it with the value, "ngForm".
|
||||
|
||||
最后,我们发现了一些额外的东西!我们定义了一个模板引用变量 **`#heroForm`** ,并且把它初始化为"ngForm"。
|
||||
最后,我们发现了一些额外的东西!我们定义了一个模板引用变量**`#heroForm`**,并且把它初始化为"ngForm"。
|
||||
|
||||
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
|
||||
|
||||
这个`heroForm`变量现在引用的是`NgForm`指令,它代表的是作为整体的表单。
|
||||
这个`heroForm`变量现在引用的是`NgForm`指令,它代表的是表单的整体。
|
||||
<a id="ngForm"></a>
|
||||
.l-sub-section
|
||||
:marked
|
||||
|
@ -935,7 +959,7 @@ figure.image-display
|
|||
### NgForm指令
|
||||
What `NgForm` directive? We didn't add an [NgForm](../api/common/NgForm-directive.html) directive!
|
||||
|
||||
什么是`NgForm`指令?我们没有添加过[NgForm](../api/common/NgForm-directive.html)指令!
|
||||
什么`NgForm`指令?我们没有添加过[NgForm](../api/common/NgForm-directive.html)指令啊!
|
||||
|
||||
Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically.
|
||||
|
||||
|
@ -948,8 +972,8 @@ figure.image-display
|
|||
control* is valid.
|
||||
|
||||
`NgForm`指令使用额外的特性扩充了`form`元素。
|
||||
它保存我们通过`ngControl`属性为各个元素创建的控件类,并且监视它们的属性变化,包括有效性。
|
||||
它还有自己的`valid`属性,只有当 *每一个被包含的控件* 都有效时,它才有效。
|
||||
它保存我们通过`ngControl` Attribute为各个元素创建的控件类,并且监视它们的属性变化,包括有效性。
|
||||
它还有自己的`valid`属性,只有当*每一个被包含的控件*都有效时,它才有效。
|
||||
|
||||
:marked
|
||||
Later in the template we bind the button's `disabled` property to the form's over-all validity via
|
||||
|
@ -965,16 +989,16 @@ figure.image-display
|
|||
Now delete the *Name*. We violate the "name required" rule which
|
||||
is duely noted in our error message as before. And now the Submit button is also disabled.
|
||||
|
||||
现在,删除 *姓名* 。我们违反了“必填姓名”规则,它还是像以前那样显示了错误信息来提醒我们。同时,提交按钮也被禁用了。
|
||||
现在,删除*姓名*。我们违反了“必填姓名”规则,它还是像以前那样显示了错误信息来提醒我们。同时,“提交”按钮也被禁用了。
|
||||
|
||||
Not impressed? Think about it for a moment. What would we have to do to
|
||||
wire the button's enable/disabled state to the form's validity without Angular's help?
|
||||
|
||||
没想明白?再想一会儿。如果没有Angular `NgForm`的帮助,我们该怎么让按钮的禁用/使能状态和表单的有效性关联起来呢?
|
||||
没想明白?再想一会儿。如果没有Angular `NgForm`的帮助,我们又该怎么让按钮的禁用/使能状态和表单的有效性关联起来呢?
|
||||
|
||||
For us, it was as simple as
|
||||
|
||||
对于我们来说,它非常简单:
|
||||
有了Angular,它就是这么简单:
|
||||
1. Define a template reference variable on the (enhanced) form element
|
||||
1. 定义一个模板引用变量,放在(强化过的)form元素上
|
||||
2. Reference that variable in a button some 50 lines away.
|
||||
|
@ -986,7 +1010,7 @@ figure.image-display
|
|||
## 切换两个表单区域(额外的荣誉)
|
||||
Submitting the form isn't terribly dramatic at the moment.
|
||||
|
||||
现在就提交表单还不够激动人心
|
||||
现在就提交表单还不够激动人心。
|
||||
.l-sub-section
|
||||
:marked
|
||||
An unsurprising observation for a demo. To be honest,
|
||||
|
@ -996,9 +1020,9 @@ figure.image-display
|
|||
If you're not interested, you can skip to the chapter's conclusion
|
||||
and not miss a thing.
|
||||
|
||||
对演示来说,这是一个平淡的结尾。老实说,即使让它更出彩,也无法教给我们任何关于表单的新知识。
|
||||
对演示来说,这是一个平淡的收场。老实说,即使让它更出彩,也无法教给我们任何关于表单的新知识。
|
||||
但这是一个锻炼我们新学到的绑定类技能的好机会。
|
||||
如果你不感兴趣,可以跳过本章的下面这部分,而不会错失任何东西。
|
||||
如果你不感兴趣,可以跳过本章的下面这部分,而不用担心错失任何东西。
|
||||
:marked
|
||||
Let's do something more strikingly visual.
|
||||
Let's hide the data entry area and display something else.
|
||||
|
@ -1018,7 +1042,7 @@ figure.image-display
|
|||
the `submitted` property is false until we submit the form,
|
||||
as this fragment from the `HeroFormComponent` reminds us:
|
||||
|
||||
主表单从一开始就是可见的,因为`submitted`属性是false,直到我们提交了这个表单,因为这个来自`HeroFormComponent`的片段提醒我们:
|
||||
主表单从一开始就是可见的,因为`submitted`属性是false,直到我们提交了这个表单。来自`HeroFormComponent`的代码片段告诉了我们这一点:
|
||||
|
||||
+makeExample('forms/ts/app/hero-form.component.ts', 'submitted')(format=".")
|
||||
|
||||
|
@ -1045,15 +1069,15 @@ figure.image-display
|
|||
We added an Edit button whose click event is bound to an expression
|
||||
that clears the `submitted` flag.
|
||||
|
||||
我们添加了一个编辑按钮,它的click事件被绑定到了一个表达式,它会清除`submitted`标志。
|
||||
我们添加了一个“编辑”按钮,它的click事件被绑定到了一个表达式,它会清除`submitted`标志。
|
||||
|
||||
When we click it, this block disappears and the editable form reappears.
|
||||
|
||||
当我们点它时,这个只读块消失了,可编辑的表单重新出现了。
|
||||
当我们点它时,这个只读块儿消失了,可编辑的表单重新出现了。
|
||||
|
||||
That's as much drama as we can muster for now.
|
||||
|
||||
现在,它比我们那个恰好够用的版本好看多了。
|
||||
现在,它比我们那个刚好够用的版本好玩儿多了。
|
||||
|
||||
.l-main-section
|
||||
:marked
|
||||
|
@ -1077,7 +1101,7 @@ figure.image-display
|
|||
- The `ngControlName` directive for validation and form element change tracking.
|
||||
- 用于验证和表单元素变化跟踪的`ngControlName`指令
|
||||
- The reference variable’s `valid` property on input controls to check if a control is valid and show/hide error messages.
|
||||
- 指向input控件的引用变量上的`valid`属性,用于检查控件是否有效、是否显示/隐藏错误信息。
|
||||
- 指向input控件的引用变量上的`valid`属性,可用于检查控件是否有效、是否显示/隐藏错误信息。
|
||||
- Controlling the submit button's enabled state by binding to `NgForm` validity.
|
||||
- 通过绑定到`NgForm`的有效性状态,控制提交按钮的禁用状态。
|
||||
- Custom CSS classes that provide visual feedback to users about invalid controls.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue