Skip to content

使用Angular2构建一个简单的Todo应用

jiyanliang edited this page Apr 6, 2015 · 19 revisions

http://www.htmlxprs.com/post/54/creating-a-super-simple-todo-app-using-angular-2-tutorial

Angular 2 is already popular and so many JavaScript developers are eagerly waiting to try it out. You may have the idea that Angular 2 is going to introduce a lot of new & exciting features and remove several old concepts. So, in this tutorial I will show you how to create a super simple Todo app using Angular 2.0. In the process you will also learn how to use Components, Templates, Data Binding and a few other important stuff. So, let's start!

Angular2越来约火,以至于很多js开发者都向用它写一些例子。当然,你可能更希望应该有一些Angular2令人兴奋的新特性的文章出来。好,那这篇文章我就展示给大家如何使用Angular2来创建一个Todo应用。在这篇文章中,你将会学习如何使用组件、模板、数据绑定等一些重要的东东。让我们现在就开始吧。

Angular 2 is currently in Alpha preview and changing rapidly. Keep an eye on quickstart app for latest changes.

Angular2当前还处在α版本,被不停的修改着。查看quickstart应用可以看到最新的修改记录。

Here is a demo for you to check out. The code is also available on GitHub. If you have any questions please comment or visit devmag.io to participate in the discussion.

这里有个例子,可以下载下来。如果你有啥问题可以提交到devmag.io去参与讨论。

Getting the Seed App

获得种子应用

Angular team has put together a simple quick start app that provides you with all the dependencies required to write Angular 2 apps. So, head over to GitHub and download the repo to your machine.

Angular团队已经把开发Angular2应用所需要的依赖都放到qiuckstart app中。你可以直接在github上下载到你的机器上。

Now you can open the app in your favourite text editor and start modifying it. The name of the root directory is quickstart. So, let's rename it to todoApp.

现在你可以使用你最爱的文本编辑器打开这个app,开始修改了。我们注意当当前的根目录是quickstart,我们把它重命名为todaoapp。

Creating Components

创建组件

In Angular 1.x we have directives. In Angular 2 we will create Components. Every component has two parts : View and Controller. View is the HTML template of your component while Controller provides JavaScript behaviour. You might have heard that Angular 2 doesn't have controllers anymore. Well, this is not exactly true as controllers are now a part of components. So, how does a component look like? Well, let's find it out by creating a component for our todo app.

Angular 1.x中有指令的概念。在Angular2中我们可以直接创建组件。每个组件都有两部分:试图和控制器。试图是你的组件中得HTML模板,二控制器则提供js行为。你可能听说Angular2不再有控制器了,但是我要告诉你,确切的说应该是控制器作为组件的一部分了。那一个组件到底长啥样呢,让我们创建一个todo应用来揭开它的面纱。

Go the root directory and create a file called TodoApp.es6. The extension .es6 means that we are going to follow ES6 syntax here. You can also use the plain old extension .js. Now paste the following content into the file.

找到根目录创建TodoApp.es6文件。.es6后缀意味着我们将要遵循ES6的语法,当然你也可以使用普通的.js作为后缀。现在我们把如下内容贴到改文件中去。

import {Component, Template, bootstrap,Foreach} from 'angular2/angular2';
import {TodoStore} from 'services/TodoStore';
@Component({
  selector: 'todo-app',
  componentServices: [
      TodoStore
  ]
})
@Template({
  url: 'templates/todo.html',
  directives: [Foreach]
})
class TodoApp {

  todoStore : TodoStore;

  constructor(todoStore: TodoStore) {
    this.todoStore = todoStore;
  }

  add($event,newtodo){
      if($event.which === 13){
        this.todoStore.add(newtodo.value);
        newtodo.value = '';
      }
  }

  toggleTodoState(todo){
    todo.done = !todo.done;
  }

}

bootstrap(TodoApp);

Explanation

解释

The first two statements are used to import ES6 modules. The very first line imports Component, Template, bootstrap and Foreach from the modules angular2/angular2. The second statement imports a custom service TodoStore from the module services/TodoStore. Don't worry about how Angular finds these modules for the time being.

