使用Angular2来执行React官方教程的详细步骤。同时使用gulp + browserify

首先

即将参加 ng-japan,所以作为预习,我尝试了 Angular2 官方网站上的 5 Min Quickstart 和 Case Study 的 Tour of Heroes。
虽然都是英文,但教程非常易懂,我乐在其中并顺利进行。正因为如此,我想再多玩一会儿,在之前我写过的 React 教程的续集中,尝试用 Angular2 执行 React 教程。Angular2 和 React 都是组件导向,但如果实现相同的东西,它们会有什么不同,我认为这样对比下会很有趣。

过去的React教程系列如下:
* 利用gulp创建了一个可以轻松运行React.js官方教程并尝试了ES6的环境。
* 利用Redux重写了React.js官方教程,并添加了Mocha + power-assert的测试。

我正在使用2016年3月的最新环境来执行。

源文件

以下链接是我在 GitHub 上发布的:https://github.com/ma-tu/react-tutorial-with-angular2

文章的结构

虽然这篇文章很长,但我会按照以下结构进行总结。

    1. 我們將以 Angular2 官方 5 分鐘快速入門為開始點。

 

    1. 我們整理了使用 gulp + browserify + tsify 將上述成果物運作起來的步驟。

 

    1. 我們整理了在以上述成果物為基礎進行 React 教程的服務器環境等安排步驟。

 

    我們將按照 React 教程的流程,以上述成果物為基礎,使用 Angular2 進行實現。

你也可以通过使用”start-react-tutorial”分支来从4开始。

那么让我们开始吧。

执行 Angular2 官方的 5 分钟快速入门

首先,进行 Angular 官方的 5 分钟快速入门。

以下是执行 [1. Angular2 公式的 5 分钟快速入门] 后的源代码,已在以下位置进行了公开。
https://github.com/ma-tu/react-tutorial-with-angular2
的 [5-min-quickstart-ends] 分支

git clone https://github.com/ma-tu/react-tutorial-with-angular2.git
cd react-tutorial-with-angular2
git checkout 5-min-quickstart-ends
npm install

2. 支持 gulp + browserify + tsify

新增依赖库

添加所需的依赖库。执行以下命令。

npm install --save-dev browserify gulp tsify vinyl-source-stream

tsify 是一个在 Browserify 中用于 TypeScript 编译的插件。

编辑 tsconfig.json

Browserify是一个系统,它使用CommonJS作为模块系统,使JavaScript在浏览器中运行。
而Angular2的5分钟快速入门则使用SystemJS作为模块系统。

    • モジュールシステムを CommonJS に変更します。

 

    • “module” : “system” を “module” : “commonjs” に変更します。

 

    • TypeScript のコンパイルは tsify を利用して、Browserify と一緒に実行するので保存時のコンパイルを行わないようにします。

 

    “compileOnSave”: false を追加します。
"compilerOptions": {
  ///
  "module": "commonjs",
  ///
},
"compileOnSave": false,

新增 gulpfile.js

请添加以下的 gulpfile.js。

var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var tsify = require('tsify');

//Browserify
gulp.task('browserify', function() {
  browserify('./app/main.ts')
    .add("typings/browser.d.ts")
    .plugin(tsify)
    .bundle()
    .on("error", function (err) {console.log("ERROR: " + err.message);})
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./'))
});

//Watch Task
gulp.task('watch', function() {
  gulp.watch('./app/**/*.ts', ['browserify'])
});

我会提供额外的解释。

./app/main.ts を Browserify の Entry Point とします。

add(“typings/browser.d.ts”) は 現状 tsify が tsconfig の files を参照していないため必要と以下 issue にあり対応しています。TypeScript の定義ファイルを追加しています。
tsify issue

tsify plugin を利用して TypeScript をコンパイルします。
Browserify した結果ファイルを ./bundle.js に出力します。

./app 以下の ts ファイルに変更があったら browseriry タスクを実行します。

以下的URL可以作为参考:
使用gulp + browserify + tsify来搭建TypeScript编译环境。

编辑 package.json 文件中的 scripts 部分。

根据将 npm start 等命令用于 Browserify 并启动监视,编辑 package.json 文件中的 scripts 部分。

