---恢复内容开始---css
原文连接:https://zhuanlan.zhihu.com/p/27696268html
这篇是在知乎上看到的, 原文连接附上。以为很不错, 就本身抄过来了。node
AngularJS最大版本号只有1.x, 2.x/4.x的版本号都是针对于全新的框架Angular。可是也不能说Angular 和 AngularJS 一点关系都没有。webpack
提一下AngularJS 的特性:双向数据绑定,MVC,指令,服务,过滤器,模块化,脏检查机制,依赖注入,scope,路由,表单校验等等。git
看下AngularJS 到 Angular的过程当中,哪些概念被留下来, 哪些被提剔除了(所谓的取其精华,去其糟粕)。angularjs
提出的部分:github
- ng-controller指令:控制器主要是业务逻辑的控制部分
- $scope概念:很轻大有很复杂(我一直没弄懂)
- 数据的双向绑定:数据双向流通可能致使数据的震荡(因此会有最多检查10次的限制,10次以后还不稳定就会报错)
保留/改善的部分:web
- 路由嵌套:AngularJS自带的路由系统是不能嵌套路由的, 到了Angular中能够随意嵌套
- 过滤器(Filter)变成管道(Pipe),概念的变化
- 依赖注入机制:直接在构造器中注入,还有分层依赖注入的概念
- 指令写法:
- ng-click-> (click)
- [属性]href="{{}}" 能够写成[href]
- [(ngModel)] 代替之前的ng-model
- *ngFor 代替ng-repeat,不适用于对象,适用于任何有Symbol.iterator属性的数据结构(能用for...of来访问) ,好比数据,集合等
- *ngIf 代替ng-if,去掉ng-show,ng-hide
- 对移动端的支持
- 模板,数据绑定,服务,模块,脏检查机制等
新增的部分:express
- 组件化:Angular的核心所在
- Typescript做为默认的开发语言
- ZoneJS 监听全部(有可能致使数据变化)的异步事件
- 支持服务端渲染
Angular Clinpm
Angular团队为开发者提供了一个开箱即用(out of the box)的脚手架工具:Angular Cli.咱们不再用担忧在项目初始化时,要搭建配置一系列的工具,好比webpack,karma,tslint,protractor等。
操做很简单,只要运行以下命令行就搞定了。
具体的语法教程可参考这里。
安装以后,文件目录以下:
my-dream-app e2e // 端到端测试 app.e2e-spec.ts app.po.ts tsconfig.e2e.json node_modules/... // npm包 src/... // 源码 angular-cli.json // 配置项 .editorconfig // 编辑器配置 .gitignore // git忽略文件配置 karma.conf.js // karma配置 package.json // npm配置 protractor.conf.js // 测试配置项 README.md // 项目说明 tsconfig.json // ts编辑器的配置 tslint.json // tslint配置项
咱们须要关注的是src文件夹,这里存放咱们全部的源代码,开发的时候基本都在src中。
src app // 代码的主要文件夹 app.component.css // 根组件样式 app.component.html // 根组件模版 app.component.spec.ts// 根组件测试 app.component.ts // 根组件脚本 app.module.ts // 根模块 assets // 静态资源 .gitkeep // 保存空文件夹 environments // 环境配置 environment.prod.ts environment.ts favicon.ico // 图标 index.html // 页面主入口 main.ts // 脚本主入口 polyfills.ts // 兼容浏览器 styles.css // 全局css样式 test.ts // 单元测试主入口
模块
Angular很重要的概念之一仍然是模块。Angular整个框架就是由不少个模块组成的,而不一样的模块须要从不一样的地方导入。打开package.json文件,能够看到依赖的angular包多是这样的:
"@angular/common": "^2.3.1", "@angular/compiler": "^2.3.1", "@angular/core": "^2.3.1", "@angular/forms": "^2.3.1", "@angular/http": "^2.3.1", "@angular/platform-browser": "^2.3.1", "@angular/platform-browser-dynamic": "^2.3.1", "@angular/router": "^3.3.1",
来简单看下这些angular包中包含了哪些经常使用的模块(至少目前为止,我以为经常使用的)。
- @angular/core:这里包含了不少经常使用的模块
- NgModule:模块定义装饰器
- Component:组件定义装饰器
- Directive:指令定义装饰器
- Pipe :管道定义装饰器
- PipeTransform:管道接口
- Injectable:服务定义装饰器
- ElmentRef:元素引用
- ViewChild:获取子元素
- Render:渲染
- Input:接受参数输入
- Output:事件输出
- EventEmitter:触发自定义事件
- @angular/common
- CommonModule:通用模块,包含内置指令ngIf,ngFor
- @angular/forms
- FormsModule:定义模版驱动表单
- ReactiveFormsModule:定义响应式表单
- FormGroup, FormArray, FormControl, FormBuilder:响应式表单元素
- Validators:表单校验
- @angular/http
- HttpModule:http请求模块
- @angular/router
- RouterModule 路由模块
- Routes 路由数据结构
- @angular/platform-browser
- platformBrowser:AoT编译
- BrowserModule:浏览器支持,注意该模块导入了CommonModule,而后导出去,因此引用了这个模块也就引用了CommonModule
- @angular/platform-browser-dynamic
- platformBrowserDynamic:JIT编译
以上模块都是Angular框架中的自带模块,而咱们开发的完整单元也是模块。一个应用中至少要有一个模块,也就是根模块。 一些共享的功能属性咱们能够抽象出来,成为共享模块。而后就是一些特性模块了。
模块的组成由组件,服务,指令,管道等等组成,这些概念会在下面讲到。定义模块的语法以下:
@NgModuel({ declarations: [], // 用到的组件,指令,管道 providers: [], // 依赖注入服务 imports: [], // 导入须要的模块 exports: [], // 导出的模块,跨模块交流 entryComponents: [] // 需提早编译好的模块 bootstrap: [] // 设置根组件 }) export class AppModule { }
全部用到的组件,指令,管道,模块都须要事先在模块中声明好,才能在具体组件中使用。服务能够在模块,组件,指令中的providers声明,也能够直接在运行时提供(参见Trotyl Yu的 例子)。
通常状况下,在根模块的bootstrap中设置启动的根组件便可,但也能够动态处理(参见Trotyl Yu的例子)。
那如何启动根模块呢?
在入口脚本中,也就是Angular Cli项目中的main.ts中,启动以下:
// 导入须要模块 import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; // 根模块 import { AppModule } from './app/app.module'; // 编译启动模块 platformBrowserDynamic().bootstrapModule(AppModule);
至此,咱们对模块有所了解,也知道了模块的定义。
组件
自从采用组件化的React大火以后,目前市面上煊赫一时的框架全都采用了组件化的理念,Angular固然也不能落后了。能够说,组件化是Angular的核心理念。按Angular在中国的布道者的话来讲,就是:
Angular的核心概念是组件,模块化机制NgModule是为组件化服务的,实际上全部其它机制都是围绕组件化而来的。只有从组件化这个角度才能把握Angular的精神内核。
组件一般都是由模版和业务逻辑组成,看一下如何用Angular写一个很简单的组件:
// hello.component.ts import { Component } from '@angular/core'; @Component({ selector: 'hello', template: '<p> {{greeting}} </p>', styles: [`p { color: red;}`] }) export class HelloComponent{ private greeting: string; constructor(){ this.greeting = 'Hello, Angular2!'; } } // 使用 <hello></hello> // 渲染结果 <hello> <p> Hello, Angular2! </p> </hello>
定义类HelloComponent的时候,加上装饰器@Component(Typescript语法),告诉Angular这个类是组件类。里面的数据称之为元数据(metadata),selector属性说明了该组件对外的使用标记,template就是组件的模版,styles是组件的样式。而HelloComponent中定义的就是该组件的业务逻辑了。
若是模版内容太多,能够单独写在一个html文件中,用templateUrl属性引入;同理,样式文件用styleUrls引入。
组件生命周期
正如其余框架的组件,Angular的组件也是有生命周期这个概念。在不一样的阶段不一样的场景下,能够调用不一样的生命周期函数钩子(hook)。
- constructor:构造器函数,通常用于注入服务
- ngOnChanges:检测到输入数据变化,首次触发发生在ngOnInit前。注意对象的属性发生变化时监听不到
- ngOnInit:组件初始化,一般会设置一些初始值
- ngDoCheck:手动触发更新检查
- ngAfterContentInit:内容初始化到组件以后
- ngAfterContentChecked:内容变动检测以后
- ngAfterViewInit:视图 初始化以后
- ngAfterViewChecked:视图发生变化检测以后,这个能够用来保证用户视图的及时更新
- ngOnDestroy:组件注销时的清理工做,一般用于移除事件监听,退订可观察对象等
具体说明能够参考这里。
组件通讯
能够想像获得,组件化的页面结构最终会造成一颗组件树。盗一张Vue的图:
不可避免,咱们须要考虑父子组件之间的参数传递问题。Anuglar提供的通讯方式有以下几种:
- 父组件到子组件:父组件用属性绑定将值传入,子组件经过@Input来接收。
// 父组件 import { Component } from '@angular/core'; @Component({ selector: 'hero-parent', template: `<h2> heroes </h2> <hero-child *ngFor="let hero of heroes" [hero]="hero" > </hero-child> ` }) export class HeroParentComponent { heroes = [{ name: 'John' }, { name: 'Lily' }]; } // 子组件 import { Component, Input } from '@angular/core'; import { Hero } from './hero'; @Component({ selector: 'hero-child', template: ` <h3>{{hero.name}}</h3> ` }) export class HeroChildComponent { @Input() hero: Hero; }
- 子组件到父组件:子组件自定义事件用@Output传出,父组件用事件绑定获取。
// 子组件 import { Component, EventEmitter, Output } from '@angular/core'; @Component({ selector: 'my-voter', template: ` <h4>{{name}}</h4> <button (click)="vote(true)">Agree</button> ` }) export class VoterComponent { @Output() onVoted = new EventEmitter<boolean>(); vote(agreed: boolean) { this.onVoted.emit(agreed); } } // 父组件 import { Component } from '@angular/core'; @Component({ selector: 'vote-taker', template: ` <h2>Should mankind colonize the Universe?</h2> <h3>Agree: {{agreed}}, Disagree: {{disagreed}}</h3> <my-voter *ngFor="let voter of voters" [name]="voter" (onVoted)="onVoted($event)"> </my-voter> ` }) export class VoteTakerComponent { agreed = 0; disagreed = 0; voters = ['Mr. IQ', 'Ms. Universe', 'Bombasto']; onVoted(agreed: boolean) { agreed ? this.agreed++ : this.disagreed++; } }
- 子组件引用:在父组件模版中添加对子组件的引用,便可经过该子组件去访问子组件的方法。
<h3>Countdown to Liftoff (via local variable)</h3> <button (click)="timer.start()">Start</button> <button (click)="timer.stop()">Stop</button> <div class="seconds">{{timer.seconds}}</div> <countdown-timer #timer></countdown-timer>
- @ViewChild():相似的,也能够在脚本中用@ViewChild()来获取子组件
import { AfterViewInit, ViewChild } from '@angular/core'; import { Component } from '@angular/core'; import { CountdownTimerComponent } from './countdown-timer.component'; @Component({ selector: 'countdown-parent-vc', template: ` <h3>Countdown to Liftoff (via ViewChild)</h3> <button (click)="start()">Start</button> <button (click)="stop()">Stop</button> <div class="seconds">{{ seconds() }}</div> <countdown-timer></countdown-timer> ` }) export class CountdownViewChildParentComponent implements AfterViewInit { @ViewChild(CountdownTimerComponent) private timerComponent: CountdownTimerComponent; seconds() { return 0; } ngAfterViewInit() { setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0); } start() { this.timerComponent.start(); } stop() { this.timerComponent.stop(); } }
- 将数据保存在服务中
- @ngrx/store:参见【译】手把手教你用ngrx管理Angular状态
模板与数据绑定
模版说白了就是html的内容,常规的html基本都是静态内容,而模版结合了框架中的新语法使得html动态化。来看看Angular中的模版有什么便利的语法:
- 插值绑定:双花括号{{}}
咱们能够看到上一节组件例子中的{{greeting}}就是插值绑定。不只能够获取变量的值,还能够直接写表达式。
- 属性(Property)绑定
<input [value]='myData'>
还有其余的,好比样式绑定:
<div [ngClass]="{special: isSpecial}"></div>
注意点:property和attribute不同,想要绑定attribute,你须要写成property。好比:
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
你将会获得以下错误信息:
Template parse errors:
Can't bind to 'colspan' since it isn't a known native property
你须要改写成这样:
<tr><td [attr.colspan]="1 + 1">One-Two</td></tr> // 或者 <tr><td attr.colspan="{{1 + 1}}">One-Two</td></tr>
- 事件绑定
<input (keyup)='handle($event)' >
能够是原生的事件:click,change,keydown,mousemove等,也能够是自定义事件,也能够是指令事件,好比ngSubmit。
- 双向绑定
<input [(ngModel)] = 'data'> // 双向绑定的背后实际上是单向绑定和事件触发,等价于下面 <input [ngModel]="data" (ngModelChange)="data=$event">
注意点:使用ngModel,须要引入FormsModule模块。
还有些内置的指令:
- 模版引用变量(# / ref-)
能够在元素上用#或者ref-前缀来标记这个元素,而后在其余地方引用。
<input #fax placeholder="fax number"> ( <input ref-fax placeholder="fax number"> ) <button (click)="callFax(fax.value)">Fax</button>
- *ngIf:控制内容的有无
<div *ngIf="show"> Can you see this? </div>
若是还有else部分,能够以下操做:
<div *ngIf="show; else elseBlock"> Can you see this? </div> <ng-template #elseBlock> else block </ng-template>
- *ngFor:循环
<div *ngFor="let hero of heroes; let i=index> {{i}}: {{hero.name}}</div>
具体的模版语法能够参考这里。
路由
一个模块有了多个组件以后,须要用路由来配置哪一个url呈现哪一个组件。
首先,咱们须要在入口页面的index.html中配置根路径:
... <head> <base href="/"> ... </head> ...
而后建立一个路由模块:
import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; ... // 路由配置 const appRoutes: Routes = [ { path: 'home', component: HomeComponent }, { path: 'heroes', component: HeroesComponent }, { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: '**', component: PageNotFoundComponent } ]; @NgModule({ imports: [ RouterModule.forRoot(appRoutes) ], exports: [ RouterModule ] }) export class AppRoutingModule {}
在主模块中导入配置好的路由模块:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; ... @NgModule({ imports: [ BrowserModule, FormsModule, AppRoutingModule ], declarations: [ AppComponent, HomeComponent, HeroesComponent, PageNotFoundComponent ], bootstrap: [ AppComponent ] }) export class AppModule { }
而在页面中须要一个容器<router-outlet></router-outlet>去承载:
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <h1>Angular Router</h1> <nav> <a routerLink="/home" routerLinkActive="active">Home</a> <a routerLink="/heroes" routerLinkActive="active">Heroes</a> </nav> <router-outlet></router-outlet> ` }) export class AppComponent { }
上面代码中的routerLink定义了用户点击后的路由跳转,routerLinkActive定义该路由激活时的样式类。
路由上还能够带上一些索引参数:
{ path: 'heroes/:id', component: HeroesComponent },
获取的方式:
import { ActivatedRoute, Params } from '@angular/router'; ... export class a { constructor( private route: ActivatedRoute ) {} // 路由参数 this.route.params }
当模块不少,路由也不少的时候,咱们可使用模块懒加载的方式。懒加载的方式也很简单,在配置路由的时候修改以下便可:
const routes: Routes = [ { // 默认转到订单管理 path: '', redirectTo: '/order', pathMatch: 'full' }, { path: 'order', loadChildren: './order/order.module#OrderModule' }, { path: 'warehouse', loadChildren: './warehouse/warehouse.module#WarehouseModule' }, { path: 'statistics/sales', component: SalesComponent } ]; // 在子模块中用RouterModule.forChild import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { OrderComponent } from './order.component'; const orderRoutes = [ { path:'', component: OrderComponent } ]; @NgModule({ imports: [RouterModule.forChild(orderRoutes)], exports: [RouterModule] }) export class OrderRoutingModule { }
服务依赖注入
服务是什么概念?能够简单地认为它是一个功能模块,重要在于它是单例对象,而且能够注入到其余的地方使用。
依赖注入是来自后端的概念,其实就是自动建立一个实例,省去每次须要手动建立的麻烦。
在Angular中定义一个服务很简单,主要在类以前加上@Injectable装饰器的功能。这是最多见的依赖注入方式useClass,其余具体参见这里。
import { Injectable } from '@angular/core'; @Injectable() export class Service { counter: number = 0; getData(){ return this.counter++; } }
而后在模块的providers中声明:
import { Service } from './service'; ... @NgModule({ imports: [ ... ], declarations: [ ... ], providers: [ Service ], // 注入服务 bootstrap: [...] }) export class AppModule { }
使用的时候须要在构造器中创建关联:
import { Component } from '@angular/core'; import { Service } from './service'; ... @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { constructor(public service: Service) { // this.service被成功注入 // 至关于 this.service = new Service(); // 而后能够调用服务 this.service.getData(); } }
因为该服务是在模块中注入,因此该模块中的全部组件使用这个服务时,使用的都是同一个实例。
除了在模块中声明,还能够在组件中声明。假设AppComponent下还有组件HomeComponent,此时咱们在AppComponent中注入这个服务:
import { Component } from '@angular/core'; import { Service } from './service'; ... @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [ Service ], // 注入服务 }) export class AppComponent { constructor(public service: Service) { // this.service被成功注入 // 至关于 this.service = new Service(); // 而后能够调用服务 this.service.getData(); } }
因为该服务是在模块中注入,因此该模块中的全部组件使用这个服务时,使用的都是同一个实例。
除了在模块中声明,还能够在组件中声明。假设AppComponent下还有组件HomeComponent,此时咱们在AppComponent中注入这个服务:
import { Component } from '@angular/core'; import { Service } from './service'; ... @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [ Service ], // 注入服务 }) export class AppComponent { constructor(public service: Service) { // this.service被成功注入 // 至关于 this.service = new Service(); // 而后能够调用服务 this.service.getData(); } }
若是HomeComponent也使用了这个服务,那它使用的将是同一个实例。这个能够从Service中的数据变化来看出。
Angular还有个分层依赖注入的概念,也就是说,你能够为任一组件建立本身独立的服务。就像上面的例子,若是想要HomeComponent不和它的父组件同使用一个服务实例的话,只要在该组件中从新注入便可:
... @Component({ selector: 'home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'], providers: [ Service ], // 从新注入服务 }) export class HomeComponent { ... }
对于先后端的接口,一般会写成服务。下面说下请求后端数据这块应该怎么写。在模块这节中提过,http有专门的HttpModule模块处理请求。首先要在模块中导入HttpModule,而后引入http服务,调用相应的请求方法便可。
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import 'rxjs/add/operator/toPromise'; @Injectable() export class HttpService { constructor(private http: Http) {} getFromServer():any { return this.http.get(`/data`) .toPromise() .then(res => res.json()) .catch(); } }
因为请求返回的对象是个可观察对象,能够转成Promise对象处理。这里须要用到RxJS的toPromise操做符,而后用then去处理返回成功结果,catch处理失败状况。这样就搞定了后端数据的请求了。
RxJS又是另一个比较高深的话题了,有机会深刻学习一下再聊。
指令
Angular的指令概念跟AngularJS的指令差很少,最重要的区别在于Angular中的组件继承指令,算是特殊的指令。咱们看下用指令的方式去写组件的简单例子:
import { Directive,Input,ElementRef } from '@angular/core'; @Directive({ selector: 'hello' }) export class HelloDirective { @Input() name: string; constructor(private el: ElementRef) {} public ngOnInit(): void { this.el.nativeElement.innerText = `hello ${this.name}!`; } } // 使用组件指令 <hello name="Yecao"></hello> // 渲染结果 <hello> hello, Yecao! </hello>
不要忘记在使用前先在模块中声明哦,我以为这是Angular最烦人的一点。
除此以外,还有属性指令和结构指令,属性指令只改变元素的样式或者行为。要写成属性指令,须要在selector属性中用[]包裹起来。来看简单地例子:
import { Directive, ElementRef, Renderer2 } from '@angular/core'; @Directive({ selector: '[highLight]' }) export class HighLightDirective { constructor(private el: ElementRef, private renderer2: Renderer2) { } ngAfterViewInit() { this.renderer2.addClass(this.el.nativeElement, 'highlight'); } } // 使用属性指令 <p highLight> 这一段会高亮显示 </p>
结构指令就是模板中提到的*ngIf,ngFor等指令,它修改了DOM结构。举个例子,重写ngIf:
import { Directive, Input, ViewContainerRef, TemplateRef } from '@angular/core'; @Directive({ selector: '[myIf]' }) export class MyIfDirective { constructor(private templateRef: TemplateRef<any>, private viewContainer: ViewContainerRef) { } @Input() set appMyIf(condition: boolean) { if (condition) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } } } // 使用结构指令 <p *myIf="false"> 这一段不会显示 </p>
管道(过滤器)
管道其实就是过滤器,就是叫法不一致而已。主要用于格式化源数据,而不改变源数据。定义和使用的方式也很简单:
import { Pipe, PipeTransform } from '@angular/core'; /* * 订单取消状态:默认为ALL表示所有,CANCEL表示已取消,NOTCANCEL表示正常 */ @Pipe({ name: 'cancelStatus' }) export class CancelStatusPipe implements PipeTransform { transform(status:string, blank: boolean):string { const map = { "ALL": "所有", "NOTCANCEL": "正常", "CANCEL": "已取消", "": "暂无", } return blank? '特殊状况': map[status]; } }
使用前记得在模块的declarations声明,或者导到共享模块,在共享模块中导出去。使用以下:
{{ "ALL" | cancelStatus }} // 所有 {{ "ALL" | cancelStatus: true }} // 特殊状况
Angular内置了一些管道:
// 日期 DatePipe {{ expression | date:"MM/dd/yy" }} // 数字 DecimalPipe,digitInfo的组成 {minIntegerDigits}.{minFractionDigits}-{maxfractionDigits} // minIntegerDigits:整数部分保留最小的位数,默认值为1. // minFractionDigits:小数部分保留最小的位数,默认值为0. // maxFractionDigits:小数部分保留最大的位数,默认值为3. {{ expression | number[:digitInfo] }} // 大写 {{ expression | uppercase }} // 小写 {{ expression | lowercase }}
参考资料
- Angular官网(英文)
- Angular Cli
- Angular官网(中文)
- 官网英雄展现板例子
- 英文视频教程
- Angular2一小时快速入门
- 大漠穷秋 Angular2 0视频教程
- angularjs 1 和 2区别,这才是Angular2的灵魂!
- Redux你的Angular 2应用--ngRx使用体验
- Angular 4 指令快速入门
本文首发于野草园,转载请注明出处。不当之处,欢迎批评指正!