1. 写在前面

之前我们跑了Angular的Hello World,你是不是对它有点感觉了呢?这一篇将结合一个TODO程序来继续学习Angular的用法。

梳理一下之前的Hello World程序。我们写了一个main.ts来引导模块AppModule,而该模块又包含组件AppComponent,这是一个Angular应用最基本的结构。下面再来简单地看看Angular各部件的含义。

先看Module(模块)。Angular应用是模块化的,每一个Angular应用至少有一个模块,被称为根模块,通常我们会以AppModule来命名。要想定义一个模块类,只需要在类上加上@NgModule装饰器。@NgModule还接收一个元数据对象,该对象有如下几个重要的属性,其中前3个在Hello World中已经见到过:

  1. declarations: 声明本模块中拥有的视图类。 Angular 有三种视图类:组件、指令和管道。
  1. bootstrap: 指定应用的主视图(称为根组件),它是所有其它视图的宿主。只有根模块才能设置bootstrap属性。
  2. imports: 导入其他模板类。
  3. exports: 导出本模块的视图类。
  4. providers: 服务的创建者,并加入到全局服务列表中,可用于应用任何部分。

接下来是Component(组件)。组件是负责控制UI上一小块区域的部件,比如一个用户列表,一个标题栏等,我们把这一小块区域称之为视图。要想定义一个组件,我们只需要在组件类的上面添加@Component装饰器,装饰器接受的元数据包含视图的名字(selector),视图的模板(templatetemplateUrl),视图的样式(styleUrls)等属性。而在组件类里面,我们会定义一些与视图相关的属性和视图的逻辑操作。

以上的两个概念ModuleComponent我们在Hello World中都有些体会,在接下来的TODO例子中会介绍模板语法,主要介绍数据绑定,它的意思就是将变量视图绑定起来,这么做的好处就是一旦变量发生变化或者视图发生变化,它们都会“同步更新”对方(双向数据绑定)。

2. TODO应用

好了,不码概念了,概念听得越多越糊涂。还是来看实际的例子比较靠谱。点击这里查看演示效果。

这次我们不自己手动建工程了,采用另一种方式:使用git克隆Angular的quickstart项目,在此之上进行开发。

使用如下命令进行克隆:

git clone https://github.com/angular/quickstart  angular-todo

idea或其他IDE打开angular-todo工程,在从命令行输入npm install安装依赖库。接着运行npm start,官方的Hello World就跑起来了,我们几乎不用修改它写好的代码(app.module.ts除外),只需在上面添加代码即可。

采用Angular模块化的思维去分析TODO应用,由于功能很简单,我们没必要建立新的模块,只需要建立一个TODO组件即可,页面分为如下形式:

下面来实现TodoComponent。首先是新建todo.component.tstodo.component.htmltodo.component.css三个文件。然后在todo.component.ts定义出TodoComponent,并指定模板(通过templateUrl属性)和样式(通过styleUrls属性):

import {Component} from '@angular/core';

@Component({
moduleId: module.id,
selector: 'todo',
templateUrl: 'todo.component.html',
styleUrls: ['todo.component.css']
})
export class TodoComponent {
}

以上和Hello World中的代码没有多少区别,相信很容易看懂。Todo组件先写到这,我们现在将它声明到AppModule中:

