Angular2 初探

jopen 4年前

Angular2初探

盼星星盼月亮终于把angular2 beta盼来了,在beta前一个月快一天一个release都把小G折磨坏了,小G公司用的angular2,然后就跟着Google那些工程师一天一个update,然后还要看update把以前的啥功能break了没,当时改rxjs的api的时候真的是想死的心都有了。还有rxjs带来的各种 bug,终于迎来了angular2 beta,码农翻身做主人,真是这个月最开心的事情了。接下来就是把所有code改好build pass就好。小G用angular已经两年多了,一开始用的angular做了一个officeapp,公司看不错就migrate到angular2 的webapp,所以小G是经历过angular1和angular2的人,看过angular1的各种丑和angular2的各种帅。所以这篇文章主要是强推angular2的软文,主要快速上手angular2,介绍一下angular2的主要优势,快速接触angular2的一些语法及 Component,Service的用法。还有就是介绍小G最喜欢的rxjs,因为angular2已经基于rxjs了,把所有东西变成stream然后各种神操作。当然一篇blog不足够介绍这么多,所以小G会分以下几块:

  1. 本篇blog会初步介绍angular2的一些简单语法及快速上手。
  2. 下一篇blog会介绍angular form 的用法
  3. 再下一篇blog会介绍angular2里面的dependency injection的主要用法以及用ood的方法来做dom manipulation。
  4. 下下篇最重要会介绍小G最爱的rxjs。
  5. 小G还会写个cheatsheet,关于angular2的主要用法以及rxjs的主要用法。rxjs的主要用法按decision tree的方法展示。

参考资料

我是第一次见有公司能把framework的文档写的这么好的,google真是给了我惊喜。如果大家不想从头开始写,小G我写了个简单app,囊括了google angular2文档里的tutorial。这里上链接:
Angular 2 quick start. 所以这个app的文件结构是这样的:

├──angular2/                                      │   │  │   ├──app/                                 │   │   ├──boot.ts                            │   │   ├──app.component.ts     │   │   ├──hero/                  //这篇博客用     │   │   |   |──hero.component.ts  │   │   ├──hero-form/             //form blog 用  │   │   |   ├──hero.ts                           │   │   |   |──hero-form.component.ts  │   │   |   |──hero-form.component.html  │   │   ├──hero-list/             //dependency injection 用  │   │   |   ├──...  │   ├──index.html  │   ├──package.json   //用来安装我们需要的库, 以及node的一些命令  │   ├──tsconfig.json  //用来配置typescript  │   ├──style.css      //这个playground的css

package.json

用过前端的都知道,npm拯救了世界。所以我们用angular2的时候肯定也会用上npm来帮我们加各种库。所以对node或者npm熟悉的肯定也知道package.json。 在package.json里面,你可以定义script:
"scripts": {      "tsc": "tsc",      "tsc:w": "tsc -w",      "lite": "lite-server",      "start": "concurrent \"npm run tsc:w\" \"npm run lite\" "   },

在这里你可以设置你一会要用npm跑的命令,以这个为例,如果你跑npm start,那他就会先跑npm run tsc用tsc compile typescript到javascript,然后跑npm run lite跑server来host整个app。
还有dependencies和devdepdencies,你可以在里面加你想要使用的库,这样你就可以用npm install安装你所有想要的库。这里有个小trick,如果你想把这个库装到系统里面,就要跑

1 
npm install -g [test] 

如果你只想随便使用一下,就跑:

1 
npm install [test] 

如果你不仅想用还想加到packge.json里面,那就要使用

1 
npm install --save [test] 

typescript.json

这个文件主要用来配置typescript的compiler,比如用啥target,用啥module,是不是要设置sourcemap,是不是要要设输出路径什么的。小G在这里配置一个输出dist,这样生成的js和map就不会和tsfile合在一起,他们会到一个单独的文件夹,
然后index.html会去查找。

index.html

