Angular.cn
首先必须要给Angular.cn中文网点100个赞,与占着百度头条的angular2.axuer.com不同,它翻译了整个网站,而且当你点击某段文字的话,它会显示它的英文原版。
比如这样:
Angular2网站的英文单词确实不好翻译,比如Tour of Heroes Tutorial(Angular2版的HelloWorld),你可以根据法语的规则翻译成英雄塔教程或者塔式英雄教程,而Angular.cn给了一个很酷炫的名字:《英雄指南》。
安装Angular2
官方推荐了2种方法使用Angular2,Quickstart和Quickstart CLI,前者是基础方式使用,后者是官方提供的脚手架,为了演示和学习的方便,我推荐大家都使用后者创建DEMO,我这里有两篇教程,分别讲解这两种使用方式。
生命周期
我下意识的认为,只要一个语言或者框架有生命周期,那么它一定是最重要的,牢记它可能不能马上对你有帮助,但肯定对你未来的编程生涯大有裨益。
这里有一段官方的对生命周期目的及意义的解释,我们可以根据自己的理解来揣摩一下这段文字的意义。
原链接:生命周期
钩子
目的和时机
ngOnChanges 当Angular(重新)设置数据绑定输入属性时响应。 该方法接受当前和上一属性值的
SimpleChanges
对象当被绑定的输入属性的值发生变化时调用,首次调用一定会发生在
ngOnInit
之前。ngOnInit 在Angular第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。
在第一轮
ngOnChanges
完成之后调用,只调用一次。ngDoCheck 检测,并在发生Angular无法或不愿意自己检测的变化时作出反应。
在每个Angular变更检测周期中调用,
ngOnChanges
和ngOnInit
之后。ngAfterContentInit 当把内容投影进组件之后调用。
第一次
NgDoCheck
之后调用,只调用一次。只适用于组件。
ngAfterContentChecked 每次完成被投影组件内容的变更检测之后调用。
ngAfterContentInit
和每次NgDoCheck
之后调用只适合组件。
ngAfterViewInit 初始化完组件视图及其子视图之后调用。
第一次
ngAfterContentChecked
之后调用,只调用一次。只适合组件。
ngAfterViewChecked 每次做完组件视图和子视图的变更检测之后调用。
ngAfterViewInit
和每次ngAfterContentChecked
之后调用。只适合组件。
ngOnDestroy 当Angular每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏。
在Angular销毁指令/组件之前调用。
使用Angular2
1、插值/模板表达式
插值表达式跟Angular1没什么区别——双大括号包裹数据,Vue也支持这个,值不过Vue叫“声明式渲染”或者“mustache”。
{{title}}
<!--app works-->
与此同时,插值表达式也支持简单的JavaScript运算,这种方法叫做模板表达式,在app.component.ts里赋值两个变量data1和data2。
//app.component.ts
export class AppComponent {
data1 = "hello";
data2 = "world";
}
修改app.component.html,它就会展示我们输入的值
{{data1+data2}}
<!--helloworld-->
值得一提的是模板表达式并不支持所有的JavaScript运算,比如。
赋值 (
=
,+=
,-=
, ...)
new
运算符使用
;
或,
的链式表达式自增或自减操作符 (
++
和--
)
2、绑定语法
用过Angular 1的肯定对ng-model语句印象深刻,这种绑定语法省去了原来JavaScript程序员编写的一大段关于DOM操作的代码,Vue中也有自己的绑定语法语句v-model,而这种优势在Angular 2中也有所体现,我们先来看一段关于数据绑定方向的说明。
数据方向
语法
单向
从数据源到视图目标
{{expression}}//插值表达式 [target] = "expression"//Property、Attribute bind-target = "expression"//类、样式
单向
从视图目标到数据源
(target) = "statement" on-target = "statement"
双向
[(target)] = "expression" bindon-target = "expression"
注:中括号“[]”或者小括号“()”包裹目标“target”的方法是简写形式,“bind-”、“on-”、“bindon-”这种写法是规范形式,不论你使用哪种形式,都能够达到你的目的。
2.1 从数据源到视图目标
我们还是看小例子,我们在app.component.html里放两个p标签,然后绑定它的类名。
<!--app.component.html-->
<p [class]="redText">如果显示绿色就正常了</p>
<p bind-class="redText">如果显示绿色就正常了</p>
然后我们在app.component.css里写上它的样式。
/*app.component.css*/
.redText{ color: red }
.greenText{ color: green }
最后我们在app.component.ts里写上我们要绑定的数据。
//app.component.ts
export class AppComponent {
redText = "greenText";
}
走你┏ (゜ω゜)=☞
OK,没有问题,我们看到,本来我们在页面里给p标签绑定的类名指定它为红色,但是我们在数据源把它改成了绿色,于是它就像我们期望的那样显示成了绿色。
此外,从数据源到视图目标的方法,不光可以绑定类名,还可以绑定属性或者样式,比如。
<!--Property-->
<img [src] = "heroImageUrl">
<hero-detail [hero]="currentHero"></hero-detail>
<div [ngClass] = "{selected: isSelected}"></div>
<!--Attribute-->
<button [attr.aria-label]="help">help</button>
<!--CSS 类-->
<div [class.special]="isSpecial">Special</div>
<!--样式-->
<button [style.color] = "isSpecial ? 'red' : 'green'">
这种绑定方法作用极大,我们今后的也会频繁用到这些。
2.2 从视图目标到数据源
从视图目标到数据源的绑定实际上就是“事件”,官方划分了三种:元素事件、组件事件、指令事件。
<!--元素的事件-->
<button (click) = "onSave()">Save</button>
<!--组件的事件-->
<hero-detail (deleteRequest)="deleteHero()"></hero-detail>
<!--指令的事件-->
<div (myClick)="clicked=$event">click me</div>
原理上大同小异,比如元素事件,其实就是你绑定了“click”方法,但是数据源并不知道你绑它干什么,这时候视图目标就告诉数据源:“你给我执行onSave()方法”,这就是视图目标到数据源的绑定。
2.3 双向
话不多说,小栗子。
<!--app.component.html-->
<p>{{ data }}</p>
<input type="text" [(ngModel)]="data">
当我们在input框里输入任何文字,都会在上方的p标签内显示。对于学过Angular 1 和Vue的人来说这个栗子实在太熟悉了,因为它们两者也有着一模一样的用法,Angular 1使用的是ng-model,Vue使用的是v-model,写法不同,但作用相同。
3、条件与循环
3.1 条件
Angular 1里有ng-if,Vue里有v-if,我们的Angular 2的语法是*ngIf。
注:“ngIf”前面的“*”是一种语法糖,有点类似于jQuery里面的“$”。
它的具体用法就是,根据*ngIf里面的值来判断当前标签是否显示,为了理解,我们将实现下面这个例子:当我们点击按钮时,上方的p标签将消失。
<!--app.component.html-->
<p *ngIf="show">hello world</p>
<button (click)="hide()">点击消失</button>
//app.component.ts
export class AppComponent {
show = true;
hide(): void{
this.show = false;
}
}
上一节我们已经讲到,通过绑定事件的方法我们给button按钮绑定了click事件——它将执行hide()函数,hide()函数将show的值改为了false,所以p标签就消失了。
注:这个函数的写法与我们学过的Javascript写法有些出入,冒号“:”前面是函数名字和括号,括号内可以写形参,冒号后面是函数的返回值类型,这种写法是ES6的写法,关于ES6的更多介绍可以点击传送门:ECMAScript 6 入门,由于后面我们还要用到很多ES6的相关语法知识,所以尽早阅读对你的学习肯定事半功倍。
OK,没有问题,这个栗子很简单,没什么难的,关于“条件”显示语法,Angular 2还提供了一个与众不同的语法——*ngSwitch,学过Javascript或者后台语言的应该对这个语法用法猜一个大概。
<span [ngSwitch]="toeChoice">
<span *ngSwitchCase="'Eenie'">Eenie</span>
<span *ngSwitchCase="'Meanie'">Meanie</span>
<span *ngSwitchCase="'Miney'">Miney</span>
<span *ngSwitchCase="'Moe'">Moe</span>
<span *ngSwitchDefault>other</span>
</span>
数据源可以对toeChoice赋值,视图目标根据数据源提供的值来对页面进行渲染,想一想这种用法可以用在哪里呢?比如说我们要写一个后台管理系统,系统可以根据登录用户的身份不同来分别渲染页面,我认为这种可以全局控制的方法实在很酷,避免了我们写一大堆*ngIf。
值得一提的是,可见性与条件语法完全是两个概念,当元素的display设置为none,或者visibility设置为hidden的时候,不可见的元素仍然在DOM当中占据着位置,系统还有可能会花费很大的内存去检查更新它,而使用条件语法,则会彻底的将元素从DOM当中删除,节省了内存,提高了系统性能。
3.2 循环
还是原来的配方,还是原来的味道,Angular 1里有ng-repeat,Vue里有v-for,我们的Angular 2的语法是*ngFor。
这次的栗子我们打算写一个类。
//app.component.ts
class Student {
id: number;
name: String;
}
然后在导出类AppComponent里初始化一下。
//app.component.ts
export class AppComponent {
ClassTwoStudents: Student[] = [
{
id: 1,
name: "张三"
},
{
id: 2,
name: "李四"
},
{
id: 3,
name: "王五"
},
]
}
现在2班学生里有3个人,分别是张三、李四、王五,然后是页面。
<!--app.component.html-->
<ul>
<li *ngFor="let student of ClassTwoStudents">
<span>{{ student.id }}</span>
<span>{{ student.name }}</span>
</li>
</ul>
注意,这里的语法一定是let student of ClassTwoStudents,写错、或者缺少了任何一个,并且没有很好的调试工具的情况下,这可能是一个非常难以发现的错误。
渲染结果如上图,OK,没有问题。
现在我们想追加一个问题,如何获得这个列表的索引值呢?很简单,只要在*ngFor里加上“let i = index;”就可以了。
<!--app.component.html-->
<ul>
<li *ngFor="let student of ClassTwoStudents;let i=index;">
<span>{{ student.id }}</span>
<span>{{ student.name }}</span>
<input type="button" (click)="showIndex(i)">
</li>
</ul>
我们增加了一个按钮,点击按钮显示索引值。现在我们把逻辑部分补全。
show(idx): void {
console.log(idx)
}
现在我们点击按钮,可以显示他们相应的索引值了,除了index索引值之外,Angular还内置了首元素、尾元素、奇元素、偶元素,但是很遗憾,我们并不能直接对它们进行操作,他们仅用于判断并返回相应的布尔值。为了能清楚的显示结果,我们把我们的列表改成表格。
<!--app.component.html-->
<table>
<tr>
<th>学生id</th>
<th>学生姓名</th>
<th>索引值</th>
<th>是否为首元素</th>
<th>是否为尾元素</th>
<th>是否为偶元素</th>
<th>是否奇元素</th>
</tr>
<tr *ngFor="let student of ClassTwoStudents;
let i = index;
let first = first;
let last = last;
let even = even;
let odd = odd;"
>
<td>{{ student.id }}</td>
<td>{{ student.name }}</td>
<td>{{ i }}</td>
<td>{{ first }}</td>
<td>{{ last }}</td>
<td>{{ even }}</td>
<td>{{ odd }}</td>
</tr>
</table>
显示结果如下。
因此,假如我们想对它们进行操作的话,只能对整个表格进行遍历,然后判断结果为true的时候再进行操作。
4、其他内置指令
4.1 类绑定
4.1.1 单个类绑定
我们有两种方法绑定单一类,一种是第2.1节提到的数据绑定,另一种是通过以下语法。
<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
[class.special]="!isSpecial">This one is not so special</div>
当isSpecial为true时,special类就被自动添加到class里,而!isSpecial的值就为false,special类就不会被添加到class里。
4.1.2 多个类的绑定
在工作当中,只添加一个类往往不能满足客户的需求,客户希望可以添加多个类,于是,客户提供给我们一段文字,他希望可以自由的添加样式。
<!--app.component.html-->
粗体:<input type="checkbox" value="粗体" [checked]="isBold" (change)="bold()">
变红:<input type="checkbox" value="变红" [checked]="isRed" (change)="red()">
下划线:<input type="checkbox" value="下划线" [checked]="isUnderLine" (change)="underline()">
<div [ngClass]="chooseClasses">我就是一段文字</div>
<button (click)="setClasses()">点击切换样式</button>
由于Angular 2不能直接绑定check事件,所以我们改用绑定change事件来代替它(此处先挖一个坑,如果后续学习过程中有更简便的方法再回来更新,如果有谁看到这个文章,并且有更好的建议,也可以邮件联系我13920422663@163.com)
//app.component.ts
chooseClasses: {};
isBold: boolean;
isRed: boolean;
isUnderLine: boolean;
bold(): void{
if(this.isBold==true){
this.isBold = false;
}else{
this.isBold = true;
}
}
red(): void{
if(this.isRed==true){
this.isRed = false;
}else{
this.isRed = true;
}
}
underline(): void{
if(this.isUnderLine==true){
this.isUnderLine = false;
}else{
this.isUnderLine = true;
}
}
setClasses() {
this.chooseClasses = {
bold: this.isBold,
red: this.isRed,
underline: this.isUnderLine
};
}
我们需要声明在html代码中绑定的chooseClasses这个对象,然后补充setClasses这个方法,把所需要的类名添加进来就可以了,当this.isBold、this.isRed、this.isUnderLine为true时,这些类名就会相应的添加到ngClass这个位置。
最后别忘了把样式补充上。
/*app.component.css*/
.red{ color: red }
.bold{ font-weight: bold }
.underline{ text-decoration: underline }
运行。
我们可以使用谷歌浏览器的查看元素功能,看到类名已经添加进来了。
4.2 样式绑定
上节中,我们通过类绑定,修改的文字的样式,其实Angular 2提供了专门的样式绑定语句——style.style-name,我个人极不推荐将样式直接写在html代码上,这样不仅破坏了语义化原则,也不符合MVC模型,而且代码的可读性很差,把所有CSS样式写在专门的CSS文件里,或者使用less或者mass预编译,是我个人更推荐的方式。
尽管有如此多的弊端,但官方给我们提供了这种方式,为了教程的完整性,我还是决定讲一讲,好在样式绑定和类绑定的用法大同小异。
和类绑定一样,样式绑定也分单一样式绑定和多个样式绑定,这里我就不分开讲了,我们对比代码来看。
4.2.1 单一样式绑定
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
This div is x-large.
</div>
4.2.2 多个样式绑定
currentStyles: {};
setCurrentStyles() {
this.currentStyles = {
// CSS styles: set per current state of component properties
'font-style': this.canSave ? 'italic' : 'normal',
'font-weight': !this.isUnchanged ? 'bold' : 'normal',
'font-size': this.isSpecial ? '24px' : '12px'
};
}
都是通过布尔值来判断是否添加样式。
5、表单
表单在前端工程师的开发过程当中实在是太重要了,表单中包含大量的属性的事件,应当是我们学习的重中之重,然而官方在开发指南里的表单讲解过于简单,超过一半的篇幅实际是在讲绑定语法,这我决定按照标签分类来讲解。
5.1 模板引用变量(template reference variable)
官方取名为“变量”,我更倾向于将它理解为“万能钥匙”,写法是"#"或者“ref-”加变量,只要将它写在表单内的任何标签,就可以获取它的属性。
//app.component.html
<input type="text" #f value="beef" name="food" [(ngModel)]="myFood">
<p>input type: {{ f.type }}</p>
<p>input value: {{ f.value }}</p>
<p>input name: {{ f.name }}</p>
输出结果。
在监听input的value属性时,与ngModel一样,可以监听用户的实时输入信息,此外,它还能绑定在radio(单选按钮)或者checkbox(多选按钮),监听它们的checked属性,来判断该按钮是否被选中了,非常方便。
5.2 下拉框
在这一节中,我们该死的客户又回来了,他提出了他的需求,希望我们在一个表单当中写三个下拉框,并获取他们值。
首先说下思路。
- 将“ngForm”赋值给一个模板引用变量
- 在select标签写上ngModel
- 在option标签绑定ngValue,并把select标签的name值赋给它
接下来我们可以直接看代码。
import {Component} from '@angular/core';
@Component({
selector: 'example-app',
template: `
<form #f="ngForm">
<select name="state1" ngModel>
<option value="" disabled>Choose a state1</option>
<option *ngFor="let state1 of states" [ngValue]="state1">
{{ state1.abbrev }}
</option>
</select>
<select name="state2" ngModel>
<option value="" disabled>Choose a state2</option>
<option *ngFor="let state2 of states" [ngValue]="state2">
{{ state2.abbrev }}
</option>
</select>
<select name="state3" ngModel>
<option value="" disabled>Choose a state3</option>
<option *ngFor="let state3 of states" [ngValue]="state3">
{{ state3.abbrev }}
</option>
</select>
</form>
<p>Form value: {{ f.value | json }}</p>
<!-- example value: {state: {name: 'New York', abbrev: 'NY'} } -->
`,
})
export class SelectControlComp {
states = [
{name: 'Arizona', abbrev: 'AZ'},
{name: 'California', abbrev: 'CA'},
{name: 'Colorado', abbrev: 'CO'},
{name: 'New York', abbrev: 'NY'},
{name: 'Pennsylvania', abbrev: 'PA'},
];
}
输出结果。
OK,没有问题,如果我们想取state2的name值,直接写上f.value.state2.name就可取到。
除了这种用法,官方还提供了使用FormControll和FormGroup进行取值,因为这两个模块涉及到面向对象的知识,在这里我先挖一个坑,这将在Angular 2学习笔记——高级篇中讲解。
5.3 input框
运用数据绑定知识和模板引用变量知识已经足够让我们对各种input框的数据做大部分的操作了,我们本节要讨论的是input框的状态。
状态
为真时的 CSS 类
为假时的 CSS 类
控件已经被访问过
ng-touched
ng-untouched
控件值已经变化
ng-dirty
ng-pristine
控件值是有效的
ng-valid
ng-invalid
Angular 2系统会根据状态,为控件添加上相应的类名,理论上,这种机制可以应用到表单内的任何控件内,但是我们绝大多数会把它用在input框(type=text)。
首先我们来看一段代码。
<!--app.component.html-->
<input type="text" class="" id="name"
required
[(ngModel)]="test"
#spy>
<br>input框的类名有: {{spy.className}}
要使用它,必须在input框绑定ngModel属性,然后我们随便给它赋一个初值test=“测试”,为了演示ng-invalid,我们还加上required属性(必须有值)。
我们并没有给class加上任何一个类名,系统为我们自动添加了三个类名,下面我们可以进行测试。
- 当我们的input框第一次获得焦点,并且失去焦点时,ng-untouched变为了ng-touched
- 当我们修改input框内的值时,ng-pristine变成了ng-dirty
- 当我们将内容全部删去时,ng-valid变成了ng-invalid
所以,根据系统给我们提供的这些类名,我们可以编写相应的样式,比如给ng-invalid加上一些红色背景之类的。
5.4 使用ngSubmit提交表单
<!--app.component.html-->
<form (ngSubmit)="test()" #heroForm="ngForm">
<input type="text" class="" id="name"
required
[(ngModel)]="test"
#spy>
<button type="submit" class="btn btn-success" [disabled]="!heroForm.form.valid">Submit</button>
</form>
我们把上一节的input框加入了一个form表单中,出人意料的是系统居然报错了,原来系统告诉你如果你的input框想放在一个form表单里,并且还绑定了ngModel,那么它就必须包含name属性,我们把name属性加上,系统错误就消失了。
表单提交函数并没有采用onSubmit(),我强烈建议自己编写提交函数,这样表单当中出现的错误信息在前台就可以检测到,并且更友善的提示用户修改,这样做可以大大提供系统运行的效率。
表单提交函数之外还绑定了一个模板引用变量,初值是“ngForm”,这个初值指的是整个表单,是系统自动为我们的form标签分配的,所以,当我们绑定了submit按钮的disabled属性值,值是false的情况下,用户是无法点击提交的。
6、管道
7、使用HTTP通信
8、动画
由于动画模块没有包含在我们的《英雄指南》里,所以Angular 2官网上的的动画模块描述的十分模糊,我们没有办法通过像操作《英雄指南》那样直接Ctrl+C/V的操作直接展示出效果,不过万幸的事,你还在坚持读我这篇博文,我将在接下来的介绍中,展示如何使用Angular 2的动画模块。
我们要完成的内容是,在组件A里创建一个新组件B,组件A里有一个按钮,通过点击按钮,来触发组件B中的动画。
所以,第一步,创建新组件,在控制台输入以下命令。
ng g new component newComponent
驼峰命名规则会被系统自动转化为“烤串”命名规则:app-new-component,
在组件A里,我们需要创建一个按钮和新组件的入口标签。
<!--app.component.html-->
<button (click)="change()">走你!</button>
<app-new-component [issildeOut]="sildeOutChange"></app-new-component>
button按钮绑定一个点击事件change(),新组件的入口标签绑定了一个属性“issilderOut”,并给它赋了一个初值“sildeOutChange”,接着我们去写逻辑。
//app.component.ts
sildeOutChange: boolean = true;
issildeOut: boolean;
change(): void{
if(this.issildeOut==true){
this.sildeOutChange=false;
this.issildeOut=this.sildeOutChange
}else{
this.sildeOutChange=true
this.issildeOut=this.sildeOutChange
}
}
很简单,到目前为止,我们还没涉及动画模块的内容,change()函数的作用就是判断issildeOut这个值为true的时候,点一下就为false,如果为false,点一下就为true了,这里我们并没有直接给issildeOut赋值,而是通过一个中间变量sildeOutChange,具体目的,我稍后介绍,先看组件B的逻辑。
//new-component.componet.ts
import { Component, OnChanges, Input, trigger, state, style, animate, transition } from '@angular/core';
@Component({
selector: 'app-new-component',
templateUrl: './new-component.component.html',
styleUrls: ['./new-component.component.css'],
animations: [ // 动画的内容
trigger('sildeInOut', [
// state 控制不同的状态下对应的不同的样式
state('sildeIn' , style({ transform: 'translateX(204px)' })),
state('sildeOut', style({ transform: 'translateX(0)' })),
// transition 控制状态到状态以什么样的方式来进行转换
transition('sildeIn => sildeOut', animate('1000ms')),
transition('sildeOut => sildeIn', animate('1000ms')),
])
]
})
export class NewComponentComponent implements OnChanges {
@Input() issildeOut : boolean;
sildeInOut = 'sildeIn'; // 避免ngOnChanges()并降低代码复杂度
ngOnChanges() {
console.log("ngOnChanges函数已经执行!");
console.log("在ngOnChanges里,issildeOut值为:"+this.issildeOut);
this.sildeInOut = this.issildeOut ? 'sildeIn' : 'sildeOut';
}
}
还记不记得我这篇博文一开始提到的生命周期?ngOnChanges执行于组件生命周期的第一个阶段,组件会判断输入属性是否发生改变,因此,在组件A里,如果我们不通过中间变量直接对issildeOut进行赋值的话,它的作用域仅存在于组件A内部,而无法影响的组件B,事实上,如果不通过中间变量的方式,在组件B里的issildeOut值,将永远是undefined。
再看逻辑,我们引入了执行动画的所有模块,包括:
- OnChanges:生命周期钩子
- Input:输入属性
- trigger:动画触发器
- state:控制不同状态
- style:控制样式
- transition:控制过渡状态(初态→终态/终态→初态)
- animate:控制过渡状态(动画持续时间/延迟显示/动画执行曲线等)
了解了这些,剩下的我们只需要像操作《英雄指南》那样Ctrl+C/V就可以了。
当然,不要忘了在组件B的视图目标上绑上你写好的动画。
<!--new-component.html-->
<p [@sildeInOut]="sildeInOut">
new-component works!
</p>
OK,没有问题。