"scripts": {
  "start": "gulp browserify && concurrently \"gulp watch\" \"npm run lite\" ",
  "watch": "gulp watch",
  "browserify": "gulp browserify",
  "lite": "lite-server",
  "typings": "typings",
  "postinstall": "typings install"
},

补充解释如下。
* && 将前后任务以串行方式执行。在这种情况下,先进行 Browserify 然后同时运行 gulp watch 和起动 lite-server。

编辑 index.html

使用 Browserify 刪除不需要的 script 註解,並改為載入由 Browserify 輸出的 bundle.js。

由於 bundle.js 內參照了 標籤,所以需要在 body 標籤內載入。

雖然也想要刪除 angular2-polyfills.js 的註解,但目前似乎還是需要它。

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    
    <link rel="stylesheet" href="styles.css">

    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
  </head>

  <body>
    <my-app>Loading...</my-app>
    <script src="bundle.js"></script>
  </body>
</html>

创建.gitignore文件

如果没有.gitignore文件,则创建一个。

node_modules
typings
app/*.js
app/*.map
bundle.js

我尝试一下。

npm start

编辑 app/app.component.ts 文件中的模板,并确认页面会自动更新。

用 gulp + browserify + tsify 完成了对 [2. gulp + browserify + tsify] 的支持的工作。

3. 为进行 React 教程而准备服务器环境

在 React.js 官方教程中,需要在中途使用 Ajax 进行服务器环境操作。请先准备好这个环境。

在完成此作業时的源代码已在以下地址进行公开:
https://github.com/ma-tu/react-tutorial-with-angular2
的 [ start-react-tutorial ] 分支上。

git clone https://github.com/ma-tu/react-tutorial-with-angular2.git
cd react-tutorial-with-angular2
git checkout start-react-tutorial
npm install

server.js 和 comments.json 的布置

将 React.js 教程存储库中的 server.js 和 comments.json 文件放置在当前目录下。
使用 express 执行 server.js 文件来启动服务器环境。

添加依赖库

请添加所需的依赖库,并执行以下指令。

npm install --save-dev express body-parser gulp-nodemon 

以下是補充說明:
* 在 React 教程中,使用了 express 和 body-parser。
* 為了自動重新載入,添加了 gulp-nodemon。

将index.html移动

server.js会以./public文件夹作为根目录启动,所以请创建./public文件夹并将index.html移动到./public下面。

修改 gulpfile

添加一个名为 ‘server’ 的任务来运行 ./server.js 。使用 gulp-nodemon 实现自动重新加载。
同时,将 Browserify 转换后的文件输出目录更改为 ./public/bundle.js,以适应 server.js 根目录的更改。

var nodemon = require('gulp-nodemon');

//Browserify
gulp.task('browserify', function() {
  browserify('./app/main.ts')
    .add("typings/browser.d.ts")
    .plugin(tsify)
    .bundle()
    .on("error", function (err) {console.log("ERROR: " + err.message);})
    .pipe(source('bundle.js'))
    .pipe(gulp.dest('./public'))
});

//Server
gulp.task('server', function () {
  nodemon({ script: './server.js',
            ext: 'html js'})
   .on('restart', function () {
     console.log('restarted!')
   })
});

修改 package.json 中的 start 任务

将使用npm start和lite-server的部分更改为使用gulp server。

"start": "gulp browserify && concurrently \"gulp watch\" \"gulp server\" ",

将angular2-polyfills.js复制

在./public/lib文件夹中创建并复制node_modules/angular2/bundles/angular2-polyfills.js文件。同时,修正index.html文件中angular2-polyfills.js的路径,并删除index.html文件中styles.css的引用部分。

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    

    <script src="lib/angular2-polyfills.js"></script>
  </head>

  <body>
    <my-app>Loading...</my-app>
    <script src="bundle.js"></script>
  </body>
</html>

为了补充说明:
由于使用了 server.js,public文件夹成为了根目录,所以无法访问node_modules文件夹下的文件,因此将其移动到public/lib下。
由于styles.css目前不存在,会导致错误,所以已经将其删除。

我会尝试执行。

npm start

在启动后的浏览器上访问 http://localhost:3000。
若显示“我的第一个 Angular 2 应用程序”,则表示成功。

现在,已经完成了准备3. React教程所需的服务器环境。

4. 使用Angular2来执行React教程。

在完成到目前为止的工作时,以下链接中的源代码已经公开。
https://github.com/ma-tu/react-tutorial-with-angular2
的 [ start-react-tutorial ] 分支

git clone https://github.com/ma-tu/react-tutorial-with-angular2.git
cd react-tutorial-with-angular2
git checkout start-react-tutorial
npm install

接下来是正题。
我们将使用Angular2来实现以下的React教程。请参考React教程。
* 官方React教程
* React v0.14教程【日本语翻译】

从这里开始,我们将根据React教程的每个主题进行工作。我们还将根据每个主题将GitHub的提交分开,以便在中途不清楚的情况下使用。
请执行以下命令开始工作。
在中途根据需要使用CTRL + C等停止并重新启动。如果重新加载不成功,重新启动也可能有所改善。

npm start 

以下是 React 教程的主题,通过上面的准备步骤已经准备好了。所以我们从 「Your first component: はじめてのコンポーネント」开始。
* Running a server: 运行服务器
* Getting started: 入门指南

你的第一个组件:第一次的组件

我将创建一个CommentBox组件。

创建app/comment-box.component.ts文件。

import {Component} from 'angular2/core';

@Component({
    selector: 'my-comment-box',
    template: 
    `
    <div className="commentBox">
      Hello, world! I am a CommentBox.
    </div>
    `
})

export class CommentBoxComponent {}

以下是对上述内容的中文本地化改写:

* comment-box.component.ts 是第一个组件的文件。
* selector: ‘my-comment-box’ 是在其他组件的模板中使用 CommentBox 组件时的标签名。


在 app/app.component.ts 中添加 CommentBoxComponent 的导入,并将模板修改如下。同时,在 directives 中指定 CommentBoxComponent。

import {Component} from 'angular2/core';
import {CommentBoxComponent} from './comment-box.component'

@Component({
    selector: 'my-app',
    template: '<my-comment-box></my-comment-box>',
    directives: [CommentBoxComponent]
})
export class AppComponent { }

补充说明:
* 在 main.ts 的 bootstrap 中启动 App 组件,并从该组件启动 CommentBox 组件。
* app.component.ts 的 template 中的 希望可以写成 ,但目前似乎不行。
* 忘记添加 directives 的 CommentBoxComponent,将会忽略模板中的自定义标签,请务必记得添加。

如果在浏览器上显示出”Hello, world!I am a CommentBox.”,那就说明执行成功了。

组成组件:制作组件

创建CommentList和CommentForm组件。

创建app/comment-list.component.ts和app/comment-form.component.ts文件。

import {Component} from 'angular2/core';

@Component({
    selector: 'my-comment-list',
    template: 
    `
    <div className="commentList">
      Hello, world! I am a CommentList.
    </div>
    `
})

export class CommentListComponent { }
import {Component} from 'angular2/core';

@Component({
    selector: 'my-comment-form',
    template: 
    `
    <div className="commentForm">
      Hello, world! I am a CommentForm.
    </div>
    `
})

export class CommentFormComponent { }

修改 CommentBox 组件,以便使用上述的组件。

在app/comment-box.component.ts中添加对CommentListComponent和CommentFormComponent的导入,并修改模板如下。同时,在directives中指定CommentListComponent和CommentFormComponent。

import {Component} from 'angular2/core';
import {CommentListComponent} from './comment-list.component';
import {CommentFormComponent} from './comment-form.component';

@Component({
    selector: 'my-comment-box',
    template: 
    `
    <div className="commentBox">
      <h1>Comments</h1>
      <my-comment-list></my-comment-list>
      <my-comment-form></my-comment-form>
    </div>
    `,
    directives: [CommentListComponent, CommentFormComponent]
})

export class CommentBoxComponent {}

如果您在浏览器中看到以下内容,则表示成功,因为它与您的第一个组件”はじめてのコンポーネント”相似,因此省略了补充说明。

Comments
Hello, world! I am a CommentList.
Hello, world! I am a CommentForm.

使用道具:使用道具

创建Comment组件。

创建 app/comment.component.ts 文件。

import {Component, Input} from 'angular2/core';

@Component({
    selector: 'my-comment',
    template: 
    `
    <div className="comment">
      <h2 className="commentAuthor">
        {{author}}
      </h2>
      {{comment}}
    </div>
    `
})

export class CommentComponent {
  @Input() author: string
  @Input() comment: string
}

以下是注释的翻译:
* 组件可以使用从父组件传递过来的数据。
* 似乎不存在与 React 的 props.children 相当的东西。 在这里,我们假设像 author 一样,comment 也作为属性使用。
* 应明确指定应从父组件传递过来的值,这在 CommentComponent 类中完成。
* 在 Angular2 的教程中,通过 @Component 的 inputs 属性进行指定,但在这里我们使用了 @Input()。为了使用 @Input(),我们在导入中加入了 Input。
这与指定 inputs: [‘author’, ‘comment’] 是相同的含义。
* 通过在模板中使用 {{author}} {{comment}} 的指令来绑定传递过来的 author 和 comment,并显示出来。

在这个时点上,我们已经创建了CommentComponent,但由于尚未被使用,所以显示没有发生变化。

组件属性:组件属性

将 CommentList 组件连接到 Comment 组件,并传递 author 和 comment 的数据。

导入 CommentComponent,然后将其设置为指令,然后编写 标签。

import {Component} from 'angular2/core';
import {CommentComponent} from './comment.component';

@Component({
    selector: 'my-comment-list',
    template: 
    `
    <div className="commentList">
      <my-comment author="Pete Hunt" comment="This is one comment"></my-comment>
      <my-comment author="Jordan Walke" comment="This is *another* comment"></my-comment>
    </div>
    `,
    directives: [CommentComponent]
})

export class CommentListComponent { }

如果在浏览器中显示以下内容,则表示运行成功。

Comments

Pete Hunt

This is one comment
Jordan Walke

This is *another* comment
Hello, world! I am a CommentForm.

以下是补充说明:
* 我们将数据与通过 CommentComponent 指定的 my-comment 属性的 author 和 comment 进行了协同。
* 由于在 React 中找不到类似于 “通过类似XML子节点的方式” 进行数据协同的方法,所以我们选择将其作为属性传递。

添加Markdown:添加Markdown

添加Markdown解析器的marked。

为了使用 Marked,需要添加必需的依赖库。请执行以下命令。

npm install --save marked
npm run typings install marked -- --save --ambient

添加import标记。然后准备将comment使用marked转换为rawMarkup()函数,并将其设置为span标签的innerHTML属性。

import {Component, Input} from 'angular2/core';
import * as marked from 'marked'

@Component({
    selector: 'my-comment',
    template: 
    `
    <div className="comment">
      <h2 className="commentAuthor">
        {{author}}
      </h2>
      <span [innerHTML]="rawMarkup()"></span>
    </div>
    `
})

export class CommentComponent {
  @Input() author: string
  @Input() comment: string

  rawMarkup():string {
    return marked(this.comment, {sanitize: true});
  }
}

如果在执行后在浏览器上显示了以下内容,并且其中的**another**部分已被斜体显示,则表示操作成功。

Comments

Pete Hunt

This is one comment
Jordan Walke

This is another comment
Hello, world! I am a CommentForm.

補充说明如下:
* 在Angular2中,不存在类似于dangerouslySetInnerHTML的属性,因此我们可以将内容设置到innerHTML属性中。但是,需要注意使用innerHTML时要防范跨站脚本攻击。
* [innerHTML]是通过使用[]来包裹属性,以便将rawMarkup()函数表达式的评估结果传递给它。
* 下面是各个属性的评估结果:

attrA将接收到字符串”1+1″。
attrB将对{{}}内的表达式进行评估,结果将传递字符串”2″。
attrC将接收到表达式”1+1″的评估结果数字2。
本文详细介绍了这个问题。
Angular 2 @Input的各种用法。

连接数据模型

虽然 React 教程中没有,但我会在这里创建 Comment 的接口。

创建 app/comment.ts 文件。

export interface Comment {
  id: number
  author: string
  text: string
}

以前我们是在CommentList组件中直接创建my-comment的,但现在将更新为根据comments数组数据来创建。最终将从服务器获取数据。

在 CommentBoxComponent 类中,通过 import Comment 接口,并指定一个 Comment 数组。在 中添加 [comments]=”comments”,将数组值传递给 CommentList 组件。

import {Component} from 'angular2/core';
import {CommentListComponent} from './comment-list.component';
import {CommentFormComponent} from './comment-form.component';
import {Comment} from './comment'

@Component({
    selector: 'my-comment-box',
    template: 
    `
    <div className="commentBox">
      <h1>Comments</h1>
      <my-comment-list [comments]="comments"></my-comment-list>
      <my-comment-form></my-comment-form>
    </div>
    `,
    directives: [CommentListComponent, CommentFormComponent]
})

export class CommentBoxComponent {
  public comments: Comment[] = [
    {id: 1, author: "Pete Hunt", text: "This is one comment"},
    {id: 2, author: "Jordan Walke", text: "This is *another* comment"}
  ];
}

通过 [comments]=”comments”,将 comments 数组传递给 CommentList 组件的 comments。


对于 CommentList 组件, 我们需要修改它的代码,以便通过从父组件传递的 comments 数组数据进行迭代处理,并创建 Comment 组件。

添加接口,并使用@Input()从父组件接收comments,并在*ngFor中进行重复显示。
为了使用@Input(),还需要导入Input。

import {Component, Input} from 'angular2/core';
import {CommentComponent} from './comment.component';
import {Comment} from './comment'

@Component({
    selector: 'my-comment-list',
    template: 
    `
    <div className="commentList">
      <my-comment *ngFor="#comment of comments" [author]="comment.author" [comment]="comment.text"></my-comment>
    </div>
    `,
    directives: [CommentComponent]
})

export class CommentListComponent {
  @Input() comments: Comment[]
}

如果在执行后的浏览器中显示出与上一次相同的结果,那就算成功了。

补充说明如下:
* 在 CommentBox 组件中,可以直接使用 comments 数组来指定评论数据。
* 在 CommentBox 组件中,通过 comments 属性将数组数据传递给 CommentList 组件。
* 在 CommentList 组件中,使用 *ngFor=”#comment of comments” 来进行循环处理,循环次数与 comments 数组长度一致。
*ngFor 是一个指令,用于指示循环处理。
comment of comments 表示逐个从 comments 数组取出值,并将其赋值给 comment 变量。
* 在 CommentList 组件中,将 comment 变量中的 author 和 text 的值赋给 author 和 comment。
在本例中,由于关联数据是字符串,因此 author={{comment.author}} 也能得到相同的结果。

从服务器获取

本次我们将整合以下三个部分来完成React教程:
* 从服务器获取数据:Fetching from the server: 从服务器获取数据
* 反应式状态:Reactive state: 反应式状态
* 更新状态:Updating state: 更新状态

在Angular2中,与服务器通信的功能由Http模块提供,因此我们可以使用它。
Http模块通过DI注入Http类的实例来使用。虽然在只使用Http模块的情况下,可以直接在组件中使用Http模块进行处理,但在Angular中,分离服务然后通过DI使用的方法成为主流。

急に浮かんできた「DI」という単語は、以下の記事で詳しく説明されています。
* Angular2のHttpモジュールを見て、ベストプラクティスを考える
* Angular2のDIについて知る

根据我的理解,DI(依赖注入)是根据Provider机制创建实例,并将其提供给自身或子组件的功能。如果要使用提供的实例,则需要将其注入到特定的变量中并进行使用。

首先创建一个Http模块的提供者。
在angular2/http中导入HTTP_PROVIDERS,然后在bootstrap的第二个参数中指定。
通过这个bootstrap指定的提供者可以被所有组件访问到。

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {HTTP_PROVIDERS} from 'angular2/http';

bootstrap(AppComponent, [HTTP_PROVIDERS]);

根据Angular2的主流,我们将创建一个名为CommentService的部分,用于从服务器获取评论并向服务器发送评论。

我将创建 comment.service.ts 文件。

import {Injectable} from 'angular2/core';
import {Comment} from './comment'
import {Http, Headers} from 'angular2/http';
import {Observable}     from 'rxjs/Observable';
import 'rxjs/Rx';

@Injectable()
export class CommentService {
  constructor(private http: Http) {
  }

  getCommentsObservable(): Observable<Comment[]> {
    return this.http.get('/api/comments').map(res => res.json() as Comment[])
                                         //.delay(2000)
                                         //.repeat()
  }

  saveCommentObservable(comment: Comment): Observable<Comment[]> {
    const headers = new Headers();
    headers.append('Content-Type', 'application/json');
    return this.http.post('/api/comments', JSON.stringify(comment), {headers:headers})
      .map(res => res.json() as Comment[])
  }
}

请补充以下说明:

* 通过@Injectable()注解和constructor(private http: Http)的描述,将在CommentService中注入之前在bootstrap中指定的Http实例。
* http.get和http.post的返回值为Observable。
* 通过使用Observable.map函数,将Observable的返回值转换为Observable<Comment[]>。
为了使用Observable.map函数,需要声明import ‘rxjs/Rx’。
* 通过指定delay(2000).repeat(),使处理每2秒执行一次。但是由于与后续保存处理的时机可能有冲突,所以这部分代码被注释掉了。
* 对于http.post,需要指定Content-Type,因此进行了添加。


在CommentBox组件中,通过导入CommentService并在providers中进行指定。在该providers中进行实例化后,在CommentBox类的构造函数中进行注入并使用。修改CommentBox组件中直接指定数组数据的部分,改为通过CommentService获取并使用。

import {Component} from 'angular2/core';
import {CommentListComponent} from './comment-list.component';
import {CommentFormComponent} from './comment-form.component';
import {CommentService} from './comment.service';
import {Comment} from './comment'

@Component({
    selector: 'my-comment-box',
    template: 
    `
    <div className="commentBox">
      <h1>Comments</h1>
      <my-comment-list [comments]="comments"></my-comment-list>
      <my-comment-form></my-comment-form>
    </div>
    `,
    directives: [CommentListComponent, CommentFormComponent],
    providers: [CommentService]
})

export class CommentBoxComponent {
  comments: Comment[];

  constructor(private _commentService: CommentService) { }

  ngOnInit() {
    this._commentService.getCommentsObservable().subscribe(comments => this.comments = comments)
  }
}

補充說明如下:
* 通過在@Component的providers中指定CommentService,將會創建一個實例。
* constructor(private _commentService: CommentService)通過_commentService注入。
* ngOnInit()將只會在實例化時被調用一次。
* 在ngOnInit()中調用CommentService的getCommentsObservable()函數。通過訂閱返回值的Observable來獲取值。

如果在浏览器中显示如下内容,则执行成功。

评论
彼特·亨特
嗨!
保罗·奥沙内西
React太棒了!
你好,世界!我是一个评论表单。

添加新的评论

接下来我们要添加表单。

将 app/comment-form.component.ts 文件进行如下修改。

import {Component} from 'angular2/core';

@Component({
    selector: 'my-comment-form',
    template: 
    `
    <form className="commentForm" (ngSubmit)="handleSubmit()">
      <input [(ngModel)]="author" placeholder="Your name" />
      <input [(ngModel)]="text" placeholder="Say something..." />
      <input type="submit" value="Post" />
    </form>
    `
})

export class CommentFormComponent { 
  public author: string
  public text: string

  handleSubmit(e) {
    if (!this.author || !this.text) {
       return;
     }

    // TODO: send request to the server         
    this.author = ''
    this.text = ''
  }  
}

補充說明如下:
* [(ng-model)]=”author”通過這個方法,author變量實現了雙向數據綁定。
* (ngSubmit)是Angular2的指令,用於處理onSubmit事件。我們可以在這裡編寫處理onSubmit事件的代碼,這裡是執行handleSubmit()函數。
* 通過2way數據綁定,handleSubmit()函數中的this.author等變數中包含了輸入值。
* 有關向服務器發送數據的處理將在下一節中進行。這裡僅進行了輸入框的初始化。

如果在执行后,浏览器中添加了两个输入区域并且添加了”post”按钮,则表示成功。

回调函数作为属性:将回调函数作为属性

服务器通信由 CommentBox 负责。更准确地说,是由 CommentBox 组件中的 CommentService 负责。

在 CommentBox 组件中添加 handleCommentSubmit 函数来处理保存到服务器的操作,并将 handleCommentSubmit 函数绑定到 my-comment-form 组件的 onCommentSubmit 事件上,通过 来实现对 ComponentForm 组件的 onCommentSubmit 事件的绑定。

import {Component} from 'angular2/core';
import {CommentListComponent} from './comment-list.component';
import {CommentFormComponent} from './comment-form.component';
import {CommentService} from './comment.service';
import {Comment} from './comment'

@Component({
    selector: 'my-comment-box',
    template: 
    `
    <div className="commentBox">
      <h1>Comments</h1>
      <my-comment-list [comments]="comments"></my-comment-list>
      <my-comment-form (onCommentSubmit)="handleCommentSubmit($event)"></my-comment-form>
    </div>
    `,
    directives: [CommentListComponent, CommentFormComponent],
    providers: [CommentService]
})

export class CommentBoxComponent {
  comments: Comment[];

  constructor(private _commentService: CommentService) { }

  ngOnInit() {
    this._commentService.getCommentsObservable().subscribe(comments => this.comments = comments)
  }

  handleCommentSubmit(comment) {
    this._commentService.saveCommentObservable(comment).subscribe(comments => this.comments = comments)
  }  
}

補充解释如下:
* 在 handleCommentSubmit 函数中调用 CommentService 来执行保存操作。保存操作的结果返回后,将其设置为 comments。
* (onCommentSubmit) 指定了事件的绑定。
* $event 是存储 Angular 事件对象的变量。


在CommentForm组件中接收onCommentSubmit,并在handleSubmit()函数中调用它。

引入 Output 和 EventEmitter ,并使用 @Output() 来指定接收 onCommitSubmit 方法。再从 handleSubmit() 函数中调用。

import {Component, Output, EventEmitter} from 'angular2/core';

@Component({
    selector: 'my-comment-form',
    template: 
    `
    <form className="commentForm" (ngSubmit)="handleSubmit()">
      <input [(ngModel)]="author" placeholder="Your name" />
      <input [(ngModel)]="text" placeholder="Say something..." />
      <input type="submit" value="Post" />
    </form>
    `
})

export class CommentFormComponent { 
  @Output() onCommentSubmit: EventEmitter<any> = new EventEmitter();

  public author: string
  public text: string

  handleSubmit() {
    if (!this.author || !this.text) {
       return;
     }

    this.onCommentSubmit.emit({author: this.author, text: this.text})
    this.author = ''
    this.text = ''
  }  
}

进行补充说明。
* 通过 @Output() onCommentSubmit: EventEmitter = new EventEmitter(); 来指定接收 onCommentSubmit。
* 需要使用 new EventEmitter()。
* 为了使用 @Output() 和 EventEmitter,需要添加 import。
* 使用 this.onCommentSubmit.emit() 调用与 onCommentSubmit 绑定的处理程序。

优化:乐观更新

与服务器的通信是异步处理的,可能需要花费一些时间(尽管在此情况下并不需要),所以在进行保存处理之前,我们会先将临时数据添加到评论中。

在handleCommentSubmit函数内,将要添加的comment对象的id先暂时设定为一个临时值,然后将其添加到this.comments中。在保存到服务器后,通过服务器返回的值重新设置this.comments。

export class CommentBoxComponent {
  comments: Comment[];

  constructor(private _commentService: CommentService) { }

  ngOnInit() {
    this._commentService.getCommentsObservable().subscribe(comments => this.comments = comments)
  }

  handleCommentSubmit(comment) {
    comment.id = Date.now();
    this.comments = this.comments.concat([comment]);
    this._commentService.saveCommentObservable(comment).subscribe(comments => this.comments = comments)
  }  
}

工作已经全部完成了。

总结

所有作業完成後的源碼已在以下位置公開:https://github.com/ma-tu/react-tutorial-with-angular2 的 [ finish-react-tutorial ] 分支。

git clone https://github.com/ma-tu/react-tutorial-with-angular2.git
cd react-tutorial-with-angular2
git checkout finish-react-tutorial
npm install

以上是我們所提供的內容,您覺得如何呢?
希望這個長篇文章能有助於您理解Angular2(或者React)。

请您确认,我们已经根据不同的部分提交了不同的修改,请您查阅。在撰写本文时,我参考了多篇文章。非常感谢您。

广告
将在 10 秒后关闭
bannerAds