Angular 權威教程 | 第1章 第一個Angular Web應用

Angular 權威教程

仿制Reddit網站

讀完本章之后, 你將掌握如何構建基本的Angular應用。

  • 簡單的應用將涵蓋Angular中的大部分基本要素
    • 構建自定義組件;
    • 從表單中接收用戶輸入
    • 把對象列表渲染到視圖中
    • 攔截用戶的點擊操作, 并據此作出反應
完成后的應用

起步

TypeScript[1]

我必須用TypeScript嗎? 并非如此! 要使用 Angular,TypeScript 并不是必需的, 但它可能是最好的選擇。 Angular也有一套 ES5 API, 但Angular本身就是用 TypeScript 寫成的, 所以人們一般也會選用它。 本書也將使用TypeScript, 因為它確實很棒, 能讓 Angular 寫起來更簡單。 當然, 并不是非它不可

  • 使用NPM[2]安裝TypeScript
   $ npm install -g typescript

angular-cli

Angular提供了一個命令行工具angular-cli, 它能讓用戶通過命令行創建和管理項目。在本章中, 我們就用它來創建第一個應用

  • 安裝angular-cli
   //安裝完畢之后, 你就可以在命令行中用ng命令運行它了
   $ npm install -g angular-cli@1.0.0-beta.18
   //不帶參數運行ng命令時, 它就會執行默認的help命令。 help命令會解釋如何使用本工具
$ ng
Could not start watchman; falling back to NodeWatcher for file system events.
Visit http://ember-cli.com/user-guide/#watchman for more info.
Usage: ng <command (Default: help)>
  • OS X 用戶
    * 安裝Homebrew工具 通過 Homebrew 工具來安裝 watchman
    * 安裝 watchman 的工具,幫助 angular-cli 監聽文件系統的變化

      ```javascript  
     // 安裝Homebrew工具后 可使用此命令
     $ brew install watchman
     ```
    
  • Linux 用戶學習如何安裝watchman

  • Windows 用戶:不必安裝任何東西, angular-cli將使用原生的 Node.js 文件監視器