前两行代码用于引入ES6模板。第一行代码是从angular2/angular2下引入组件、模板、bootstrap和forEach。第二行代码从模块services/TodoStore中引入了我们的TodoStore服务。不要为Angular能否找到这些模块而担心。

Now, we need to create the controller for our component. Let's name it TodoApp and create the class as following :

现在,我们需要创建我们组件的控制器:名称为TodoApp:

class TodoApp{

}

It's a usual ES6 class with several annotations such as :

在上面的ES6类代码中,有几个注解引起我们的兴趣:

@Component : This signifies that TodoApp is a component. It accepts an object. The selector property indicates what should be the HTML selector for this component. componentServices indicates the services our component depends on. In our case there is only one dependency TodoStore, which is going to be created shortly.

@Component: 这标示TodoApp是一个组件。而参数是一个对象,包含一个选择器属性,用来表示这个组件应该使用啥HTML选择器;同时这个对象还包含componentServices用来标示我们的组件依赖的服务。在我们的例子中,只依赖了TodoStore,后面我们会创建它的。

@Template : This annotation specifies the template that should be used by our component. In our case it's templates/todo.html. So, we make the url property point to the template location. Similarly, directives property indicates the directives required by our template. We are going to need a single directive ForEach for our template. We will just specify this in directives property.

@Template: 这个注解标示它将是我们组件中使用的模板。在我们的例子中是templates/todo.html。因此我们的url属性指向了模板位置。类似的,指令属性表示我们模板中用到的指令。我们将会使用一个简单的指令ForEach到我们的模板中,通过制定指令属性就可以了,很简单。

Now we will add a constructor to our TodoApp class as following :

现在我们将增加一个构造函数到我们的TodoApp类中:

constructor(todoStore: TodoStore) {
   this.todoStore = todoStore;
}

The parameter todoStore: TodoStore tells Angular's DI system to inject an instance of the service TodoStore into our component during initialization. This is called IoC (Inversion of Control). Instead of obtaining our dependencies ourselves, we ask Angular to automatically instantiate the dependencies and inject them into our component. So, we take the instance of TodoStore and store it in an instance variable todoStore.

参数todoStore:TodoStore告诉Angular的DI系统要在初始化阶段注入TodoStore服务的实例到我们组件中。这就是IoC,我们不需要自己去获得依赖,我们让Angular自动的实例化依赖,然后注入到我们组件中。所以我们拿到了TodoStore的实例化,并存到变量totoStore中。

We will discuss about the methods add() and toggleTodoState() subsequently. Now let's take a look at our service TodoStore.

我们先把add和toggleTodoState方法放在一边,现在我们来看下我们的TodoStore服务。

Creating the Service

创建服务

Let's create a file called TodoStore.js inside the directory services. Now paste the following content into the file :

首先在services目录下创建一个名为TodoStore.js的文件,同时把下面的代码复制进去:

export class TodoStore {
    constructor(){
        this.todoList = [];
    }
    add(item){
        this.todoList.unshift({text:item,done:false});
    }
}

Explanation

解释

TodoStore is a simple JavaScript class. It has a constructor and a single method add(). The constructor creates a simple array todoList which holds our todo items. We use this list in our template to render the todo items. The add() method is called everytime a new todo item is about to be created. It takes the todo text, creates an object and adds it to the todoList array. Note that done property indicates if the todo item is marked as done. Finally, we export the class with the help of export keyword.

TodoStore是一个简单的js类,它又一个构造函数和一个add方法

Now go back to TodoApp.es6 and see how we are importing the above class. Creating the template Let's create a file todo.html inside the directory templates. Here is the content of the file :