import {NgModule}      from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import {AppComponent} from './app.component';
import {TodoComponent} from './todo.component'; @NgModule({
imports: [BrowserModule, FormsModule],
declarations: [AppComponent, TodoComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}

注意,以上还引入了官方的FormsModule模块,它用于form元素的相关操作,我们待会会用到,这里提前引入。现在,我们就可以在AppComponent中使用自定义的todo组件了。和之前分析的结构一样,模板中先是一个标题,再是todo组件,然后添加点样式:

import {Component} from '@angular/core';

@Component({
selector: 'my-app',
template: `
<div class="main-container">
<h1>TODO</h1>
<todo></todo>
</div>
`,
styles:[`.main-container{width: 960px; margin: 0 auto;}`]
})
export class AppComponent {
}

现在基本的骨架就OK了,接下来的重点就是todo组件的具体编写了。之前有提过,使用Angular等数据驱动的框架和写jQuery等库最大的不同在于Angular是以数据为中心,任何变化的视图(或者不变的视图,只要你愿意)背后必然对应着相应的数据结构。拿我们的todo应用来说,视图中的todo列表的背后就对应着一个数组,todo列表的每一项就对应数组中的某一个位置上的元素。如果我们想添加或删除todo列表项,那么只需要对对应的数组进行添加或删除操作;如果我们想修改todo列表的某一项的信息,我们只需要修改对应的数组的对应位置上的todo对象即可。要知道,我们用JS修改一个变量,要比直接修改DOM轻松的多。如果你是第一次使用Angular这类采用数据驱动的框架,你一定会觉得It's amazing,妈妈再也不用担心我拼接html字符串了。

有了上面的分析,我们先要将一个todo项的数据结构定义出来,因此在todo.component.ts中,添加如下代码:

class Item {
content: string; // todo内容
date: Date; // todo创建日期
done: boolean; // 是否完成 constructor(content: string) {
this.content = content;
this.date = new Date();
}
}

然后在TodoComponent类中,我们需要定义一个Item数组,表示我们的todo列表,并且我们还要需要一个添加todo项的方法:

export class TodoComponent {
todoList: Item[] = []; addTodo(value: any): void {
this.todoList.push(new Item(value));
}
}

OK,TodoComponent类暂时就完成了,代码很简单,就不做说明了。接下来就是todo组件模板和样式的编写。模板代码其实和我们平常写的html代码差不多,不同之处在于加了一些Angular自定义的语法,用于控制页面的渲染,模板代码如下:

<p>
<label for="todo-input">Todo: </label>
<input id="todo-input" type="text" #input (keyup.enter)="addTodo(#input.value)"/>
</p>
<label>List: </label>
<ol>
<p *ngIf="todoList.length == 0">Empty...</p>
<li *ngFor="let item of todoList" [class.done]="item.done">
<label>
<input type="checkbox" [(ngModel)]="item.done">
<span [style.color]="item.done ? 'gray' : 'green'">{{item.content}}</span>
-
<span class="hint">{{item.date | date:"yyyy-MM-dd HH:mm:ss"}}</span>
</label>
</li>
</ol>

要重点说说上面的模板语法。首先最先让你困惑的是这句,

<input id="todo-input" type="text" #input (keyup.enter)="addTodo(input.value)"/>

#input是什么鬼?其实它的官方叫法是template reference variable(模板引用变量),它用于在模板中对DOM元素或指令的引用。这里#input就是用input变量来引用它所在的input元素(即<input .../>),因此在后面(keyup.enter)="addTodo(input.value)"中,我们可以用input.value拿到输入框输入的值。那么keyup.enter又是什么鬼呢?它其实被称为Event binding(事件绑定)。为了说清楚事件绑定的概念,我们把它拆开来看,一个是事件,一个是绑定。

先说事件。这和原生js一样,比如鼠标点击(click),上面的按键后松开(keyup)等,都是事件。并且Angular还能自定义事件,这个先不做说明。上面的keyup.enter事件会在键盘enter键抬起时触发,触发后会调用我们在TodoComponent中写好的addTodo方法,并把input.value作为参数传递进去。

再说绑定。之前已经多次提到过,绑定实际上就是把数据与视图,事件与方法等关联起来,一方发生变化或被触发,另一方自动地随之做出变化或被触发。绑定是有方向的,这个方向指的是数据流动的方向,因此可分为如下三类:

这三类绑定方式的语法分别是[]()[()]

// 第一种,数据源 -> 视图
<img [src] = "user.avatar">
// 第二种,视图 -> 数据源
<button (click) = "delete()">删除</button>
// 第三种,视图 <-> 数据源
<input [(ngModel)]="user.name">

在todo组件的模板代码中,我们还用到了两个指令*ngIf*ngFor,它们是Angular的内置指令,用法根据上面的例子应该就能看懂。最后还有一个语法是管道,操作符是:”|”,这个跟其他框架中所谓的“过滤器”的概念很像。它将操作符左侧的表达式的值作为右侧函数的输入,最终整个表达式的值由右侧函数的返回值决定。管道还能串联。

以上就是模板的基本语法,只是简单的说明,具体请参考文档

这样todo组件的模板就写好了,最后我们可以在todo.component.css中加些样式。比如:

.hint{
color: #999999;
}
.done{
text-decoration: line-through;
}
li{
line-height: 2;
}

以上便是整个TODO应用的编写过程,你跑出来了吗?目前程序还有一个小缺陷,就是我们回车,输入的内容被加入todo列表后,输入框应该被清空,这个就交给你去完成了。

Angular学习笔记(2)——TODO小应用的更多相关文章

  1. Angular 学习笔记 (version 6 小笔记)

    1. lazyload 的 path 变成相对路径了, 不过如果你用 ng update 的话, 依然可以不需要修改, cli config 好像能调支持绝对路径的写法. const routes: ...

  2. angular学习笔记(三十一)-$location(2)

    之前已经介绍了$location服务的基本用法:angular学习笔记(三十一)-$location(1). 这篇是上一篇的进阶,介绍$location的配置,兼容各版本浏览器,等. *注意,这里介绍 ...

  3. angular学习笔记(三十一)-$location(1)

    本篇介绍angular中的$location服务的基本用法,下一篇介绍它的复杂的用法. $location服务的主要作用是用于获取当前url以及改变当前的url,并且存入历史记录. 一. 获取url的 ...

  4. angular学习笔记(三十)-指令(10)-require和controller

    本篇介绍指令的最后两个属性,require和controller 当一个指令需要和父元素指令进行通信的时候,它们就会用到这两个属性,什么意思还是要看栗子: html: <outer‐direct ...

  5. angular学习笔记(三十)-指令(7)-compile和link(2)

    继续上一篇:angular学习笔记(三十)-指令(7)-compile和link(1) 上一篇讲了compile函数的基本概念,接下来详细讲解compile和link的执行顺序. 看一段三个指令嵌套的 ...

  6. angular学习笔记(三十)-指令(7)-compile和link(1)

    这篇主要讲解指令中的compile,以及它和link的微妙的关系. link函数在之前已经讲过了,而compile函数,它和link函数是不能共存的,如果定义了compile属性又定义link属性,那 ...

  7. angular学习笔记(三十)-指令(6)-transclude()方法(又称linker()方法)-模拟ng-repeat指令

    在angular学习笔记(三十)-指令(4)-transclude文章的末尾提到了,如果在指令中需要反复使用被嵌套的那一坨,需要使用transclude()方法. 在angular学习笔记(三十)-指 ...

  8. angular学习笔记(三十)-指令(5)-link

    这篇主要介绍angular指令中的link属性: link:function(scope,iEle,iAttrs,ctrl,linker){ .... } link属性值为一个函数,这个函数有五个参数 ...

  9. angular学习笔记(三十)-指令(2)-restrice,replace,template

    本篇主要讲解指令中的 restrict属性, replace属性, template属性 这三个属性 一. restrict: 字符串.定义指令在视图中的使用方式,一共有四种使用方式: 1. 元素: ...

随机推荐

  1. [RxJS] Creating Observable From Scratch

    Get a better understanding of the RxJS Observable by implementing one that's similar from the ground ...

  2. 页面显示磁盘空间使用情况-Agedu

    下载:http://www.chiark.greenend.org.uk/~sgtatham/agedu/ [root@localhost ~]# tar zxvf agedu-r9723.tar.g ...

  3. windows系统还原

    windows系统还原 windows 系统还原有两种方法: 方法一.开始-控制面板-系统和安全-备份和还原 (或者开始—所有程序—附件—系统工具—系统还原) 详细请看下面的截图说明 方法二.开机的时 ...

  4. Thread 常搞混的几个概念sleep、wait、yield、interrupt (转)

    原文网址:http://blog.csdn.net/partner4java/article/details/7993420sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到 ...

  5. 数据库分库分表(sharding)系列(二) 全局主键生成策略

    本文将主要介绍一些常见的全局主键生成策略,然后重点介绍flickr使用的一种非常优秀的全局主键生成方案.关于分库分表(sharding)的拆分策略和实施细则,请参考该系列的前一篇文章:数据库分库分表( ...

  6. 关于plist文件

    一.代码创建plist文件: NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomain ...

  7. 关于(x&y)+((x^y)>>1)的探究

    今天在程序员面试宝典上看到 int f(int x int y ) { return (x&y)+((x^y)>>1) } f(729,271) 结果为500 从式子中可以看出分为 ...

  8. socket网络编程中的同步,异步,阻塞式,非阻塞式,有何联系与区别?

    一.举个打电话的例子: 阻塞   block   是指,你拨通某人的电话,但是此人不在,于是你拿着电话等他回来,其间不能再用电话.同步大概和阻塞差不多. 非阻塞   nonblock   是指,你拨通 ...

  9. 【译】神经网络与深度学习 Ch1-Section0

    用神经网络识别手写数字 人类的视觉系统是是大自然的奇迹.考虑下面手写数字序列: 大多数人能够轻易地是识别出是504192.在我们大脑的每个半球都有一个基础的皮质,这就是我们熟知的V1区,它包含了14亿 ...

  10. C# winform 窗体 彻底退出窗体的方法

      1.this.Close();   只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若有托管线程(非主线程),也无法干净地退出: 2.Application.Exit();  强制所有消 ...