示例項目

  • 運行ng new命令(最新版好像不能使用‘_’和‘angular’關鍵字,會提示警告)
   $ ng new angular2_hello_world //練習時命名 helloWorld 可以通過
   //運行之后輸出:
   installing ng 2
   create .editorconfig
   create README.md
   create srcappapp.component.css
   create srcappapp.component.html
   create srcappapp.component.spec.ts
   create srcappapp.component.ts
   create srcappapp.module.ts
   create srcappindex.ts
   create srcappshared/index.ts
   create src/assets/.gitkeep
   create src/assets/.npmignore
   create src/environments/environment.dev.ts
   create src/environments/environment.prod.ts
   create src/environments/environment.ts
   create src/favicon.ico
   create src/index.html
   create src/main.ts
   create src/polyfills.ts
   create src/styles.css
   create src/test.ts
   create src/tsconfig.jsoncreate src/typings.d.ts
   create angular-cli.json
   create e2e/app.e2e-spec.ts
   create e2e/app.po.ts
   create e2e/tsconfig.json
   create .gitignore
   create karma.conf.js
   create package.json
   create protractor.conf.js
   create tslint.json
   Successfully initialized git.
   ( Installing packages for tooling via npm
   ```
   * **npm依賴的安裝** (會自動安裝)
   
   ```bash
   //提示這行代碼表示安裝依賴完成
   Installed packages for tooling via npm.
   ```
   * **進入angular2_hello_world目錄**
   
   ```java
       $ cd angular2_hello_world
       $ tree -F -L 1
       .
       ├──README.md // an useful README
       ├──angular-cli.json // angular-cli configuration file
       ├──e2e/ // end to end tests
       ├──karma.conf.js // unit test configuration
       ├──node_modules/ // installed dependencies
       ├──package.json // npm configuration
       ├──protractor.conf.js // e2e test configuration
       ├──src/ // application source
       └──tslint.json // linter config file
       3 directories, 6 files
   ```
* **進入 src 目錄 查看應用代碼**
   
   ```bash
   $ cd src
   $ tree -F
   .|-- app/
   | |-- app.component.css
   | |-- app.component.html
   | |-- app.component.spec.ts
   | |-- app.component.ts
   | |-- app.module.ts
   | |-- index.ts
   | `-- shared/
   | `-- index.ts
   |-- assets/
   |-- environments/
   | |-- environment.dev.ts
   | |-- environment.prod.ts
   | `-- environment.ts
   |-- favicon.ico
   |-- index.html
   |-- main.ts
   |-- polyfills.ts
   |-- styles.css
   |-- test.ts
   |-- tsconfig.json
   `-- typings.d.ts
   4 directories, 18 files
   ```
* **編輯器打開index.html**
   
   ```html
   //聲明了頁面的字符集(charset) 、 標題(title) 和基礎URL(base href) 
   <!doctype html>
   <html>
   <head>
   <meta charset="utf-8">
   <title>Angular2HelloWorld</title>
   <base href="">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="imagex-icon" href="favicon.ico">
   </head>
   <body>
   // 應用將會在app-root標簽處進行渲染 
     文本Loading...是一個占位符,應用加載之前會顯示它,也可以是加載動畫 
   <app-root>Loading...</app-root>
   </body>
   </html>

運行應用

//angular-cli有一個內建的HTTP服務器,根目錄運行命令
$ ng serve
** NG Live Development Server is running on http://localhost:4200. **
// a bunch of debug messages
Build successful - 1342ms.

我們的應用正在localhost的4200端口上運行。 打開瀏覽器并訪問 http://localhost:4200

運行中的應用

制作Component(組件)

Angular背后的指導思想之一就是組件化<select><form><video>都是由瀏覽器的開發者預先定義好的,我們要教瀏覽器認識一些擁有自定義功能的新標簽

  • 使用angular-cli 的 generate 指令創建新組建

     $ ng generate component hello-world
     installing component
     create srcapphello-world/hello-world.component.css
     create srcapphello-world/hello-world.component.html
     create srcapphello-world/hello-world.component.spec.ts
     create srcapphello-world/hello-world.component.ts
    
  • 定義新組建

  • Component注解

  • 組件定義類

打開第一個TypeScript文件:srcapphello-world/hello-world.component.ts,接下來就會一步步講解它。

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

TypeScript文件的后綴是.ts而不是.js。 問題在于瀏覽器
并不知道該如何解釋TypeScript文件。 為了解決這個問題, ng
serve命令會自動把.ts文件編譯為.js文件

導入依賴

  • import 語句定義了依賴的模塊

  • @angular/core 部分告訴程序到哪里查找所需的這些依賴

  • import 語句的結構:

    //從另一個模塊中拉取這些依賴,并且讓這些依賴在當前文件中可用
    import { things } from wherever
    

Component注解

導入依賴后, 聲明該組件

//注解其實是讓編譯器為代碼添加功能的途徑之一
//可以把注解看作添加到代碼上的元數據。 
//當在HelloWorld類上使用@Component時,
//就把HelloWorld“裝飾”(decorate) 成了一個Component。
@Component({
//selector屬性用來指出該組件將使用哪個DOM元素
selector: 'app-hello-world',
})

用templateUrl添加模板

@Component({
//Angular加載該組件時,就會讀取此文件的內容作為組件的模板
templateUrl: './hello-world.component.html',
})

添加template

@Component({
selector: 'app-hello-world',
//傳入template選項來為@Component添加一個模板
template: `
<p>
hello-world works inline!
</p>
`
})
  • ` ... `反引號定義多行字符串,ES6中的一個新特性

templateUrl 和 template 選擇哪種寫法?
視情況而定,把代碼和模板分開。對一些開發團隊來說更容易,不過某些項目會增加成本,因為不得不在一大堆文件之間切換
如果模板行數短于一頁,更傾向于把模板和代碼放在一起(也就是.ts文件中)。同時看到邏輯和視圖部分,便于理解它們的互動。
內聯寫法缺點:編輯器不支持對內部HTML字符串進行語法高亮

用styleUrls添加CSS樣式

Angular使用一項叫作樣式封裝(styleencapsulation) 的技術,它意味著在特定組件中指定的樣式只會應用于該組件本身。

@Component({
//引入 CSS 作為該組件的樣式
同一個組件可以加載多個樣式表
styleUrls: ['./hello-world.component.css']
})

加載組件

把該組件的標簽添加到一個將要渲染的模板中

//<app-hello-world>標簽添加到app.component.html中
<h1>
{{title}}
<app-hello-world></app-hello-world>
</h1>
正常運行

把數據添加到組件中

  • 創建新組建
   //顯示用戶的名字
   ng generate component user-item
```html
 //app-user-item標簽添加到app.component.html中  
    <h1>
      {{title}}
      <app-hello-world></app-hello-world>
      <app-user-item></app-user-item>
    </h1>
```

UserItemComponent顯示一個指定用戶的名字

  • name屬性

    • 我們往 UserItemComponent 類添加了一個 name 屬性
    • name指定類型是TypeScript中的特性,用來確保它的值必須是string
  • 構造函數

    • 這個函數會在創建這個類的實例時自動調用
    • 可以用模板語法[3]({{ }})在模板中顯示該變量的值
    export class UserItemComponent implements OnInit {
    //name是我們想設置的屬性名,而string是該屬性的類型
        name: string; // <-- added name property
        constructor() {
           // 組件被創建時, 把name設置為'Felipe'
            this.name = 'Felipe'; // set the name
        }
        ngOnInit() {
        }
    }
    //useritem.component.html 中
    <p>
        Hello {{ name }}
    </p>
    
重新加載后

使用數組

  • 創建一個會渲染用戶列表的新組件
 ng generate component user-list
  • 修改 app.component.html
<h1>
{{title}}
<app-hello-world></app-hello-world>
<app-user-list></app-user-list>
</h1>
  • 修改 user-list.component.ts
export class UserListComponent implements OnInit {
    //語法表示names的類型是string構成的數組
    //另一種寫法是Array<string>。
    names: string[];
    constructor() {
        this.names = ['Ari', 'Carlos', 'Felipe', 'Nate'];
    } 
    ngOnInit() {
    }
}
  • 修改 user-list.component.html
<ul>
//循環處理 names 中的每一個元素并將其逐個賦值給一個名叫 name 的局部變量
//( name 是一個局部變量 可以更換 如foobar )
<li *ngFor="let name of names">Hello {{ name }}</li>
</ul>

NgFor指令將為數組 names 中的每一個條目都渲染出一個 li 標簽,并聲明一個本地變量 name 來持有當前迭代的條目。然后這個新變量將被插值到 Hello {{ name }}代碼片段里
如果你想進一步探索,可以直接閱讀Angular源代碼來學習Angular核心團隊是如何編寫組件的

使用UserItemComponent組件

用UserItemComponent作為子組件,為列表中的每個條目指定模板

  • 配置 UserListComponent 來渲染 UserItemComponent
  • 配置 UserItemComponent 來接收 name 變量作為輸入
  • 配置 UserListComponent 的模板來把用戶名傳給
    UserItemComponent

渲染UserItemComponent

//把li標簽替換為app-user-item
標簽
<ul>
    <app-user-item *ngFor="let name of names">
    </app-user-item>
</ul>
image.png

接收輸入

 //修改 UserItemComponent
 // 引入 Input
 import { 
     Component, 
     OnInit, 
     Input // <--- added this } from '@angular/core';
@Component({
    selector: 'app-user-item',
    templateUrl: './user-item.component.html',
    styleUrls: ['./user-item.component.css']
})
export class UserItemComponent implements OnInit {
    //添加 @Input 注解
    @Input() name: string; // <-- added Input annotation
    constructor() {
      // 不希望有默認值
     // removed setting name
    } ngOnInit() {
    
    }
}

傳入Input值

**為了把一個值傳入組件,就要在模板中使用方括號[]語法。 **

// 修改 userlist.component.html
<ul>
    <app-user-item *ngFor="let name of names" [name]="name">
    </app-user-item>
</ul>

添加一個帶方括號的屬性(比如[foo])意味著把一個值傳給該組件上同名的輸入屬性(比如 foo)

// name 右側的值來自 ngFor 中的 let name ...語句
<app-user-item *ngFor="let individualUserName of names" 
    [name]="individualUserName">
</app-user-item>

[name]部分指定的是 UserItemComponent 上的 Input。注意,我們正在傳入的并不是字符串字面量"individualUserName", 而是individualUserName 變量的值, 也就是 names 中的每個元素。

執行過程

  • 在 names 中迭代
  • 為 names 中的每個元素創建一個新的 UserItemComponent
  • 把當前名字的值傳給 UserItemComponent 上名叫 name 的 Input屬性

啟動速成班

Angular應用是如何啟動的?
每個應用都有一個主入口點。該應用是由 angular-cli 構建的,而
angular-cli 則是基于一個名叫 webpack 的工具。 你不必理解webpack 就能使用 Angular, 但理解應用的啟動流程是很有幫助的。

// 通過運行下列命令來啟動
// ng會查閱angular-cli.json文件來找出該應用的入口點
    ng serve

大體流程

  • angular-cli.json指定一個"main"文件, 這里是main.ts;
  • main.ts 是應用的入口點, 并且會引導(bootstrap) 我們的應用;
  • 引導過程會引導一個Angular模塊——我們尚未討論過模塊, 不過很快就會談到;
  • 我們使用 AppModule 來引導該應用, 它是在srcappapp.module.ts中指定的;
  • AppModule 指定了將哪個組件用作頂層組件, 這里是 AppComponent;
  • AppComponent 的模板中有一個<app-user-list>標簽, 它會渲染出我們的用戶列表

Angular有一個強大的概念: 模塊。當引導一個 Angular 應用時,并不是直接引導一個組件, 而是創建了一個 NgModule,它指向了你要加載的組件。

// 為 AppModule 類添加了元數據
@NgModule({
    declarations: [
        AppComponent,
        HelloWorldComponent,
        UserItemComponent,
        UserListComponent
    ],
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule
    ],
    providers: [],
    bootstrap: [AppComponent]
    })
export class AppModule { }

@NgModule注解有三個屬性

  • declarations(聲明):指定了在該模塊中定義的組件。要想在模板中使用一個組件,你必須首先在NgModule中聲明它
  • imports :描述了該模塊有哪些依賴。我們正在創建一個瀏覽器應用,因此要導入BrowserModule
  • bootstrap 告訴Angular, 當使用該模塊引導應用時, 我們要把AppComponent加載為頂層組件

擴展你的應用

應用的邏輯組件

構造兩個組件

  1. 整體應用程序,包含一個用來提交新文章的表單
  2. 每個文章
// 創建一個新的應用
ng new xxx( 應用名字 )

添加CSS

在本項目中,我們將使用 Semantic-UI 來幫助添加樣式。Semantic-UI 是一個CSS框架, 類似于Zurb FoundationTwitterBootstrap

完成版示例代碼中復制以下文件到你的應用目錄下:

  • src/index.html
  • src/styles.css
  • srcappvendor
  • src/assets/images

應用程序組件

構建一個新的組件

  • 存儲我們的當前文章列表
  • 包含一個表單, 用來提交新的文章。
// 修改 app.component.html
<form class="ui large form segment">
  <h3 class="ui header">Add a Link</h3>
    <div class="field">
      <label for="title">Title:</label>
      <input name="title">
  </div>
<div class="field">
  <label for="link">Link:</label>
  <input name="link">
</div>
</form>
表單

添加互動

  • 添加一個提交按鈕

    //把事件的名字包裹在圓括號()中就可以告訴Angular: 我們要響應這個事件
    <button (click)="addArticle()"
    class="ui positive right floated button">
    Submit link
    </button>
    
  • 定義一個函數

    // 修改 app.component.ts
    export class AppComponent {
      addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
    console.log(`Adding article title: ${title.value} and link:${link.value}`);
    return false;
    }
    }
    
  • 修改模板

    <form class="ui large form segment">
        <h3 class="ui header">Add a Link</h3>
        <div class="field">
            <label for="title">Title:</label>
            // input標簽上使用了#(hash)來要求Angular把該元素賦值給一個局部變量
            <input name="title" #newtitle> <!-- changed -->
        </div>
        <div class="field">
            <label for="link">Link:</label>
            <input name="link" #newlink> <!-- changed -->
        </div>
        // 通過把#title和#link添加到適當的<input/>元素上,就可以把它們作為變量傳給按鈕上的addArticle()函數
         <!-- added this button -->
        <button (click)="addArticle(newtitle, newlink)"
        class="ui positive right floated button">
        Submit link
        </button>
    </form>
    

