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阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容