填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

英雄編輯器

The hero editor

應用程式現在有了基本的標題。 接下來你要建立一個新的元件來顯示英雄資訊並且把這個元件放到應用程式的外殼裡去。

The application now has a basic title. Next you will create a new component to display hero information and place that component in the application shell.

要檢視本頁所講的範例程式,參閱現場演練 / 下載範例

For the sample app that this page describes, see the現場演練 / 下載範例.

建立英雄列表元件

Create the heroes component

使用 Angular CLI 建立一個名為 heroes 的新元件。

Using the Angular CLI, generate a new component named heroes.

ng generate component heroes
      
      ng generate component heroes
    

CLI 建立了一個新的資料夾 src/app/heroes/,並生成了 HeroesComponent 的四個檔案。

The CLI creates a new folder, src/app/heroes/, and generates the three files of the HeroesComponent along with a test file.

HeroesComponent 的類別檔案如下:

The HeroesComponent class file is as follows:

import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { constructor() { } ngOnInit() { } }
app/heroes/heroes.component.ts (initial version)
      
      import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}
    

你要從 Angular 核心函式庫中匯入 Component 符號,並為元件類別加上 @Component 裝飾器。

You always import the Component symbol from the Angular core library and annotate the component class with @Component.

@Component 是個裝飾器函式,用於為該元件指定 Angular 所需的元資料。

@Component is a decorator function that specifies the Angular metadata for the component.

CLI 自動生成了三個元資料屬性:

The CLI generated three metadata properties:

  1. selector— 元件的選擇器(CSS 元素選擇器)

    selector— the component's CSS element selector

  2. templateUrl— 元件範本檔案的位置。

    templateUrl— the location of the component's template file.

  3. styleUrls— 元件私有 CSS 樣式表文件的位置。

    styleUrls— the location of the component's private CSS styles.

CSS 元素選擇器 app-heroes 用來在父元件的範本中匹配 HTML 元素的名稱,以識別出該元件。

The CSS element selector, 'app-heroes', matches the name of the HTML element that identifies this component within a parent component's template.

ngOnInit() 是一個生命週期鉤子,Angular 在建立完元件後很快就會呼叫 ngOnInit()。這裡是放置初始化邏輯的好地方。

The ngOnInit() is a lifecycle hook. Angular calls ngOnInit() shortly after creating a component. It's a good place to put initialization logic.

始終要 export 這個元件類別,以便在其它地方(比如 AppModule)匯入它。

Always export the component class so you can import it elsewhere ... like in the AppModule.

新增 hero 屬性

Add a hero property

HeroesComponent 中新增一個 hero 屬性,用來表示一個名叫 “Windstorm” 的英雄。

Add a hero property to the HeroesComponent for a hero named "Windstorm."

hero = 'Windstorm';
heroes.component.ts (hero property)
      
      hero = 'Windstorm';
    

顯示英雄

Show the hero

開啟範本檔案 heroes.component.html。刪除 Angular CLI 自動產生的預設內容,改為到 hero 屬性的資料繫結。

Open the heroes.component.html template file. Delete the default text generated by the Angular CLI and replace it with a data binding to the new hero property.

{{hero}}
heroes.component.html
      
      {{hero}}
    

顯示 HeroesComponent 檢視

Show the HeroesComponent view

要顯示 HeroesComponent 你必須把它加到殼元件 AppComponent 的範本中。

To display the HeroesComponent, you must add it to the template of the shell AppComponent.

別忘了,app-heroes 就是 HeroesComponent元素選擇器。 所以,只要把 <app-heroes> 元素新增到 AppComponent 的範本檔案中就可以了,就放在標題下方。

Remember that app-heroes is the element selector for the HeroesComponent. So add an <app-heroes> element to the AppComponent template file, just below the title.

<h1>{{title}}</h1> <app-heroes></app-heroes>
src/app/app.component.html
      
      <h1>{{title}}</h1>
<app-heroes></app-heroes>
    

如果 CLI 的 ng serve 命令仍在執行,瀏覽器就會自動重新整理,並且同時顯示出應用的標題和英雄的名字。

Assuming that the CLI ng serve command is still running, the browser should refresh and display both the application title and the hero name.

建立 Hero 類別

Create a Hero interface

真實的英雄當然不止一個名字。

A real hero is more than a name.

src/app 資料夾中為 Hero 類別建立一個檔案,並新增 idname 屬性。

Create a Hero interface in its own file in the src/app folder. Give it id and name properties.

export interface Hero { id: number; name: string; }
src/app/hero.ts
      
      export interface Hero {
  id: number;
  name: string;
}
    

回到 HeroesComponent 類別,並且匯入這個 Hero 類別。

Return to the HeroesComponent class and import the Hero interface.

把元件的 hero 屬性的型別重構為 Hero。 然後以 1id、以 “Windstorm” 為名字初始化它。

Refactor the component's hero property to be of type Hero. Initialize it with an id of 1 and the name Windstorm.

修改後的 HeroesComponent 類別應該是這樣的:

