赞
踩
本课程是一个系列基础教程,目标是带领读者上手实战,课程以新版本 Angular 的 3 个核心概念作为主线:组件、路由、模块,加上业务开发过程中必须用到的特性:工具、指令、表单、RxJS、i18n、测试,一共分为 9 部分,34 篇文章。
除了组件、路由、模块这 3 个核心小节具有很强的关联性之外,其它内容都是完全独立的,您可以在用到的时候再翻阅。
认真读完这个系列文章之后,您会深入理解新版本 Angular 的概念模型,具备使用 Angular 上手进行开发的基本能力。
你可能会问:Angular 的文章到处有,网上一大片,我为什么要来读你这个系列文章?
这是非常好的一个问题,说明你对阅读内容有质量要求。
如果是我,我也会问这个问题。
整体上说,这个系列的文章有以下特色:
我会按照初学者一般的学习过程,用我自己的语言一步一步进行讲解。如你所知,最近的5年我一直在玩前端方面的东西,从 jQuery、SVG、ExtJS、Adobe Flex、Angular,这样一路玩过来。尤其是2016年,这一整年的时间我都代表 Angular 项目组在中国进行技术推广。在这5年,我在超过40家企业、开源组织、大学里面进行了大量演讲,在网络上发布了大量的视频和文章。在到处交流的过程中,认识了很多人,有经验丰富的后端开发者,也有新入行的初学者,他们跟我讲过很多自己的困惑。所以,这个系列文章里面的内容我至少反复讲过20遍以上,我会把常见的一些疑问融入在内容里面。
我会帮你扫平日常开发中常见的坑,这些坑大部分都是开发者们反馈给我的,或者说到我这里吐槽过的。举几个典型的例子:
诸如此类的坑还有不少,我都是一个坑一个坑踩过来的。当然,我相信你自己也能踩过来,但是从节约时间的角度看,还是跟着我的思路走一遍更快不是吗?
这个系列的文章全部聚焦使用层面的话题,覆盖日常开发中使用频最高的特性。除非迫不得已,尽量不扯原理。长期以来,我发现有很多朋友的学习方式存在误区。比如:有一些人上来就去研究“变更检测”的原理,还有 RxJS 的原理,这种方式除了打击你自己的自信心之外并不能得到任何好处。因为你迟早会发现,在计算机领域,任何东西研究到最底层都和“算法”、“数据结构”、“设计模式”有关。而就我所知,很多朋友并不具备研究这些内容的基础知识,不过是白白浪费自己的时间而已。所以,我推荐采用更加务实一点的方案,首先学会如何使用,等用熟了,有时间、有闲情的时候再去研究那些底层的原理。设计发动机很难,但是学会开车并不难,对吧?所以我写这个系列的目标很简单,就是带你学会开车,而不是教你设计发动机。
这个系列的文章非常看重“概念模型”( Mental Model )的构建。我发现,很多开发者已经做过非常多的项目,但是当你跟他聊的时候,你很快就会发现他并没有掌握这门框架的精髓。打几个比方,当别人提到 Spring 的时候,你的大脑里面第一个想到一定是 DI、IOC、AOP 这些东西;当别人提到 Hibernate 或者 Mybatis 的时候,你的大脑里面立即会浮现出 ORM 的概念;当别人提到 React 的时候,你想到的应该是 VDom、JSX;当别人提到 jQuery 的时候,你首先想到的应该是$对吧?所以,你可以看到,任何一个成功的框架都有自己独创的“概念模型”,或者叫“核心价值”也可以。这是框架本身存在的价值,也是你掌握这门框架应该紧扣的主线,而不是上来就陷入到茫茫多的技术细节里面去。
文章里面所涉及到例子总数量大约200个左右,有少量例子来自官方文档,其它都是我自己一点一点手动敲出来的。我把这些例子分成了9个开源项目,它们互相独立,方便大家进行参考和练习。这些教学用的开源项目本身是免费的,列在这篇文章的尾部。
既然如此,问题就来了,新版本的 Angular 的核心概念是什么呢?
非常简单,一切都是围绕着“组件”( Component )的概念展开的:
所以,在这个系列的文章里面,Component、NgModule、Router 加起来会占据绝大部分篇幅,而一些琐碎的小特性会被忽略掉。我相信,你只要紧扣“组件化”这个主线,就能站在一个很高的角度统摄全局,从而掌握到这门框架的精髓。
这个系列的文章适合以下人群阅读:
特别注意:这个系列的文章不是前端入门读物,你至少需要会一门编程语言,无论前端还是后端都可以,如果你曾经使用过一门前端框架,那就更好了。
关于 Angular 的浏览器兼容性,请看下图:
有一些国内的开发者会来争论兼容 IE8 的问题,我想给你两个事实:
不值得为了这么少的市场份额付出那么多的研发和维护成本。
你完全可以以上两点事实去说服你的客户。
老版本使用 AngularJS 指代,所有新版本都叫做 Angular。原因很好理解,因为老版本是用 JS 开发的,所以带一个 JS 后缀,而新版本是基于 TypeScript 的,带 JS 后缀不合适。
这个系列的文章不会单独讲 TypeScript,正如我一直强调的:TypeScript 不难,JavaScript 才难。你跟着我的思路,TypeScript 绝对不会成为你学习 Angular 的障碍。相反,一旦你写熟练了之后,TypeScript 可以非常有效地提升编码效率和程序可读性。
根据官方的解释,Angular 从2.0之后会保证向下兼容,每隔半年会升级一个大版本,只有升级大版本的时候才会做一些 breaking change。
所以这个系列文章里面不再强调版本号,涉及到的所有实例代码都基于目前(2017-10)最新的4.x版本。
这个系列文章一共分11章,34个小节。
本系列课程对应的所有示例项目列表:
最后是那一句套话:水平有限,错漏难免,欢迎指正。可以在我的读者圈里跟我沟通交流。
2009年,NodeJS 发布了第一个版本,标志着前端开发正式告别了刀耕火种的原始状态,开始进入工业化时代。
在 NodeJS 出现之前,前端开发领域有很多事情我们是做不到的,例如:
而这一切在 NodeJS 出现之后都得到了很好的解决:
就前端开发目前整体的状态来说,无论你使用什么框架,NodeJS、webpack、SASS、Karma+Jasmine、WebDriverJS 这个组合是无论如何绕不过去的。
在开发 Angular 应用的时候,当然也离不开大量基于 NodeJS 的工具,我们需要 TypeScript compiler、webpack、Karma、Jasmine、Protracter 等模块。
有相关经验的开发者都知道,自己从头开始去搭建一套基于 webpack 的开发环境是一件非常麻烦的事情。很多初学者在搭建环境这一步上面消耗了过多的精力,导致学习热情受到了沉重的打击。
当团队规模比较大的时候,在每个人的机器上配置环境需要消耗大量的时间。有一些团队为了避开这个坑,利用 Docker 来做开发环境的同步和版本升级,看起来也是一个非常不错的方案。
Angular 项目组从一开始就注意到了这个问题,所以有了 @angular/cli 这个神器,它的底层基于 webpack,集成了以上提到的所有 NodeJS 组件。你只要装好 @angular/cli 就够了,而不需要自己从头一步一步安装那些 NodeJS 插件。
当然,在安装 @angular/cli 之前你需要先把 NodeJS 安装好,请到官方网站下载安装包: https://nodejs.org/ ,安装过程和普通软件没有区别。装好 NodeJS 之后就可以安装 @angular/cli 了,由于 npm 会自动访问海外的服务器,所以强烈推荐使用 cnpm 进行安装:
npm i -g cnpm --registry=https://registry.npm.taobao.orgcnpm i -g @angular/cli
cnpm 是淘宝发布的一款工具,会自动把 npm 上面的所有包定时同步到国内的服务器上来,cnpm 本身也是一款 NodeJS 模块。
@angular/cli 安装成功之后你的终端里面将会多出一个名叫 ng 的命令,敲下 ng,将会显示完整的帮助文档:
我们来创建第一个入门项目 HelloAngular,请在你的终端里面运行:
ng new HelloAngular
@angular/cli 将会自动帮你把目录结构创建好,并且会自动生成一些模板化的文件,就像这样:
请特别注意:@angular/cli 在自动生成好项目骨架之后,会立即自动使用 npm 来安装所依赖的 Node 模块,所以这里我们要 Ctrl+C 终止掉,然后自己进入项目的根目录,使用 cnpm 来进行安装。
安装完成之后,使用 ng serve 命令启动项目:
打开你的浏览器,访问默认的4200端口,看到以下界面说明环境 OK 了:
请注意:
ng 提供了很多非常好用的工具,除了可以利用 ng new 来自动创建项目骨架之外,它还可以帮助我们创建 Angular 里面所涉及到的很多模块,最常用的几个如下:
更多的命令和参数请在终端里面敲 ng 仔细查看,尽快熟悉这些工具可以非常显著地提升你的编码效率。
@angular/cli 这种“全家桶”式的设计带来了很大的方便,同时也有一些人不太喜欢,因为很多底层的东西被屏蔽掉了,开发者不能天马行空地自由发挥。比如:@angular/cli 把底层 webpack 的配置文件屏蔽掉了,很多喜欢自己手动配 webpack 的开发者就感到很不爽。
对于国内的开发者来说,上面这些其实不是最重要的,国内开发者碰到的坑主要是由两点引起的:
所以,如果你的开发平台是 Windows,请特别注意:
如你所知,一直以来,前端开发领域并没有一款特别好用的开发和调试工具。
所以,Visual Studio Code(简称 VS Code)才会呈现出爆炸性增长的趋势。它是微软开发的一款前端编辑器,完全开源免费。VS Code 底层是 Electron,界面本身是用 TypeScript 开发的。对于 Angular 开发者来说,当然要强烈推荐 VS Code。最值得一提的是,从1.14开始,可以直接在 VS Code 里面调试 TypeScript 代码。
请参照以上步骤打开 launch.json 配置文件。
请把你本地 launch.json 文件里面的内容改成这样:
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Chrome", "url": "http://localhost:4200", "webRoot": "${workspaceRoot}" } ]}
在你的 app.component.ts 的构造函数里面打个断点,我本地是这样打断点的:
打开终端,进入项目根目录,运行 ng serve 启动项目,然后从 VS Code 的 debug 界面启动 Chrome:
注意,你可能需要 F5 刷新一下 Chrome 才能进入断点!
目前,无论你使用什么前端框架,都必然要使用到各种 NodeJS 工具,Angular 也不例外。与其它框架不同,Angular 从一开始就走的“全家桶”式的设计思路,因此 @angular/cli 这款工具里面集成了日常开发需要使用的所有 Node 模块,使用 @angular/cli 可以大幅度降低搭建开发环境的难度。
几乎所有前端框架都在玩“组件化”,而且最近都不约而同地选择了“标签化”这种思路,Angular 也不例外。
对新版本的 Angular 来说,一切都是围绕着“组件化”展开的,组件是 Angular 的核心概念模型。
以下是一个最简单的 Angular 组件定义:
SASS 是一款非常好用的 CSS 预编译器,Bootstrap 官方从4.0开始已经切换到了 SASS。
目前(2017-10),@angular/cli 创建项目的时候没有自动使用 SASS 作为预编译器,我们需要自己手动修改一些配置文件,请按照以下步骤依次修改:
angular-cli.json 里面的 styles.css 后缀改成 .scss当你后面再使用 ng g c *** 自动创建组件的时候,默认就会生成 .scss 后缀的样式文件了。
angular-cli.json 里面的 styleExt 改成 .scss
src 下面 style.css 改成 style.scss
app.component.scss
app.component.ts 里面对应修改
改完之后,重新 ng serve,打开浏览器查看效果。
SASS 的 API 请参考官方网站
SASS 只是一个预编译器,它支持所有 CSS 原生语法。利用 SASS 可以提升你的 CSS 编码效率,增强 CSS 代码的可维护性,但是千万不要幻想从此就可以不用学习 CSS 基础知识了。
模板是编写 Angular 组件最重要的一环,你至少需要深入理解以下知识点才能玩转 Angular 模板:
几乎每一款前端框架都会提供自己的模板语法,在 jQuery 如日中天的时代,有 Handlebars 那种功能超强的模板。最近有 React 推崇的 JSX 模板写法,当然还有 Angular 提供的那种与“指令”紧密结合的模板语法。
综合来说,无论是哪一种前端模板,大家都比较推崇“轻逻辑”( logic-less )的设计思路。
简而言之,所谓“轻逻辑”就是说,你不能在模板里面编写非常复杂的 JavaScript 表达式。比如,Angular 的模板语法就有规定:
有一个非常重要的原因,比如你编写了以下 Angular 模板:
<ul> <li *ngFor="let race of races"> {{race.name}} </li></ul>
很明显,浏览器不认识 *ngFor 和 {{...}} 这种语法,所以必须在浏览器里面进行“编译”,获得对应的模板函数,然后再把数据传递给模板函数,最终结合起来获得一堆 HTML 标签,然后才能把这一堆标签插入到 DOM 树里面去。
如果启用了 AOT,处理的步骤有一些变化,@angular/cli 会对模板进行“静态编译”,避免在浏览器里面动态编译的过程。
而 Handlebars 这种模板引擎完全是运行时编译模板字符串的,你可以编写以下代码:
//定义模板字符串var source=`<ul> {{#each races}} <li>{{name}}</li> {{/each}}</ul>`;//在运行时把模板字符串编译成JS函数var templateFn=Handlebars.compile(source);//把数据传给模板函数,获得最终的HTMLvar html=templateFn([ {name:'人族'}, {name:'神族'}, {name:'虫族'}]);
注意到 Handlebars.compile 这个调用了吧?这个地方的本质是在运行时把模板字符串“编译”成了一个 JS 函数。
鉴于 JS 解释执行的特性,你可能会担忧这里会有性能问题。这种担忧是合理的,但是 Handlebars 是一款非常优秀的模板引擎,它在内部做了各种优化和缓存处理。模板字符串一般只会在第一次被调用的时候编译一次,Handlebars 会把编译好的函数缓存起来,后面再次调用的时候会从缓存里面获取,而不会多次进行“编译”。
上面我们多次提到了“编译”这个词,所以很显然这里有一个东西是无法避免的,那就是我们必须提供一个 JS 版的“编译器”,让这个“编译器”运行在浏览器里面,这样才能在运行时把用户编写的模板字符串“编译”成模板函数。
有一些模板引擎会真的去用 JS 编写一款“编译器”出来,比如 Angular 和 Handlebars,它们都真的编写了一款 JS( TS )版的编译器。而有一些简单的模板引擎只是用正则表达式做了字符串替换而已,显得特别简陋。这种简陋的模板引擎对模板的写法有非常多的限制,因为它不是真正的编译器,能支持的语法特性非常有限。
所以,评估一款模板引擎的强弱,最核心的东西就是评估它的“编译器”做得怎么样。但是不管怎么说,毕竟是 JS 版的“编译器”,我们不可能把它做得像 g++ 那么强大,也没有必要做得那么强大,因为这个 JS 版的编译器需要在浏览器里面运行,搞得太复杂浏览器拖不动!
以上就是为什么大多数模板引擎都要强调“轻逻辑”的最根本原因。
对于 Angular 来说,强调“轻逻辑”还有另一个原因:在组件的整个生命周期里面,模板函数会被执行很多次。你可以想象, Angular 每次要刷新组件的外观的时候,都需要去调用一下模板函数,如果你在模板里面编写了非常复杂的代码,一定会增加渲染时间,用户一定会感到界面有“卡顿”。
人眼的视觉延迟大约是100ms到400ms之间,如果整个页面的渲染时间超过400ms,界面基本上就卡得没法用了。有一些做游戏的开发者会追求60fps刷新率的细腻感觉,60分之1秒约等于16.7ms,如果 UI 整体的渲染时间超过了16.7ms,就没法达到这个要求了。
轻逻辑( logic-less )带来了效率的提升,也带来了一些不方便,比如很多模板引擎都实现了 if 语句,但是没有实现 else,所以开发者们在编写复杂业务逻辑的时候模板代码会显得非常啰嗦。
目前来说,并没有完美的方案能同时兼顾运行效率和语法表现能力,这里只能取一个平衡。
Mustache 语法也就是你们说的双花括号语法{{...}},老外觉得它像八字胡子,很奇怪啊,难道老外喜欢侧着头看东西?
好消息是,很多模板引擎都接受了 Mustache 语法,这样一来学习量又降低了不少,开心吧?
关于 Mustache 语法,你需要掌握3点:
请依次看例子:
插值语法关键代码实例:
<h3> 欢迎来到{{title}}!</h3>
public title = '假的星际争霸2';
简单的数学表达式求值:
<h3>1+1={{1+1}}</h3>
调用组件里面定义的方法:
<h3>可以调用方法{{getVal()}}</h3>
public getVal():any{ return 65535;}
<input #heroInput><p>{{heroInput.value}}</p>
有一些朋友会追问,如果我在模板里面定义的局部变量和组件内部的属性重名会怎么样呢?
如果真的出现了重名,Angular 会按照以下优先级来进行处理:
模板局部变量 > 指令中的同名变量 > 组件中的同名属性。
这种优先级规则和 JSP 里面的变量取值规则非常类似,对比一下很好理解对不对?你可以自己写代码测试一下。
属性绑定是用方括号来做的,写法:
<img [src]="imgSrc" />
public imgSrc:string="./assets/imgs/1.jpg";
很明显,这种绑定是单向的。
事件绑定是用圆括号来做的,写法:
<button class="btn btn-success" (click)="btnClick($event)">测试事件</button>
对应 Component 内部的方法定义:
public btnClick(event):void{ alert("测试事件绑定!");}
双向绑定是通过方括号里面套一个圆括号来做的,模板写法:
<font-resizer [(size)]="fontSizePx"></font-resizer>
对应组件内部的属性定义:
public fontSizePx:number=14;
AngularJS 是第一个把“双向数据绑定”这个特性带到前端来的框架,这也是 AngularJS 当年最受开发者追捧的特性,之一。
根据 AngularJS 团队当年讲的故事,“双向数据绑定”这个特性可以大幅度压缩前端代码的规模。大家可以回想一下 jQuery 时代的做法,如果要实现类似的效果,是不是要自己去编写大量的代码?尤其是那种大规模的表单,一大堆的赋值和取值操作,都是非常丑陋的“面条”代码,而有了“双向数据绑定”特性之后,一个绑定表达式就搞定。
目前,主流的几款前端框架都已经接受了“双向数据绑定”这个特性。
当然,也有一些人不喜欢“双向数据绑定”,还有人专门写了文章来进行批判,也算是前端一景。
Angular 有3个内置的结构型指令:*ngIf、*ngFor、ngSwitch。ngSwitch 的语法比较啰嗦,使用频率小一些。
*ngIf 代码实例:
<p *ngIf="isShow" style="background-color:#ff3300">显示还是不显示?</p><button class="btn btn-success" (click)="toggleShow()">控制显示隐藏</button>
public isShow:boolean=true;public toggleShow():void{ this.isShow=!this.isShow;}
*ngFor 代码实例:
<li *ngFor="let race of races;let i=index;"> {{i+1}}-{{race.name}}</li>
public races:Array<any>=[ {name:"人族"}, {name:"虫族"}, {name:"神族"}];
*ngSwitch 代码实例:
<div [ngSwitch]="mapStatus"> <p *ngSwitchCase="0">下载中...</p> <p *ngSwitchCase="1">正在读取...</p> <p *ngSwitchDefault>系统繁忙...</p></div>
public mapStatus:number=1;
特别注意:一个 HTML 标签上只能同时使用一个结构型的指令。
因为“结构型”指令会修改 DOM 结构,如果在一个标签上使用多个结构型指令,大家都一起去修改 DOM 结构,到时候到底谁说了算?
那么需要在同一个 HTML 上使用多个结构型指令应该怎么办呢?有两个办法:
使用频率比较高的3个内置指令是:NgClass、NgStyle、NgModel。
NgClass 使用案例代码:
<div [ngClass]="currentClasses">同时批量设置多个样式</div><button class="btn btn-success" (click)="setCurrentClasses()">设置</button>
public currentClasses: {};public canSave: boolean = true;public isUnchanged: boolean = true;public isSpecial: boolean = true;setCurrentClasses() { this.currentClasses = { 'saveable': this.canSave, 'modified': this.isUnchanged, 'special': this.isSpecial };}
.saveable{ font-size: 18px;} .modified { font-weight: bold;}.special{ background-color: #ff3300;}
NgStyle 使用案例代码:
<div [ngStyle]="currentStyles"> 用NgStyle批量修改内联样式!</div><button class="btn btn-success" (click)="setCurrentStyles()">设置</button>
public currentStyles: {}public canSave:boolean=false;public isUnchanged:boolean=false;public isSpecial:boolean=false;setCurrentStyles() { this.currentStyles = { 'font-style': this.canSave ? 'italic' : 'normal', 'font-weight': !this.isUnchanged ? 'bold' : 'normal', 'font-size': this.isSpecial ? '36px' : '12px' };}
ngStyle 这种方式相当于在代码里面写 CSS 样式,比较丑陋,违反了注意点分离的原则,而且将来不太好修改,非常不建议这样写。
NgModel 使用案例代码:
<p class="text-danger">ngModel只能用在表单类的元素上面</p> <input [(ngModel)]="currentRace.name"><p>{{currentRace.name}}</p>
public currentRace:any={name:"随机种族"};
请注意,如果你需要使用 NgModel 来进行双向数据绑定,必须要在对应的模块里面 import FormsModule。
管道的一个典型作用是用来格式化数据,来一个最简单的例子:
{{currentTime | date:'yyyy-MM-dd HH:mm:ss'}}
public currentTime: Date = new Date();
Angular里面一共内置了12个管道:
在复杂的业务场景里面,12个管道肯定不够用,如果需要自定义管道,请查看这里的例子。
管道还有另一个典型的作用,就是用来做国际化,后面有一个独立的小节专门演示 Angular 的国际化写法。
本节完整可运行的实例代码请参见这里请检出 template 分支。
阅读全文: http://gitbook.cn/gitchat/column/59dae2081e6d652a5a9c3603
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。