四項修改

  1. 在模版中創建了一個 button 標簽, 向用戶表明應該點擊哪里
  2. 新建了一個名叫 addArticle 的函數, 來定義按鈕被點擊時要做的事情
  3. 在 button 上添加了一個(click)屬性, 意思是“只要點擊了這個按鈕, 就運行 addArticle 函數”
  4. 在兩個 <input>標簽上分別添加了#newtitle 和 #newlink 屬性

  • 綁定input的值

    // 注意, 第一個輸入標簽是這樣的:
    <input name="title" #newtitle>
    

    Angular把這個<input>綁定到變量 newtitle 上。 #newtitle 語法被稱作一個解析(resolve),其效果是讓變量 newtitle 可用于該視圖的所有表達式中 。newtitle 現在是一個對象, 它代表了這個inputDOM元素(更確切地說,它的類型是 HTMLInputElement )。由于newtitle是一個對象,我們可以通過newtitle.value表達式來獲取這個輸入框的值

  • 把事件綁定到動作

    • addArticle是組件定義類AppComponent里的一個函數。
      (2) newtitle來自名叫title的<input>標簽上的解析
      (#newtitle) 。
      (3) newlink來自名叫link的<input>標簽上的解析
      (#newlink)
<button (click)="addArticle(newtitle, newlink)"
class="ui positive right floated button">
Submit link
</button>
  • 定義操作邏輯
    • title和link 都是 HTMLInputElement 類型的對象
    • 從 input 中獲取值, 就得調用title.value
//${title.value}放在了字符串中, 它最終會被
替換成title.value的值
addArticle(title: HTMLInputElement, link: HTMLInputElement): 
boolean {
console.log(`Adding article title: ${title.value} and link: ${link.value}`);
return false;
}
點擊按鈕

添加文章組件

生成一個新組件

  1. 在模板中定義了 ArticleComponent 的視圖
  2. 通過為類加上 @Component 注解定義了 ArticleComponent 組件的元數據
  3. 定義了一個組件定義類(ArticleComponent) , 其中是組件本身的邏輯
一篇文章
ng generate component article
  • 創建 ArticleComponent 的 template
// 修改 article.component.html
// 左側是投票的數量
// four wide column 和 twelve wide column 這兩個 CSS 類
//來指定這兩列。它們來自 Semantic UI 的 CSS 庫
<div class="four wide column center aligned votes">
    <div class="ui statistic">
        <div class="value">
            {{ votes }}
        </div>
        <div class="label">
            Points
        </div>
    </div>
</div>
// 右側是文章的信息
<div class="twelve wide column">
    <a class="ui large header" href="{{ link }}">
    {{ title }}
    </a>
    <ul class="ui big horizontal list voters">
         <li class="item">
             <a href (click)="voteUp()">
             <i class="arrow up icon"></i>
             upvote
            </a>
        </li>
        <li class="item">
            <a href (click)="voteDown()">
            <i class="arrow down icon"></i>
            downvote
           </a>
        </li>
    </ul>
</div>

a 標簽的 href 屬性中:href="{{ link }}"。在這種情況下,href 的值會根據組件類的 link 屬性的值進行動態插值計算得出

  • 創建ArticleComponent
// 修改 article.component.ts
@Component({
    selector: 'apparticle',
    templateUrl: './article.component.html',
    styleUrls: ['./article.component.css'],
    host: {
        //apparticle都獨占一行 Semantic UI 用來表示行的CSS類 
        class: 'row'
    }
})
  • 創建組件定義類ArticleComponent
// 創建了以下三個屬性
// 1. votes: 一個數字,用來表示所有“贊”減去所有“踩”的數量之和。
// 2. title: 一個字符串, 用來存放文章的標題。
// 3. link: 一個字符串, 用來存放文章的URL
export class ArticleComponent implements OnInit {
    votes: number;
    title: string;
    link: string;
    constructor() {
        this.title = 'Angular 2';
        this.link = 'http://angular.io';
        this.votes = 10;
    }
    voteUp() {
        this.votes += 1;
    } 
    voteDown() {
        this.votes -= 1;
    } 
    ngOnInit() {
    }
}
  • 使用apparticle組件
// AppComponent的模板中
<button (click)="addArticle(newtitle, newlink)" class="ui positive right floated button">
    Submit link
</button>
</form>
<div class="ui grid posts">
    <apparticle>
    </apparticle>
</div>

在 AngularJS 中,指令的匹配是全局的;而Angular中,你需要明確指定要使用哪個組件,意味著我們不必被迫在全局命名空間中共享這些指令選擇器。

// app.module.ts
import { AppComponent } from './app.component';
import { ArticleComponent } from './article/article.component.ts';
@NgModule({
    declarations: [
    AppComponent,
    ArticleComponent // <-- added this
],

默認情況下, JavaScript會把click事件冒泡到所有父級組件中。因為click事件被冒泡到了父級,瀏覽器就會嘗試導航到這個空白鏈接,于是瀏覽器就重新刷新了。
解決:我們得讓click的事件處理器返回false。這能確保瀏覽器不會嘗試刷新頁面。

voteDown(): boolean {
    this.votes -= 1;
    return false;
}
// and similarly with `voteUp()`

渲染多行

創建Article類

// 此目錄下創建文件 article/article.model.ts
// 在MVC模式中, 它被稱為模型(model) 
export class Article {
    title: string;
    link: string;
    votes: number;
    constructor(title: string, link: string, votes?: number) {
        this.title = title;
        this.link = link;
        this.votes = votes || 0;
    }
}
// article.component.ts
import { Article } from './article.model';
export class ArticleComponent implements OnInit {
    article: Article;
    constructor() {
        this.article = new Article(
        'Angular 2',
        'http://angular.io',
        10);
    } 
    voteUp(): boolean {
        this.article.votes += 1;
        return false;
    }
    voteDown(): boolean {
        this.article.votes -= 1
        return false;
    } 
    ngOnInit() {
    }
}
// 視圖模型 article.component.html
<div class="four wide column center aligned votes">
    <div class="ui statistic">
        <div class="value">
            {{ article.votes }}
        </div>
        <div class="label">
            Points
        </div>
    </div>
</div>
<div class="twelve wide column">
    <a class="ui large header" href="{{ article.link }}">
        {{ article.title }}
    </a>
    <ul class="ui big horizontal list voters">
        <li class="item">
            <a href (click)="voteUp()">
            <i class="arrow up icon"></i>
            upvote
            </a>
        </li>
        <li class="item">
            <a href (click)="voteDown()">
            <i class="arrow down icon"></i>
            downvote
            </a>
        </li>
    </ul>
</div>

當前的voteUp和voteDown違反了迪米特法則。迪米特法則是指:一個對象對其他對象的結構或屬性所作的假設應該越少越好。問題在于ArticleComponent組件了解太多Article類的內部知識了

// article.model.ts
export class Article {
    title: string;
    link: string;
    votes: number;
    constructor(title: string, link: string, votes?: number) {
        this.title = title;
        this.link = link;
        this.votes = votes || 0;
    } 
    voteUp(): void {
        this.votes += 1;
    } 
    voteDown(): void {
        this.votes -= 1;
    } 
    domain(): string {
    try {
        const link: string = this.link.split('//')[1];
        return link.split('/')[0];
    } catch (err) {
        return null;
    }
    }
}
// article.component.ts
export class ArticleComponent implements OnInit {
    article: Article;
    constructor() {
        this.article = new Article(
        'Angular',
        'http://angular.io',
        10);
    }
    voteUp(): boolean {
        this.article.voteUp();
        return false;
    } 
    voteDown(): boolean {
        this.article.voteDown();
        return false;
    } 
    ngOnInit() {
    }
}

為什么模型和組件中都有一個voteUp函數?
這兩個函數所做的事情略有不同。ArticleComponent 上的 voteUp() 函數是與組件的視圖有關的,而 Article 模型上的 voteUp() 定義了模型上的變化。
我們把大量邏輯移出組件,放進了模型中。與此對應的MVC指南應該是胖模型,皮包骨的控制器;其核心思想是,我們要把大部分領域邏輯移到模型中,以便讓組件只做盡可能少的工作。

存儲多篇文章

// 讓 AppComponent 擁有一份文章集合
// 引入模型
import { Article } from './article/article.model';

export class AppComponent {
//articles 是 Article 的數組。另一種寫法是 Array<Article>
// Array 是一個集合,它只能存放 Article 類型的對象
    articles: Article[];
    constructor() {
    this.articles = [
        new Article('Angular 2', 'http://angular.io', 3),
        new Article('Fullstack', 'http://fullstack.io', 2),
        new Article('Angular Homepage', 'http://angular.io', 1),
    ];
    } 
    addArticle(title: HTMLInputElement,link: HTMLInputElement): boolean{
        console.log(`Adding article title: ${title.value} and link: ${link.value}`);
        this.articles.push(new Article(title.value, link.value, 0));
        title.value = '';
        link.value = '';
        return false;
    }
}

使用inputs配置ArticleComponent

有了一個Article模型的列表, 該怎么把它們傳給ArticleComponent組件呢?
這里我們又用到了 Input。以前 ArticleComponent 類的定義是下面這樣的

// article.component.ts
export class ArticleComponent implements OnInit {
    article: Article;
    constructor() {
        //構造函數中硬編碼了一個特定的Article; 而制作組件時, 不但要能封裝, 還要能復用
        this.article = new Article(
        'Angular 2',
        'http://angular.io',
        10);
}
// 修改 article.component.ts
export class ArticleComponent implements OnInit {
    @Input() article: Article;
        voteUp(): boolean {this.article.voteUp();
        return false;
    } 
    voteDown(): boolean {
        this.article.voteDown();
        return false;
    } 
    ngOnInit() {
    }
}

渲染文章列表

// 修改 AppComponent 模板
// 1. articles是一個Article的數組, 由AppComponent組件定義
// 2. foobar是一個articles數組中的單個元素(一個Article對象)由NgFor定義
// 3. article是一個字段名, 由ArticleComponent中的inputs屬性定義。
Submit link
</button>
</form>
<!-- start adding here -->
<div class="ui grid posts">
    <apparticle *ngFor="let article of articles" [article]="article">
    </apparticle>
</div>
<!-- end adding here -->
最終效果

添加新文章

// 修改按鈕 思路:
// 1. 創建一個具有所提交標題和URL的Article新實例
// 2. 把它加入Article數組;
// 3. 清除input字段的值
addArticle(title: HTMLInputElement, link: HTMLInputElement): boolean {
    console.log(`Adding article title: ${title.value} and link: ${link.value}`);
    this.articles.push(new Article(title.value, link.value, 0));
    //修改value屬性時, 頁面中的input標簽也會跟著
    改變
    title.value = '';
    link.value = '';
    return false;
}

最后的修整

顯示文章所屬的域名

// article.model.ts
domain(): string {
    try {
        // 注意:URL必須包含http://
        const link: string = this.link.split('//')[1];
        return link.split('/')[0];
    } catch (err) {
        return null;
    }
}
// ArticleComponent的模板
<div class="twelve wide column">
    <a class="ui large header" href="{{ article.link }}">
       {{ article.title }}
    </a>
<!-- right here -->
<div class="meta">({{ article.domain() }})</div>
<ul class="ui big horizontal list voters">
    <li class="item">
        <a href (click)="voteUp()">

基于分數重新排序

//AppComponent上創建一個新方法 sortedArticles
sortedArticles(): Article[] {
    return this.articles.sort((a: Article, b: Article) => b.votes - a.votes);
}

// app.component.html 
<div class="ui grid posts">
    <apparticle *ngFor="let article of sortedArticles()" 
        [article]="article">
    </apparticle>
</div>

全部代碼

總結

Angular程序的寫法

  1. 把應用拆分成組件
  2. 創建視圖
  3. 定義模型
  4. 顯示模型
  5. 添加交互。

(第一章完結)

自己練習的代碼,希望對你有用

自己練習效果


  1. TypeScript 是 JavaScript ES6 版的一個超集, 增加了類型支持。 ?

  2. npm是Node.js的一部分。 如果你的系統中沒有npm命令, 請確認你安裝的Node.js是包含它的版本 ?

  3. 模板標簽中間的任何東西都會被當作一個表達式來展開 ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容