英雄编辑器
本地搭建环境去开发
真正的应用程序开发是发生在像你的机器一样的本地的开发环境中的。
跟随着 setup (这里是源地址,英文版的,读者也可以参考我的帖子AngularJS学习之二:配置本地开发环境)指导去创建一个新的名为angular-tour-of-heroes
的项目后,文件结构看起来就像这样:
当我们完成了这第一章节,这个app运行起来就像 live example.
保持app处于转译和运行中
(注:这里转译的原词是transpiling,这个词在百度、有道、科林斯都查不到,谷歌解释为转译,我理解为自动编译和加载)
(注:这里转译的原词是transpiling,这个词在百度、有道、科林斯都查不到,谷歌解释为转译,我理解为自动编译和加载)
我们想要启动TypeScript编译器,让它来监控变化,并且启动服务。通过在终端窗口输入下面的指令来完成这个。
npm start
这个命令以观察模式运行编译器,启动服务,在浏览器运行app,当我们继续构建英雄之旅的时候保持着app的运行。
展示我们的英雄
我们想要在我们的app中显示英雄的数据。
更新这个AppComponent
,这样它就有两个属性: atitle
属性显示应用的名字和一个hero
属性显示一个名字叫"Windstorm"的英雄。
app.component.ts (AppComponent class)
export class AppComponent {
title = 'Tour of Heroes';
hero = 'Windstorm';
}
现在使用对这些属性的数据绑定来更新在 @Component
装饰里面的模板。
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
浏览器应该刷新并且显示我们的标题和英雄。
这双重的花括号告诉我们的app去从组件中读取title
和hero
属性,并且去渲染它们。这就是单向数据绑定的“插值”模式。
在 Displaying Data chapter 去学习更多的插值内容。
英雄对象
在这个时候,我们的英雄只是一个名字。我们的英雄需要更多的属性。让我们把 hero
从一个字符串转换成一个类吧。
使用id
和 name
属性来创建 Hero 类。现在把它放在app.component.ts
文件顶部附近,仅仅在import语句下面。
app.component.ts (Hero class)
export class Hero {
id: number;
name: string;
}
现在我们有了一个 Hero
类,让我们反射组件的组件的 hero
属性为一个 Hero
类型。然后初始化它,id初始化为1,名字初始化为“Windstorm”。
app.component.ts (hero property)
hero: Hero = {
id: 1,
name: 'Windstorm'
};
因为我们把这个英雄从一个字符串转换成了一个对象,我们更新在模板中指向 name
属性的绑定。
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
浏览器刷新并且继续显示我们的英雄的名字。
增加更多的HTML
显示一个名字是好的,但是我们还想要看到我们的英雄的所有的属性。我们增加一个<div>
给我们英雄的id
属性和 另外一个<div>
给我们英雄的name
属性。
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
哦,我们的模板字符串在变长。我们最好小心一些,避免在模板中出现输入错误的风险。
多行模板字符串
我们可以使用字符串连接来制作一个更可读的模板,但是它很快会变得难看,不容易去阅读,容易制造拼写错误。取而代之的,让我们来充分利用在ES2015 和TypeScript中的模板字符串功能来保持我们的清爽。
修改围绕模板的引号为反引号,然后把这些 <h1>
, <h2>
和<div>
元素各放到一行上。
app.component.ts (AppComponent's template)
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div><label>name: </label>{{hero.name}}</div>
`
修改我们的英雄
我们希望能够在一个文本编辑英雄的名字。
像下面这样用<label>
和<input>
反射这个英雄的名字的 <label>
:
app.component.ts (input element)
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input value="{{hero.name}}" placeholder="name">
</div>
`
我们在浏览器看到这个英雄的名字显示在<input>文本框中。但是有些事感觉不对。当我们修改名字的时候,我们发现我们的修改没有影响到<h2>
。我们使用到<input>
的单向绑定将不会得到想要的结果。
双向绑定
我们尝试在<input>显示英雄的名字,修改它,并且看到这些变化,无论我们在哪里绑定了英雄的名字。简而言之,我们想要双向绑定。
在我们可以使用对于 form inputs 的双向绑定之前,我们需要在我们的Angular模块中导入FormsModule包。我们把它加入到 NgModule
装饰器的 imports
数组。这个数组包含被你的应用所使用的外部模块列表。现在我们已经引入了表单的包,这个包里面包含 ngModel
。
app.module.ts (FormsModule import)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
在 Forms 和 Template Syntax 章节可以了解更多关于 FormsModule
and ngModel
的信息。
让我们使用内建的指示符ngModel 来更新模板,从而达到双向绑定的作用。
使用以下的<input [(ngModel)]="hero.name"placeholder="name">来取代原本的<input>
。
浏览器刷新,我们又看到了我们的英雄。我们可以修改这个英雄的名字,并且立刻在 <h2>
看到反馈出来的效果。
我们已经走过的路
让我们清点一下我们已经做的事情。
- 我们的英雄之旅使用了插值的双花括号(一种单向数据绑定)来显示一个应用程序的标题和一个 Hero 对象的属性。
- 我们使用ES2015的模板字符串编写了多行模板去让我们的模板具有可读性。
- 我们使用内建的ngModel 指示符来增加对
<input>
元素的双向绑定后,可以达到显示和修改英雄名字的作用。 - 这个
ngModel
指示符还可以传播hero.name 的任何其它绑定的修改。
运行这部分的 live example 。
这是完整的 app.component.ts
,它现在这个样子:
app.component.ts
import { Component } from '@angular/core';
export class Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name">
</div>
`
})
export class AppComponent {
title = 'Tour of Heroes';
hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
前方的路
我们的英雄之旅仅仅显示了一个英雄,我们其实想要显示一个英雄列表。我们同时还想要允许用户去选择一个英雄,并且显示他的细节。我们将在 next tutorial chapter (下一个教程章节)学到更多关于如何去检索列表,把它们绑定到模板,允许用户去选择一个英雄。
以下是原文,摘自:https://angular.io/docs/ts/latest/tutorial/toh-pt1.html。
Setup to develop locally
Real application development takes place in a local development environment like your machine.
Follow the setup instructions for creating a new project named angular-tour-of-heroes
after which the file structure should look like this:
When we're done with this first episode, the app runs like this live example.
Keep the app transpiling and running
We want to start the TypeScript compiler, have it watch for changes, and start our server. Do this by entering the following command in the terminal window.
npm start
This command runs the compiler in watch mode, starts the server, launches the app in a browser, and keeps the app running while we continue to build the Tour of Heroes.
Show our Hero
We want to display Hero data in our app
Update the AppComponent
so it has two properties: a title
property for the application name and a hero
property for a hero named "Windstorm".
app.component.ts (AppComponent class)
export class AppComponent {
title = 'Tour of Heroes';
hero = 'Windstorm';
}
Now update the template in the @Component
decoration with data bindings to these new properties.
template: '<h1>{{title}}</h1><h2>{{hero}} details!</h2>'
The browser should refresh and display our title and hero.
The double curly braces tell our app to read the title
and hero
properties from the component and render them. This is the "interpolation" form of one-way data binding.
Learn more about interpolation in the Displaying Data chapter.
Hero object
At the moment, our hero is just a name. Our hero needs more properties. Let's convert the hero
from a literal string to a class.
Create a Hero
class with id
and name
properties. For now put this near the top of the app.component.ts
file, just below the import statement.
app.component.ts (Hero class)
export class Hero {
id: number;
name: string;
}
Now that we have a Hero
class, let’s refactor our component’s hero
property to be of type Hero
. Then initialize it with an id of 1
and the name, "Windstorm".
app.component.ts (hero property)
hero: Hero = {
id: 1,
name: 'Windstorm'
};
Because we changed the hero from a string to an object, we update the binding in the template to refer to the hero’s name
property.
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2>'
The browser refreshes and continues to display our hero’s name.
Adding more HTML
Displaying a name is good, but we want to see all of our hero’s properties. We’ll add a <div>
for our hero’s id
property and another <div>
for our hero’sname
.
template: '<h1>{{title}}</h1><h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div><label>name: </label>{{hero.name}}</div>'
Uh oh, our template string is getting long. We better take care of that to avoid the risk of making a typo in the template.
Multi-line template strings
We could make a more readable template with string concatenation but that gets ugly fast, it is harder to read, and it is easy to make a spelling error. Instead, let’s take advantage of the template strings feature in ES2015 and TypeScript to maintain our sanity.
Change the quotes around the template to back-ticks and put the <h1>
, <h2>
and <div>
elements on their own lines.
app.component.ts (AppComponent's template)
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div><label>name: </label>{{hero.name}}</div>
`
Editing Our Hero
We want to be able to edit the hero name in a textbox.
Refactor the hero name <label>
with <label>
and <input>
elements as shown below:
app.component.ts (input element)
template:`
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input value="{{hero.name}}" placeholder="name">
</div>
`
We see in the browser that the hero’s name does appear in the <input>
textbox. But something doesn’t feel right. When we change the name, we notice that our change is not reflected in the <h2>
. We won't get the desired behavior with a one-way binding to <input>
.
Two-Way Binding
We intend to display the name of the hero in the <input>
, change it, and see those changes wherever we bind to the hero’s name. In short, we want two-way data binding.
Before we can use two-way data binding for form inputs, we need to import the FormsModule
package in our Angular module. We add it to the NgModule
decorator's imports
array. This array contains the list of external modules used by our application. Now we have included the forms package which includes ngModel
.
app.module.ts (FormsModule import)
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
Learn more about the FormsModule
and ngModel
in the Forms and Template Syntax chapters.
Let’s update the template to use the ngModel
built-in directive for two-way binding.
Replace the <input>
with the following HTML
<input [(ngModel)]="hero.name" placeholder="name">
The browser refreshes. We see our hero again. We can edit the hero’s name and see the changes reflected immediately in the <h2>
.
The Road We’ve Travelled
Let’s take stock of what we’ve built.
- Our Tour of Heroes uses the double curly braces of interpolation (a kind of one-way data binding) to display the application title and properties of a
Hero
object. - We wrote a multi-line template using ES2015’s template strings to make our template readable.
- We can both display and change the hero’s name after adding a two-way data binding to the
<input>
element using the built-inngModel
directive. - The
ngModel
directive also propagates changes to every other binding of thehero.name
.
Run the live example for this part.
Here's the complete app.component.ts
as it stands now:
app.component.ts
import { Component } from '@angular/core';
export class Hero {
id: number;
name: string;
}
@Component({
selector: 'my-app',
template: `
<h1>{{title}}</h1>
<h2>{{hero.name}} details!</h2>
<div><label>id: </label>{{hero.id}}</div>
<div>
<label>name: </label>
<input [(ngModel)]="hero.name" placeholder="name">
</div>
`
})
export class AppComponent {
title = 'Tour of Heroes';
hero: Hero = {
id: 1,
name: 'Windstorm'
};
}
The Road Ahead
Our Tour of Heroes only displays one hero and we really want to display a list of heroes. We also want to allow the user to select a hero and display their details. We’ll learn more about how to retrieve lists, bind them to the template, and allow a user to select a hero in the next tutorial chapter.