这个是整个app存在的html支持,angular2说白了还是javascript,它是不能脱离html的存在。所以跟普通的javascript一样,需要html来load这个框架。在这个例子中小G就使用了systemjs,这个库能找到angular2 的入口然后把整个app load出来。
当然不仅仅可以使用systemjs,还有一些其他的框架比如webpack,可以bundle你的库让你的库不再成为一团乱麻。不过代价是他会有个很奇怪的build 方式,他有个server来查看你的code的改变然后在memory里面compile typescript然后把编译好的javascript放到memory里面然后用webpack dev server host然后让index.html直接找,这样的坏处是在debug的时候sourcemap很难找。虽然chrome debugger可以很容易解决这个问题。但是其他编译器比如vscode的chrome debugger extension就没有那么智能了。小G曾经尝试用vscode debug angular webpack starter,过程非常痛苦。

boot

Angular2 对于angular1一个最大的进步是所有的东西都模块化了,web component好像变成了现在web technology的一个趋势。比如google正在推崇的polymer就完全使用了web component的思想,他给你很多个component,就像搭积木一样把你要的component下载下来,然后搭起来搭成你想要的一个web app。小G觉得这个想法是很不错,不过小G试了下polymer反应还是有点慢,虽然那些component做的很好看很material design,但是用户体验太慢的话还是不行吧。但是我相信polymer一直在进步,总有一天可以很好的使用它。这个boot等于整个app的入口,在boot.ts里面加入了这个app的root component,然后在index里面用systemjs调用这个boot,就把这个app整个load起来了。具体如下:
index.html      |      |systemjs调用boot    boot      |boot调用模块化app的root component      |    app

而boot在调用模块化的component root的时候使用了bootstrap()的函数,我们可以importanglar2/core来得到bootstrap()这个函数。看到这里大家可能要想为啥不直接用index load app 为啥要通过一个boot呢?确实如果只想做web app,可以直接用systemjs来load app。有boot的原因是因为我们可能不仅仅想做web application,
我们还想做native app, 还想做cordova或者ionic的app,这样只需要替换boot其他所有的代码都可以复用,这是angular2的一个很大的改善。angular1里面并没有很好的支持这一个功能。

app

正如小G刚才所说,angular2把所有的组建都模块化了,在angular2里面,你的整个app 可以变成很多个模块,这些模块可以定义输入输出,也可以继承,可以拼接,这些模块就是不同的component,component之间的通信,数据交换就用service。在小G写的例子里,可以看到就加入了三个模块:hero,hero-form,heros-list。
@Component({      selector: 'my-app',      template:`      < hero>< /hero>      < hero-form>< /hero-form>      < heroes-list>< /heroes-list>      `,      directives:[HeroComponent, HeroFormComponent,HeroesListComponent]  })

首先@Component是angular2里面新引进的函数,通过这个函数我们可以设置selector,template,directives,styles等等。当然如果你要使用Component这个函数就要引入库:

1 
import {Component} from 'angular2/core'; 
当然还可以引入子模块,用相对路径即可:
import {HeroComponent} from './hero/hero.component';  import {HeroFormComponent} from './hero-form/hero-form.component';  import {HeroesListComponent} from './hero-list/heroes-list.component';

selector

主要用来定义外部的模块如何使用这个模块。比如在app.component中selector是my-app,那在外部的index.html里面要load app.component这个模块就要使用

template