<style> @import url(http://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css); @import url(http://fonts.googleapis.com/css?family=Open+Sans); </style> <style> .container{ font-family: 'Open Sans', sans-serif; margin-top: 200px; } .done{ text-decoration: line-through; color : #999999; } .bottom-offset{ margin-bottom: 20px; } </style>
{{todo.text}}
Explanation Every template has its own style. You can basically create stylesheets and refer to them by using @import statements inside the templates. We are importing Bootstrap's CSS and Google Fonts via @import. We also have some custom CSS rules which are written inside <style> element. You can also have a different CSS file for this and import it here.

There are two parts in our template which are :

Text Field which takes input from users. An area which shows a list of todo items. We create the text field as following :

<input type="text" class="form-control" placeholder="Write something here" autofocus #newtodo (keyup)="add($event,newtodo)"/> Now let's create our favourite magical two way data binding. Unlike Angular 1.x, you create data binding by writing #modelname. In this case we would like to bind the input field with a model newtodo. So, we add the attribute #newtodo to the input element. As a result whenever you write something into the field, it's automatically synced with the newtodo model. Also we would like to create a todo item when enter key is pressed. So, we will write the following to run a function TodoApp#add() on keyup event :

(keyup)="add($event,newtodo)" Now we need to create a div for showing a list of todo items. This is done as following :

{{todo.text}}
We use *foreach to loop through the set of todo items. We select each item by writing #todo in todoStore.todoList.

For each item, we have a checkbox (to mark the item as done) and a todo text. The text is displayed by the expression {{todo.text}}. Also we apply a class called done when the done property is true. This strikes out the items that are completed. We do it by writing [class.done]="todo.done". When you see an attribute surrounded with [], it means the attribute value is a data binding expression. So, whenever todo.done changes, the class done is added/removed. Similarly, we also bind the checked property of the checkbox with todo.done by surrounding it with []. Next, we want to invoke the function TodoApp#toggleTodoState() whenever the checkbox is clicked.

Revisiting TodoApp.es6 Now, let's discuss about the two functions in TodoApp that we left intentionally earlier.

add() add($event,newtodo){ if($event.which === 13){ this.todoStore.add(newtodo.value); newtodo.value = ''; } } The add function has two parameters : $event and newtodo. This is called every time keyup event is fired by the input field in our template. Inside the function we check whether enter key is pressed. If yes then we call this.todoStore.add() and pass newtodo.value as an argument. Note that although we refer to the model newtodo in our template with #newtodo, the actual value is stored in newtodo.value. The next step is clearing newtodo.value so that the input field is also cleared (due to two way data binding) and we can start adding new items.

toggleTodoState() toggleTodoState(todo){ todo.done = !todo.done; } This is called whenever click event is fired by a checkbox. We simply toggle the value of todo.done property. As a result, data binding kicks in and we see a strikethrough effect in the corresponding todo item (Remember the .done class in template?).

Creating index.html The finally step is creating index.html in the root and loading our main module. Here is the content :

<title>Angular 2 Todo App</title> <script src="/dist/es6-shim.js"></script> Warming up... <script> System.paths = { 'angular2/*':'/angular2/*.js', 'rtts_assert/*': '/rtts_assert/*.js', 'services/*' : '/services/*.js', 'todoApp': 'TodoApp.es6' }; System.import('todoApp'); </script> You should note a few points here :

/dist/es6-shim.js should be loaded into the

. It contains Traceur, ES6 Module Loader, System, Zone, and Traceur options for meta-data annotations. We refer to our main component TodoApp with the selector Warming up.... The text Warming up is displayed while the module is loading. System.paths tells Angular where to look for modules. We have specified a property 'services/' : '/services/.js'. That's why Angular is able to find the module TodoStore when we say import {TodoStore} from 'services/TodoStore' inside TodoApp.es6. Similarly, we have specified the path for todoApp and other required modules. Finally, the statement System.import('todoApp') loads the module todoApp and kicks off the application. If you look into the file TodoApp.es6, you will see a line bootstrap(TodoApp) in the end. It helps bootstrap and initialize the module. Running the app Go to the root directory and type http-server. Now you can access the app at http://localhost:8080.

Note : If you don't have http-server, you can install it with the following command :

sudo npm install -g http-server I hope you enjoyed reading this article. I am as excited as you about Angular 2.0. Let us know what you think in comments or discuss about it on devmag.io.

Clone this wiki locally