The revised HeroesComponent class file should look like this:

import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { hero: Hero = { id: 1, name: 'Windstorm' }; constructor() { } ngOnInit() { } }
src/app/heroes/heroes.component.ts
      
      import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };

  constructor() { }

  ngOnInit() {
  }

}
    

頁面顯示變得不正常了,因為你剛剛把 hero 從字串改成了物件。

The page no longer displays properly because you changed the hero from a string to an object.

顯示 hero 物件

Show the hero object

修改範本中的繫結,以顯示英雄的名字,並在詳情中顯示 idname,就像這樣:

Update the binding in the template to announce the hero's name and show both id and name in a details layout like this:

<h2>{{hero.name}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div><span>name: </span>{{hero.name}}</div>
heroes.component.html (HeroesComponent's template)
      
      <h2>{{hero.name}} Details</h2>
<div><span>id: </span>{{hero.id}}</div>
<div><span>name: </span>{{hero.name}}</div>
    

瀏覽器自動重新整理,並顯示這位英雄的資訊。

The browser refreshes and displays the hero's information.

使用 UppercasePipe 進行格式化

Format with the UppercasePipe

hero.name 的繫結修改成這樣:

Modify the hero.name binding like this.

<h2>{{hero.name | uppercase}} Details</h2>
src/app/heroes/heroes.component.html
      
      <h2>{{hero.name | uppercase}} Details</h2>
    

瀏覽器重新整理了。現在,英雄的名字顯示成了大寫字母。

The browser refreshes and now the hero's name is displayed in capital letters.

繫結表示式中的 uppercase 位於管道運算子( | )的右邊,用來呼叫內建管道 UppercasePipe

The word uppercase in the interpolation binding, right after the pipe operator ( | ), activates the built-in UppercasePipe.

管道 是格式化字串、金額、日期和其它顯示資料的好辦法。 Angular 發佈了一些內建管道,而且你還可以建立自己的管道。

Pipes are a good way to format strings, currency amounts, dates and other display data. Angular ships with several built-in pipes and you can create your own.

編輯英雄名字

Edit the hero

使用者應該能在一個 <input> 輸入框中編輯英雄的名字。

Users should be able to edit the hero name in an <input> textbox.

當用戶輸入時,這個輸入框應該能同時顯示修改英雄的 name 屬性。 也就是說,資料流從元件類別流出到螢幕,並且從螢幕流回到元件類別

The textbox should both display the hero's name property and update that property as the user types. That means data flows from the component class out to the screen and from the screen back to the class.

要想讓這種資料流動自動化,就要在表單元素 <input> 和元件的 hero.name 屬性之間建立雙向資料繫結。

To automate that data flow, setup a two-way data binding between the <input> form element and the hero.name property.

雙向繫結

Two-way binding

把範本中的英雄詳情區重構成這樣:

Refactor the details area in the HeroesComponent template so it looks like this:

<div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"/> </label> </div>
src/app/heroes/heroes.component.html (HeroesComponent's template)
      
      <div>
  <label>name:
    <input [(ngModel)]="hero.name" placeholder="name"/>
  </label>
</div>
    

[(ngModel)] 是 Angular 的雙向資料繫結語法。

[(ngModel)] is Angular's two-way data binding syntax.

這裡把 hero.name 屬性繫結到了 HTML 的 textbox 元素上,以便資料流可以雙向流動:從 hero.name 屬性流動到 textbox,並且從 textbox 流回到 hero.name

Here it binds the hero.name property to the HTML textbox so that data can flow in both directions: from the hero.name property to the textbox, and from the textbox back to the hero.name.

缺少 FormsModule

The missing FormsModule

注意,當你加上 [(ngModel)] 之後這個應用無法工作了。

Notice that the app stopped working when you added [(ngModel)].

開啟瀏覽器的開發工具,就會在控制檯中看到如下資訊:

To see the error, open the browser development tools and look in the console for a message like

Template parse errors: Can't bind to 'ngModel' since it isn't a known property of 'input'.
      
      Template parse errors:
Can't bind to 'ngModel' since it isn't a known property of 'input'.
    

雖然 ngModel 是一個有效的 Angular 指令,不過它在預設情況下是不可用的。

Although ngModel is a valid Angular directive, it isn't available by default.

它屬於一個可選模組 FormsModule,你必須自行新增此模組才能使用該指令。

It belongs to the optional FormsModule and you must opt-in to using it.

AppModule

Angular 需要知道如何把應用程式的各個部分組合到一起,以及該應用需要哪些其它檔案和函式庫。 這些資訊被稱為元資料(metadata)

Angular needs to know how the pieces of your application fit together and what other files and libraries the app requires. This information is called metadata.

有些元資料位於 @Component 裝飾器中,你會把它加到元件類別上。 另一些關鍵性的元資料位於 @NgModule裝飾器中。

Some of the metadata is in the @Component decorators that you added to your component classes. Other critical metadata is in @NgModuledecorators.

最重要的 @NgModule 裝飾器位於最上層類別 AppModule 上。

The most important @NgModule decorator annotates the top-level AppModule class.

Angular CLI 在建立專案的時候就在 src/app/app.module.ts 中生成了一個 AppModule 類別。 這裡也就是你要新增 FormsModule 的地方。

The Angular CLI generated an AppModule class in src/app/app.module.ts when it created the project. This is where you opt-in to the FormsModule.

匯入 FormsModule

Import FormsModule

開啟 AppModule (app.module.ts) 並從 @angular/forms 函式庫中匯入 FormsModule 符號。

Open AppModule (app.module.ts) and import the FormsModule symbol from the @angular/forms library.

import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
app.module.ts (FormsModule symbol import)
      
      import { FormsModule } from '@angular/forms'; // <-- NgModel lives here
    

然後把 FormsModule 新增到 @NgModule 元資料的 imports 陣列中,這裡是該應用所需外部模組的列表。

Then add FormsModule to the @NgModule metadata's imports array, which contains a list of external modules that the app needs.

imports: [ BrowserModule, FormsModule ],
app.module.ts (@NgModule imports)
      
      imports: [
  BrowserModule,
  FormsModule
],
    

重新整理瀏覽器,應用又能正常工作了。你可以編輯英雄的名字,並且會看到這個改動立刻體現在這個輸入框上方的 <h2> 中。

When the browser refreshes, the app should work again. You can edit the hero's name and see the changes reflected immediately in the <h2> above the textbox.

宣告 HeroesComponent

Declare HeroesComponent

每個元件都必須宣告在(且只能宣告在)一個 NgModule 中。

Every component must be declared in exactly one NgModule.

沒有宣告過 HeroesComponent,可為什麼本應用卻正常呢?

You didn't declare the HeroesComponent. So why did the application work?

這是因為 Angular CLI 在產生 HeroesComponent 元件的時候就自動把它加到了 AppModule 中。

It worked because the Angular CLI declared HeroesComponent in the AppModule when it generated that component.

開啟 src/app/app.module.ts 你就會發現 HeroesComponent 已經在頂部匯入過了。

Open src/app/app.module.ts and find HeroesComponent imported near the top.

import { HeroesComponent } from './heroes/heroes.component';
src/app/app.module.ts
      
      import { HeroesComponent } from './heroes/heroes.component';
    

HeroesComponent 也已經宣告在了 @NgModule.declarations 陣列中。

The HeroesComponent is declared in the @NgModule.declarations array.

declarations: [ AppComponent, HeroesComponent ],
src/app/app.module.ts
      
      declarations: [
  AppComponent,
  HeroesComponent
],
    

注意 AppModule 聲明瞭應用中的所有元件,AppComponentHeroesComponent

Note that AppModule declares both application components, AppComponent and HeroesComponent.

檢視最終程式碼

Final code review

應用跑起來應該是這樣的:現場演練 / 下載範例。本頁涉及的程式碼如下:

Your app should look like this現場演練 / 下載範例. Here are the code files discussed on this page.

import { Component, OnInit } from '@angular/core'; import { Hero } from '../hero'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'] }) export class HeroesComponent implements OnInit { hero: Hero = { id: 1, name: 'Windstorm' }; constructor() { } ngOnInit() { } }<h2>{{hero.name | uppercase}} Details</h2> <div><span>id: </span>{{hero.id}}</div> <div> <label>name: <input [(ngModel)]="hero.name" placeholder="name"/> </label> </div>import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; // <-- NgModel lives here import { AppComponent } from './app.component'; import { HeroesComponent } from './heroes/heroes.component'; @NgModule({ declarations: [ AppComponent, HeroesComponent ], imports: [ BrowserModule, FormsModule ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Tour of Heroes'; }<h1>{{title}}</h1> <app-heroes></app-heroes>export interface Hero { id: number; name: string; }
      
      import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';

@Component({
  selector: 'app-heroes',
  templateUrl: './heroes.component.html',
  styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
  hero: Hero = {
    id: 1,
    name: 'Windstorm'
  };

  constructor() { }

  ngOnInit() {
  }

}
    

小結

Summary

  • 你使用 CLI 建立了第二個元件 HeroesComponent

    You used the CLI to create a second HeroesComponent.

  • 你把 HeroesComponent 新增到了殼元件 AppComponent 中,以便顯示它。

    You displayed the HeroesComponent by adding it to the AppComponent shell.

  • 你使用 UppercasePipe 來格式化英雄的名字。

    You applied the UppercasePipe to format the name.

  • 你用 ngModel 指令實現了雙向資料繫結。

    You used two-way data binding with the ngModel directive.

  • 你知道了 AppModule

    You learned about the AppModule.

  • 你把 FormsModule 匯入了 AppModule,以便 Angular 能識別並應用 ngModel 指令。

    You imported the FormsModule in the AppModule so that Angular would recognize and apply the ngModel directive.

  • 你知道了把元件宣告到 AppModule 是很重要的,並認識到 CLI 會自動幫你宣告它。

    You learned the importance of declaring components in the AppModule and appreciated that the CLI declared it for you.