主要用来定义这个模块里面的html结构,他也可以加入内部的模块,比如这个 app.component就加入了三个不同的子模块。这样写html是不是好像就像搭积木一样简单了?有一点要注意的是当template引用html 的时候需要使用‘’符号,一般这个符号在键盘上数字键1的左边。<br /> 在template里还有一个很重要的概念,用过angular1的童鞋都知道怎么在html里面引用controller里面的变量,没错,用{ { } },在angular2里面也一样。 我们在class里面定义了一个变量,在template里面就可以用{ { } }直接引用。例如: <pre class="brush:java; toolbar: true; auto-links: false;">@Component({ selector:'hero', template: < div >{{title}}< /div > }) export class HeroComponent { public title = 'Tour of Heroes'; }</pre><br /> <p>这样可以在template直接引用class里面的title,等于template里面是&lt; div&gt;Tour of Heroes&lt; /div&gt; </p> <p>然后你就可以随意改这个变量,然后render出来的html也会跟着改变。<br /> 另外我在template里面想提到的是@Input @Output factory method以及[(ngModel)]的双向绑定。 </p> <h5>@Input </h5> 这个@Input是用来定义模块的输入的,用来让父模块往子模块传递内容: <pre class="brush:java; toolbar: true; auto-links: false;">@Component({ selector: 'bank-account', template: Bank Name: {{bankName}} Account Id: {{id}} }) class BankAccount { @Input() bankName: string; @Input('account-id') id: string; // this property is not bound, and won't be automatically updated by Angular normalizedBankName: string; } @Component({ selector: 'app', template: < bank-account bank-name="RBC" account-id="4747">< bank-account> , directives: [BankAccount] }) class App {}</pre><br /> <p>再这个例子中,父模块是app,子模块是BankAccount, 在app中我们想往子模块里面传递back-name和account-id这两个信息,可以通过property的方式最简单直接,而子模块要接受这个 property就要用@Input来接收。子模块会在BankAccount里面直接接收传递过来的property。传递的方式可以是驼峰法,也可以直接写在input里面,就如<br /> 例子里面写的那样。要记住一点的是父模块在引用子模块的时候是用的[]。 </p> <h5>@Output </h5> 如果我们的子模块想自定义一些event传递给父模块又该怎么做呢?这时候就要用到@Output了。 <pre class="brush:java; toolbar: true; auto-links: false;">@Directive({ selector: 'interval-dir', }) class IntervalDir { @Output() everySecond = new EventEmitter(); @Output('everyFiveSeconds') five5Secs = new EventEmitter(); constructor() { setInterval(() =&gt; this.everySecond.emit("event"), 1000); setInterval(() =&gt; this.five5Secs.emit("event"), 5000); } } @Component({ selector: 'app', template: < interval-dir (every-second)="everySecond()" (every-five-seconds)="everyFiveSeconds()"> < /interval-dir> `, directives: [IntervalDir] }) class App { everySecond() { console.log('second'); } everyFiveSeconds() { console.log('five seconds'); } }</pre>

因为在普通的html里面,比如button我们会有onclick这种event来监听这个button是否被按了,然后angular2也完全允许我们自定义这种event listening的模式,我们可以给任何模块定义这种event,当触发了event之后就会从子模块往父模块传递子模块的信息。

再这个例子里,父模块是app,子模块是IntervalDir, 然后在子模块定义event触发的条件,比如每隔1s要触发事件,每隔5s要触发事件。然后当父模块监听到这些事件时可以做相应的操作。当然我们如果想传递信息,可以在new EventEmitter()里面加入我们想要传递的东西,然后在使用的时候加入函数的参数里面。比如我们想传递一个
test,我们只需要这么改:
class IntervalDir {    @Output() everySecond = new EventEmitter(test);  }  < interval-dir (every-second)="everySecond(test)" (every-five-seconds)="everyFiveSeconds()">  everySecond(test) { console.log(test); }

而在这里,父模块在引用子模块的时候是用的()。

[(ngModel)]

最后当然是最经典的angular特有的ngModel双向binding了。而这个特性是< input >特有的。我们注意到了 [(ngModel)] 既有[]也有()。没错它既有Input的特性也有Output的特性。

1 
< input [(ngModel)]="hero.name" placeholder="name" > 

在这里,我们在view里面改input里面的内容,hero.name也会跟着改变,而如果我们在class里面改变hero.name在view里面的内容也会跟着改变。然后就是mvvm的世界了。

directives

主要是加入子模块的模块,当我们定义了一个模块之后,如果我们想要它被别的模块使用,必须定义一个出口被别的模块使用,比如app.component这个模块最后就要:

1 
export class AppComponent {} 

让angular2知道它是一个AppComponent模块,然后在boot里面可以加入这个模块。同理我也export了HeroComponent,HeroFormComponent以及HeroesListComponent模块。然后在app.component的一开始就import进来,然后就要在directive加入这些模块然后在template才能识别相应的selector。如果
不在directive中引入相应的模块,那在compile的时候template就不知道相应的selector是啥就不能正常的render了。

providers

这个主要用来加入service的模块。当我们想在模块之中使用service的时候,和directive相同的道理,我们需要加入provider这样这个模块就知道使用的service的provider。小G在用angular的过程中碰到最多的问题就是missing provider的问题,在angular1中可能是由于service的dependency之间有loop,
表示service互相依赖,会有问题。但angular2由于模块化了这个dependency injection的问题得到了很好的解决。如果再碰到这类问题首先要检查模块是否加入了service provider。

styles

这个主要定义这个模块使用的css,不用放到外部的style.css里面。这样这个模块的css完全由自己掌控,是不是方便了很多?

这整个app的root component介绍完了,这个root component主要功能是加载了其他模块。这个博客还会介绍一个hero component。

hero component

这块主要介绍的就是template里面的input和output还有ngFor:
< li *ngFor="#hero of heroes"       [class.selected]="hero === selectedHero"      (click)="onSelect(hero)">      < span class="badge">{{hero.id}}< /span> {{hero.name}}  < /li >

比如我们有一个list的heros,我们可以在html里面用ngFor来循环这个list然后把list里面的英雄输出到html里面。在这里的语法和angular1完全不一样了。在angular1里面,完全没有以及#的符号,但是在angular2里面,和#确实很关键的语法。
( )这个在ngFor的前缀表示整个< li >元素以及它的子元素合成一个master模板。如果不想使用( ),ngFor需要被包含在一个template的元素里面:
< template ngFor #hero [ngForOf]="heroes" >    < hero-detail [hero]="hero" >< /hero-detail >  < /template >

Angular2还有这个语法的就是ngIf。如果不用()也需要把ngIf包含在template里面。
而在hero前面的( # )表示了hero是一个本地模板变量,整个合起来就是循环heros的list,然后对于每个hero,在< li >< /li >内可以进行操作。
从上面的例子还可以看到input和output。只不过这两个都已经在li模块里面集成好了,不需要自己定义。所以对于li这个元素的input,我们可以定义这个li的class的是否被选择。对于li元素的output我们可以定义点击事件监听。然后在这个模块中进行相应的函数操作。

小结

洋洋洒洒这么多,其实angular2的主要知识点有下面几个:

  1. Angular2的文件结构,typescript的设置,package.json的设置。
  2. boot的作用,更换boot可以达到复用代码让代码工作在其他framework的作用。
  3. Angular2是模块化的,自上而下的一个树状结构。每个模块之间可以互相调用,也可以用service互相通信。
  4. Angular2里面的一些概念,比如Component,Directive,Provider,selector,template, @Input, @Output, [ ( ngModel) ], ngFor, ngIf.

如果你知道上面这些是咋回事,那差不多可以开始自己写个angular2的app啦。一方面可以在小G的简单app上随便操作看效果,另一方面小G也给个angular2的快速开发app的project:angular2-webpack-starter。小G就是用这个来做比较大型的app开发的。这个项目是跟着angular2不断在更新的,比较伤感的是用vscode很难debug。

本文标题:Angular2 初探

文章作者:G

发布时间:2015年12月21日 - 23时30分

最后更新:2015年12月23日 - 15时39分

原始链接:http://gabriel0402.github.io/2015/12/21/overview/

许可协议: "署名-非商用-相同方式共享 3.0" 转载请保留原文链接及作者。