-
Notifications
You must be signed in to change notification settings - Fork 2
使用Angular2构建一个简单的Todo应用
Angular2越来约火,以至于很多js开发者都想用它写一些例子。当然,你可能更希望有一些Angular2令人兴奋的新特性的文章出来。好,那这篇文章我就展示给大家如何使用Angular2来创建一个Todo应用。在这篇文章中,你将会学习如何使用组件、模板、数据绑定等一些重要的东东。让我们现在就开始吧。
Angular2当前还处在α版本,被不停的修改着。查看quickstart应用可以看到最新的修改记录。
这里有个例子,可以下载下来。如果你有啥问题可以提交到devmag.io去参与讨论。
Angular团队已经把开发Angular2应用所需要的依赖都放到qiuckstart app中。你可以直接在github上下载到你的机器上。
现在你可以使用你最喜欢的文本编辑器打开这个app,开始编码了。我们注意到,当前的根目录是quickstart,我们把它重命名为todaoapp。
Angular 1.x中有指令的概念,而在Angular2中我们可以直接创建组件。每个组件都有两部分:视图和控制器。视图是你的组件中得HTML模板,而控制器则提供js的行为。你可能听说Angular2不再有控制器了,但是我要告诉你,确切的说应该是控制器作为组件的一部分了。那一个组件到底长啥样呢,让我们创建一个todo应用来揭开它的面纱。
找到根目录创建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);
前两行代码用于引入ES6模板。第一行代码是从angular2/angular2下引入组件、模板、bootstrap和forEach。第二行代码从模块services/TodoStore中引入了我们的TodoStore服务。不要为Angular能否找到这些模块而担心,后面我们会解释。
现在,我们需要创建我们组件的控制器:名称为TodoApp:
class TodoApp{
}
在上面的ES6类代码中,有几个注解引起我们的兴趣:
@Component: 这标识TodoApp是一个组件。而参数是一个对象,包含一个选择器属性,用来表示这个组件应该使用啥HTML选择器;同时这个对象还包含componentServices用来标示我们的组件依赖的服务。在我们的例子中,只依赖了TodoStore,后面我们会创建它的。
@Template: 这个注解标识它将是我们组件中使用的模板。在我们的例子中是templates/todo.html。因此我们的url属性指向了模板位置。类似的,指令属性表示我们模板中用到的指令。我们将会使用一个简单的指令ForEach到我们的模板中,通过制定指令属性就可以了,很简单。
现在我们将增加一个构造函数到我们的TodoApp类中:
constructor(todoStore: TodoStore) {
this.todoStore = todoStore;
}
参数todoStore:TodoStore告诉Angular的DI系统要在初始化阶段注入TodoStore服务的实例到我们组件中。这就是IoC,我们不需要自己去获得依赖,而是让Angular自动的实例化依赖,然后注入到我们组件中。所以我们拿到了TodoStore的实例化,并存到变量totoStore中。
我们先把add和toggleTodoState方法放在一边,现在我们来看下我们的TodoStore服务。
首先在services目录下创建一个名为TodoStore.js的文件,同时把下面的代码复制进去:
export class TodoStore {
constructor(){
this.todoList = [];
}
add(item){
this.todoList.unshift({text:item,done:false});
}
}
TodoStore是一个简单的js类,它有一个构造函数和一个add方法。构造函数创建了一个简单的数组todoList用于存放我们的todo对象。我们在模板中使用这个数组去渲染todo数据。每当一个新的toto对象被创建时候都会调用add方法。add方法接收文本参数后包装成一个对象放入到todoList数组中。注意下,done属性用来表示todo对象是否被完成了。最后我们使用export关键字来导出这个class。
现在回到TodoApp.es6,来看下我们如何导入上面的类的。
在templates目录下创建todo.html文件,内容如下:
<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>
<div class="container">
<div class="row">
<div class="col-xs-4 col-xs-offset-4">
<div class="bottom-offset">
<input type="text" class="form-control" placeholder="Write something here" autofocus #newtodo (keyup)="add($event,newtodo)"/>
</div>
<div *foreach="#todo in todoStore.todoList">
<input type="checkbox" [checked]="todo.done" (click)="toggleTodoState(todo)"/> <span [class.done]="todo.done">{{todo.text}}</span>
</div>
</div>
<div class="col-xs-4"></div>
</div>
</div>
每个模板都有自己的样式。你可以先创建一个样式表,然后使用@import来引入到模板来。我们使用@import引入了Bootstrap得样式和google的字体样式。另外还有一些定制的css样式,当然你也可以写到另一个css文件中然后引入进来。
模板中又如下两部分:
- 供用户输入的输入框
- 显示todo对象的区域
来看下如何创建text输入框:
<input type="text" class="form-control" placeholder="Write something here" autofocus #newtodo (keyup)="add($event,newtodo)"/>
现在我们创建魔法般的双向数据绑定。不像Angular1.x,这里通过加入#modelname就可以实现数据绑定了。在这个例子中我们把模型newtodo绑定在了Input输入框中,输入框会和模型newtodo的数据自动同步的。同时我们想在按enter键的时候创建一个todo对象,因此我们在keyup事件上写了TodoApp#add()方法。
(keyup)="add($event,newtodo)"
现在我们来创建一个展示todo列表的div,看下面的代码:
<div *foreach="#todo in todoStore.todoList">
<input type="checkbox" [checked]="todo.done" (click)="toggleTodoState(todo)"/>
<span [class.done]="todo.done">{{todo.text}}</span>
</div>
我们使用*foreach来循环todo列表,通过#todo对象来取出todoStore.todoList中每个对象。
针对每个对象,我们有一个checkbox和一个todo内容文本。内容文本通过{{todo.text}}来展示,如果该todo已经完成,则加上一个class,看下代码:[class.done]="todo.done"。当你看到[]包围着一个属性的时候,则说明这个属性值是数据绑定表达式。因此当todo.done变化的时候,done属性会被新增和移除。类似的,我们使用[]来包围着checkbox的checked属性。下一步我们要做的就是当checkbox被点击的时候要触发TodoApp#toggleTodoState()方法。
现在我们看下TodoApp中得两个方法:
* add()
add($event,newtodo){
if($event.which === 13){
this.todoStore.add(newtodo.value);
newtodo.value = '';
}
}
add方法有两个参数:$event和newtodo。该方法会每当keyup事件被触发的时候执行。在方法内部,我们做了一个是否是enter键的判断,如果是,则调用this.todoStore.add(newtodo.value)。注意,虽然我们在模板中引入的是模型newtodo,但是实际的值是存在newtodo.value中得。下一步是清除掉newtodo.value,这样输入框中得取值也会被清掉(因为双向数据绑定),我们就可以新增新的todo项了。
* toggleTodoState()
toggleTodoState(todo){
todo.done = !todo.done;
}
这个方法会在checkbox被点击的时候触发,我们只是简单的toggle了todo.done属性的值。这样,因为有数据绑定,我们会看到已经完成的todo会有删除线效果(还记得.done的class样式么)。
最后一步是在跟目录下创建index.html来加载我们的主模块,内容如下:
<html>
<head>
<title>Angular 2 Todo App</title>
<script src="/dist/es6-shim.js"></script>
</head>
<body>
<todo-app>Warming up...</todo-app>
<script>
System.paths = {
'angular2/*':'/angular2/*.js',
'rtts_assert/*': '/rtts_assert/*.js',
'services/*' : '/services/*.js',
'todoApp': 'TodoApp.es6'
};
System.import('todoApp');
</script>
</body>
</html>
- /dist/es6-shim.js应该包含在head标签里面,该文件包含了Traceur, ES6模块加载器, System, Zone以及Traceur的注解选项。
- 我们的TodoApp组件的选择器指向的是
<todo-app>Warming up...</todo-app>
,Warming up...
会在组件加载完成之前一直显示着。 - System.paths告诉Angular到哪里去寻找模块。我们指定了属性'services/' : '/services/.js',这就是为什么Angular能够找到TodoApp.es6中TodoStore得原因。类似,我们还制定了todoApp的路径和一些其他必须得模块。
- 最后,System.import('todoApp')加载了todoApp模块,这样就可以完成整个应用了。如果你仔细研究TodoApp.es6,你会发现最后一行bootstrap(TodoApp),它是用来帮助bootstrap来初始化模块的。
到根目录下,输入http-server。你就可以通过http://localhost:8080来访问了。
注意:如果你没有http-server,你可以通过如下命令安装下:
sudo npm install -g http-server
我希望你已经很愉快的阅读了这篇文章。我对Angular2.0充满兴奋。如果你有啥想法可以直接留言或者参与devmag.io上得讨论。
参考文档:http://www.htmlxprs.com/post/54/creating-a-super-simple-todo-app-using-angular-2-tutorial