内容简介
本书堪称 Angular 领域的里程碑式著作,涵盖了关于 Angular 的几乎所有内容。对于没有经验的人,本书平实、通俗的讲解,递进、严密的组织,可以让人毫无压力地登堂入室,迅速领悟新一代 Web 应用开发的精髓。如果你有相关经验,那本书对 Angular 概念和技术细节的全面剖析,以及引人入胜、切中肯綮的讲解,将帮助你彻底掌握这个框架,在自己职业技术修炼之路上更进一步。本书的读者对象为所有想要理解和学习 Angular 的前端开发人员。
作者简介
Ari Lerner,全栈工程师,拥有多年 Angular 经验,自办并运营 Angular 电子报 ng-newsletter.com,在著名硅谷工程师培训学校 Hack Reactor 担任 AngularJS 讲师。Fullstack.io 创始人。
Felipe Coury,Gistia Labs 联合创始人兼 CTO。
Nate Murray,全栈工程师,曾任职于 IFTTT,拥有数据挖掘和增量 Web 服务等方面的背景。
Carlos Taborda,Gistia Labs 联合创始人兼主管。
本书内容
推荐序
很高兴这本《Angular 权威教程》成为 Angular 中文资源的一部分,希望它能广受欢迎,给中国的 Angular 社区提供一份令人愉悦的学习资源,也希望它帮助更多工程师开始使用下一代 Angular 框架来开发应用。
我认识雪狼和他所属的 Nice Angular 社区是在2016年。那时候,他们开始了对 Angular 官方网站卓越的本地化工作。现在,这份中文官网已经部署在了 angular.cn 上。
本书及其翻译工作充分体现了中国开源软件开发者的热情和共享精神。感谢雪狼等来自 Nice Angular 社区的志愿者们对此作出的贡献。愿本书帮助你开始试用 Angular!祝你成功!
Naomi Black,Google Angular 项目经理兼主管
作为一项开源技术和前沿 Web 开发框架,Angular 持续吸引着中国区开发人员的关注。作为雪狼及其所属 Nice Angular 社区的集体工作成果,这本书是开源力量的又一次证明,证明这种热情、这种志愿精神确实可以帮助业界享受到全球最新的开发技术。我谨代表 Google 开发技术推广部向这本书的出版表示祝贺。
栾跃,Google 开发技术推广部大中华区主管
译者序
简介
以笔者之所见,《Angular 权威教程》大概是目前除了 Angular 官方文档之外最全面的学习资料了,这从其英文版多达600多页的篇幅就可见一斑。相应地,它面对的对象涵盖了从入门级到中高级的读者,是一本可以陪伴你成长的好书。
在内容安排上,本书具有大量的例子以保障其足够浅显,但也穿插着一些原理分析以保障其足够深入。除此之外,本书还给出了很多外部参考资料,让富有探险精神的你可以向专家级进发。
翻译说明
未来的版本号及发布计划
Angular 就要出4.0了!是的,过一阵子还有 Angular 5/6/7/8……这本书会很快过时吗?答案是“不会”。Angular 开发组对于未来的版本号及发布计划有一个正式的说明,大意是:
我们要兼顾向后兼容和向前演进,因此以后我们将严格遵循 SemVer 语义化版本规范,并力求让版本升级变得可预测,以便使用者可以提前安排。在大版本号之间会出现少量破坏性变更,但是不用担心,相邻的大版本号之间只会把一些 API 标记为废弃的。也就是说,理想情况下,4的程序是可以直接迁移到5的,只是会收到一些 API 废弃提示,到6中才会彻底移除。同时,官方会在文档中给出详细的升级指南,帮助开发者升级。
因此,尽请放心,Angular 以后绝不会出现像从1升级到2这么大的变化。事实上,NodeJS 现在采用的就是类似的版本策略,提高发布的可预测性对于工程化开发是很有价值的。
另外,这里为什么没有3?简单点说就是因为路由模块比其他模块多发布过一次,因此当你使用 core 模块的2.0时,和它配套的 router 模块却是3.0的,这容易让开发人员困惑,跳过3,可以让所有模块的编号重新对齐。
对框架名称的说明
Angular 开发组正式确定了新的命名策略:用 AngularJS 来代表 1.x 版本,而 Angular 代表 2.x、4.x、5.x 等很多后续版本,因为 Angular 2+ 将支持 TypeScript/JavaScript/Dart,而不再是 JavaScript。这些变化已经在官方文档中体现出来了,而本书也将同样遵循这样的命名策略。
名词:装饰器与注解
@Component
等语法元素在 TypeScript 中被称为装饰器(decorator),但在本书中,作者统一称其为注解(annotation)。这两种提法都是正确的。在语法层面,@Component
确实是装饰器,这是 TypeScript 的标准叫法;但是在语义层面,Angular 中是把它作为注解使用的。两者的区别是,装饰器直接改变被装饰者的行为,而注解则提供元数据,供框架去根据这些元数据做不同的处理。在 Angular 目前的版本中,@Component
确实只是提供了元数据。
我们在跟原作者讨论之后,决定还是跟随作者的提法来翻译。不过在日常工作中,还是建议你遵循 TypeScript 的提法,将其称为装饰器。
支持与勘误
如果对本书中的一些概念不太理解,请参阅 Angular 官方中文站 angular.cn。这里有来自官方开发组的权威资料。
如果对本书有任何疑问或发现问题,请到 https://github.com/nice-angular/ng-book-2 提交 issue。
同时,对于一些经过确认的 issue,我们也会更新在勘误区。
关于我们
参与本次翻译的一共有7位成员,都是 AngularJS 领域的专家和 Angular 领域的先行者。稍后会有我们的简短介绍。
本书各章的译者和校对者如下:
翻译 | 一校 | 二校 | |
---|---|---|---|
第1章 | 雪狼、叶志敏 | 郑丰彧 | 郑丰彧 |
第2章 | 破狼 | 破狼 | 雪狼 |
第3章 | 张旋 | 张旋 | 雪狼 |
第4章 | 郑丰彧 | 郑丰彧 | 雪狼 |
第5章 | 破狼 | 破狼 | 雪狼 |
第6章 | 王子实 | 王子实 | 雪狼 |
第7章 | 叶志敏 | 叶志敏 | 叶志敏 |
第8章 | 雪狼 | 雪狼 | 雪狼 |
第9章 | 郑丰彧 | 郑丰彧 | 雪狼 |
第10章 | 郑丰彧 | 郑丰彧 | Hantsy |
第11章 | 郑丰彧 | 郑丰彧 | Hantsy |
第12章 | 郑丰彧 | 郑丰彧 | 雪狼 |
第13章 | 郑丰彧 | 郑丰彧 | 雪狼 |
第14章 | 郑丰彧 | 郑丰彧 | 雪狼 |
第15章 | Hantsy | Hantsy | 叶志敏 |
第16章 | 雪狼 | 雪狼 | 张旋 |
除此之外,雪狼还承担了项目管理和中文统稿工作;破狼负责全书的技术准确性把关;叶志敏负责与作者沟通,并在英文理解方面进行把关。
我们的感恩
本书得以发行,首先要感谢 Angular 开发组及其项目经理 Naomi Black。正是由于她的支持和牵线搭桥,才有了我们和图灵的这次合作。
我们还要感谢 Google 开发技术推广部及其大中华区主管栾跃和项目经理程路,正是由于他们的努力,让 Angular 在中国的推广普及工作有了正规军的加入,而本书的出版正是推广计划中的一小部分。
我们还要感谢图灵的编辑朱巍和杨琳,在整个翻译过程中,她们给了我们许多专业的指导和帮助。本书得以在迅速出版的同时保证高质量,她们的经验和把关居功甚伟。
最后,要感谢 Angular 中文社区。我所指的并不是由我们几个创建并管理的这些 QQ 群、微信群等,而是指广义的中文社区。无论你在北京还是上海,也无论你在国内还是海外;无论你是高手还是新兵,也无论你是否像我们一样是 Angular 的忠实粉丝,你们都是广义 Angular 中文社区中的一员。在我们的心中,只有一个 Angular 中文社区,她不被任何人拥有,也被每一个人拥有,因为她就是我们每个人。
固然,我们这几位译者都是推广 Angular 的志愿者与先行者,但我们真正希望看到的是一个繁荣、开放、互通的中文社区,是全球 Angular 社区的一部分,我们希望看到 Angular 的技术社区遍地开花。因此,如果你有自己的组织或影响力,请联系我们,我们愿与你携手共进,分享各种知识、渠道与资源,共同制定与推进社区发展计划。要知道,无论你将来是求职还是创业,一个繁荣的社区都会给你带来强力的支持。
一旦有了共同的愿景和开放、包容的文化,我们就能无视时空的阻隔,在天南海北守望相助,共同面对新技术的挑战与机遇。纷繁的世界、冰冷的技术与温暖的社区,共同构成了本书的出版背景。
雪狼的感恩
汪志成,网名雪狼。ThoughtWorker & Google 开发者专家(GDE),拥有18年软件开发经验,崇尚简单、专业、分享,“好为人师,好为人师”;合著有《AngularJS深度剖析与最佳实践》。
首先,我要感谢我的家人,特别是我的妻子春娜。为了翻译官方文档和这本书,我失去了很多陪伴他们的时间,没有他们的支持,故事将无从开始。
其次,我要感谢 ThoughtWorks,没有这样一个平台,我就无法安心钻研技术,更没有大量把新技术应用于工程实践中的机会。
最后,要特别感谢我刚刚出生的女儿,你是激励我前进的动力。闺女,看到了吗?这是老爹给你的迎新礼物。
破狼的感恩
格茸扎西,网名破狼。ThoughtWorks 一线码农、架构师、咨询师;爱好读书和旅游,也常涂鸦一些技术博文;合著有《AngularJS 深度剖析与最佳实践》;国内 Angular 最早布道者,Nice Angular 社区“狼主”。
首先,要感谢我的妻子和父亲。因为他们的鼓励,我才能顺利完成本书相应章节的翻译。
其次,要感谢 ThoughtWorks 这个大家庭。因为在这个自组织和黑客文化环境的熏陶下,我才能潜心钻研这些技术。
最后,要感谢图灵出版社的朱巍编辑、本书的作者以及其他译者们。
叶志敏的感恩
叶志敏,虽留英多年、远漂他乡、四处奔波,一颗热爱软件开发的心却依旧如初。多年前曾与雪狼共事,合作愉快,因此成为好朋友。由雪狼推荐进入 Angular 世界,使用 Angular 和 .NET 平台开发软件多年。从 Alpha 阶段开始使用 Angular。与雪狼合作,翻译 Angular 官方文档站,并经过 Angular 团队的推荐,承接翻译本书的重任。
首先感谢我的妻子。从怀孕到照顾女儿健康成长,她一直对我的工作非常理解和支持,从无怨言。其次,感谢我母亲和岳母的慈爱与帮助。最后,希望女儿能健康成长,平安一生。
Hantsy 的感恩
Hantsy,拥有15年软件工程经验。2012年曾受JBoss(RedHat 子公司)邀请前往波士顿参加 JBoss 用户和开发人员年度大会,并获得 JBoss Community Recognition Awards。现为自由职业者,远程工作多年。
感谢 Angular 中文团队和图灵的支持,非常荣幸参与本书中文版的翻译。感谢 Angular 团队的努力,为我们带来如此优秀的工具框架。
张旋的感恩
张旋,PMP、ACP、NPDP,中科院计算所烟台分所集成应用中心主任。1982年生人,1996年起接触编程。正式从事软件工作行业11年。擅长项目管理、团队管理、技术体系建设。非常喜欢研究和对比各种新技术,生成适合工程使用的技术栈,并灌输到整个团队中去。
十分荣幸能成为 Nice Angular 社区的一员,感谢雪狼和破狼。感谢本书原作者为我们提供了一本这么好的 Angular 教程,也感谢本书的所有翻译者,从你们身上我确实学到了很多。感谢我的老婆莉莉,照看乐乐辛苦了,谢谢你给我时间让我做自己喜欢的事。最后感谢图灵出版社的朱巍编辑,本次合作非常愉快,期待下次更好的机会!
郑丰彧的感恩
郑丰彧,网名Z,现就职于大商集团天狗网,Angular 爱好者,喜欢函数式编程、WebGL。
首先,我要感谢雪狼,一次很偶然的机会受到雪狼的邀请,让我受宠若惊,也为我开启了这次 Angular 翻译之旅。
其次,我要感谢我的家人,尤其是有孕在身的老婆和孕育中的宝宝。为了翻译这本书,我牺牲了很多原本用来陪伴你们的时间。
最后,我想对即将出世的女儿柚柚说句话:我们全家人对你的期待正如我们 Nice Angular 社区对此书的期待。所以,赶快“问世”吧!
王子实的感恩
王子实,现任光辉城市全栈工程师。1992年生,自学生时代便喜好编程,一直以来对各种新技术非常着迷,乐于对其进行研究与探索,并将成果在团队中进行推广,以提升整体效率。
非常感谢雪狼能够给我这次机会参与到本书的翻译中来,能够让我对 Angular 社区尽一点点自己的绵薄之力。
同时也要感谢其他参与翻译的译者们,让我有了这次非常宝贵的经验。尤其是在翻译过程中遇到一些技术问题以及对原书内容有一些疑惑时,大家探究与实践的精神让我印象深刻。
还要感谢我的妻子默默给予我支持与理解。
最后,就是要感谢 Google 带给我们 Angular 这个强大而又好用的框架。希望它也能越来越好,不断进步!
编写你的第一个 Angular Web 应用(上)
-
-
- 1.1 仿制 Reddit 网站
- 1.2 起步
- 1.2.1 TypeScript
- 1.2.2 angular-cli
- 1.2.3 示例项目
- 1.3 运行应用
- 1.3.1 制作Component
- 1.3.2 导入依赖
- 1.3.3 Component注解
- 1.3.4 用templateUrl添加模板
- 1.3.5 添加template
- 1.3.6 用styleUrls添加 CSS 样式
- 1.3.7 加载组件
- 1.4 把数据添加到组件中
-
1.1 仿制 Reddit 网站
在本章中,我们将构建一个应用,它能让用户发表推荐文章(包括标题和URL)并对每篇文章投票。
你可以把该应用看作类似于Reddit{1[http://reddit.com]}或 Product Hunt{2[http://producthunt.com]}的起步版网站。
这个简单的应用将涵盖 Angular 中的大部分基本要素,包括:
构建自定义组件;
从表单中接收用户输入;
把对象列表渲染到视图中;
拦截用户的点击操作,并据此作出反应。
读完本章之后,你将掌握如何构建基本的 Angular 应用。
图1-1展示了该应用最终完成后的界面截图。
图1-1 完成后的应用
首先,用户将提交一个新的链接。之后,其他用户可以对每篇文章投票:“顶”或“踩”。每个链接都有一个最终得票数,我们可以对自己认为有用的链接投票(如图1-2所示)。
图1-2 包含新文章的应用
在本项目和整本书中,我们都将使用 TypeScript。TypeScript 是 JavaScript ES6 版的一个超集,增加了类型支持。本章不会深入讲解 TypeScript;如果你熟悉 ES5(“普通”的JavaScript)或 ES6(ES2015),那么在后续的学习过程中应该不会有什么问题。
在第2章中,我们将更深入地学习 TypeScript。因此,即使你对某些新语法不太熟悉,也不必担心。
1.2 起步
1.2.1 TypeScript
要开始使用 TypeScript,首先需要安装 Node.js。安装方式很多,请参见 Node.js 官方网站(https://nodejs.org/download/)了解详情。
我必须用 TypeScript 吗?并非如此!要使用 Angular,TypeScript 并不是必需的,但它可能是最好的选择。Angular 也有一套 ES5 API,但 Angular 本身就是用 TypeScript 写成的,所以人们一般也会选用它。本书也将使用 TypeScript,因为它确实很棒,能让 Angular 写起来更简单。当然,并不是非它不可。
安装完 Node.js,接着就要安装 TypeScript了。请确保安装1.7或更高的版本。要想安装它,请运行下列npm
命令:
$ npm install -g typescript
通常,
npm
是 Node.js 的一部分。如果你的系统中没有npm
命令,请确认你安装的 Node.js 是包含它的版本。**
Windows 用户:我们将在全书中使用 Linux/Mac 风格的命令行。强烈建议你安装 Cygwin{3[https://www.cygwin.com/]}。借助它,你就能直接运行本书中的这些命令了。
1.2.2 angular-cli
Angular 提供了一个命令行工具angular-cli
,它能让用户通过命令行创建和管理项目。它自动化了一系列任务,比如创建项目、添加新的控制器等。多数情况下,选用angular-cli
都是明智的决定。当你创建和维护应用时,它能帮你遵循很多常用模式。
要想安装angular-cli
,只要运行下列命令即可:
$ npm install -g angular-cli@1.0.0-beta.18
安装完毕之后,你就可以在命令行中用ng
命令运行它了。运行ng
命令时,你会看到一大堆输出,不过不用管它;往回滚屏,你会看到如下内容:
$ ngCould not start watchman; falling back to NodeWatcher for file system events.Visit http://ember-cli.com/user-guide/#watchman for more info.Usage: ng
之所以得到这一大堆输出,是因为当我们不带参数运行ng
命令时,它就会执行默认的help
命令。help
命令会解释如何使用本工具。
如果你在 OS X 或 Linux 上运行,可能还会在输出中看到这一行:
Could not start watchman; falling back to NodeWatcher for file system events.
这意味着我们没有安装过一个名叫 watchman 的工具。此工具能帮助angular-cli
监听文件系统的变化。如果你在 OS X 上运行,建议使用 Homebrew 工具安装它,命令如下:
$ brew install watchman
如果你是 OS X 用户并且运行这个
brew
命令时出现错误,那么表示你尚未正确安装 Homebrew 工具。请参阅http://brew.sh/来安装它,然后再试一次。如果你是 Linux 用户,可以参阅https://ember-cli.com/user-guide/#watchman来学习如何安装 watchman。
如果你是 Windows 用户,那么不必安装任何东西,
angular-cli
将使用原生的 Node.js 文件监视器。
现在你应该已经装好angular-cli
及其依赖了。在本章中,我们就用它来创建第一个应用。
1.2.3 示例项目
现在,环境已经准备好了,我们这就来编写第一个 Angular 应用吧!
打开终端窗口并且运行ng new
命令,快速创建一个新的项目:
$ ng new angular2_hello_world
运行之后,你将看到下列输出:
installing ng 2 create .editorconfig create README.md create src/app/app.component.css create src/app/app.component.html create src/app/app.component.spec.ts create src/app/app.component.ts create src/app/app.module.ts create src/app/index.ts create src/app/shared/index.ts create src/assets/.gitkeep create src/assets/.npmignore create src/environments/environment.dev.ts create src/environments/environment.prod.ts create src/environments/environment.ts create src/favicon.ico create src/index.html create src/main.ts create src/polyfills.ts create src/styles.css create src/test.ts create src/tsconfig.json create src/typings.d.ts create angular-cli.json create e2e/app.e2e-spec.ts create e2e/app.po.ts create e2e/tsconfig.json create .gitignore create karma.conf.js create package.json create protractor.conf.js create tslint.jsonSuccessfully initialized git.( Installing packages for tooling via npm
它将运行一段时间,进行 npm 依赖的安装。一旦安装结束,我们会看到一条成功信息:
Installed packages for tooling via npm.
这里生成了很多文件!现在不用关心它们都是什么。我们会在本书中讲解每一个文件的含义和用途。不过现在,我们先把注意力集中在如何用 Angular 代码开始工作上。
进入ng
命令创建的 angular2_hello_world 目录,来看看它里面都有什么:
$ cd angular2_hello_world$ tree -F -L 1.├── README.md // an useful README├── angular-cli.json // angular-cli configuration file├── e2e/ // end to end tests├── karma.conf.js // unit test configuration├── node_modules/ // installed dependencies├── package.json // npm configuration├── protractor.conf.js // e2e test configuration├── src/ // application source└── tslint.json // linter config file3 directories, 6 files
我们目前关注的目录是 src,应用代码就在里面。下面看看我们在那里创建了什么:
$ cd src$ tree -F.|-- app/| |-- app.component.css| |-- app.component.html| |-- app.component.spec.ts| |-- app.component.ts| |-- app.module.ts| |-- index.ts| `-- shared/| `-- index.ts|-- assets/|-- environments/| |-- environment.dev.ts| |-- environment.prod.ts| `-- environment.ts|-- favicon.ico|-- index.html|-- main.ts|-- polyfills.ts|-- styles.css|-- test.ts|-- tsconfig.json`-- typings.d.ts4 directories, 18 files
用你惯用的文本编辑器打开 index.html,应该会看到如下代码。
code/first_app/angular2_hello_world/src/index.html
Angular2HelloWorld Loading...
我们把它分解一下。
code/first_app/angular2_hello_world/src/index.html
Angular2HelloWorld
如果你熟悉 HTML,这第一部分就很平淡无奇了。我们在这里声明了页面的字符集(charset)、标题(title)和基础 URL(base href)。
code/first_app/angular2_hello_world/src/index.html
如果你继续深入模板主体(body),就会看到下列代码。
code/first_app/angular2_hello_world/src/index.html
Loading...
我们的应用将会在app-root
标签处进行渲染,稍后剖析源代码的其他部分时还会看到它。文本 Loading...是一个占位符,在应用代码加载之前会显示它。我们可以借助此技巧来通知用户该应用正在加载,可以像这里一样显示一条消息,也可以显示一个加载动画或其他形式的进度通知。
之后就可以编写应用代码了。
1.3 运行应用
在开始修改之前,我们先把这个自动生成的初始应用加载到浏览器中。angular-cli
有一个内建的 HTTP 服务器,我们可以用它来启动应用。回到终端,进入应用的根目录(在本应用中是./angular2_hello_world目录)并运行命令。
$ ng serve** NG Live Development Server is running on http://localhost:4200. **// a bunch of debug messagesBuild successful - 1342ms.
我们的应用正在 localhost 的4200端口上运行。打开浏览器并访问 http://localhost:4200,结果如图1-3所示。
注意,如果4200端口由于某种原因被占用了,也可以在其他端口号上启动。仔细阅读你电脑上的输出信息,找出开发服务器的实际 URL。
图1-3 运行中的应用
好,现在我们设置好了应用,而且知道了该如何运行它,可以开始写代码了。
1.3.1 制作Component
Angular 背后的指导思想之一就是组件化。
在 Angular 应用中,我们写 HTML 标记并把它变成可交互的应用。不过浏览器只认识一部分标签,比如、
和
等,它们的功能都是由浏览器的开发者预先定义好的。
如果我们想教浏览器认识一些新标签,该怎么办呢?比如我们想要一个
标签,用来显示天气;又比如想要一个
标签,用来创建一个登录面板。
这就是组件化背后的基本思想:我们要教浏览器认识一些拥有自定义功能的新标签。
如果你用过 AngularJS,那么可以把组件当作新版本的指令。
让我们来创建第一个组件。写完该组件之后,就能在 HTML 文档中使用它了,就像这样:
要使用angular-cli
来创建新组件,可以使用generate
(生成)命令。
要生成hello-world
组件,我们需要运行下列命令:
$ ng generate component hello-worldinstalling component create src/app/hello-world/hello-world.component.css create src/app/hello-world/hello-world.component.html create src/app/hello-world/hello-world.component.spec.ts create src/app/hello-world/hello-world.component.ts
那该怎么定义一个新组件呢?最基本的组件包括两部分:
(1) Component
注解
(2) 组件定义类
下面来看看组件的代码,然后逐一讲解。打开第一个 TypeScript 文件:src/app/hello-world/hello-world.component.ts。
code/first_app/angular2_hello_world/src/app/hello-world/hello-world.component.ts
import { Component, OnInit } from '@angular/core';@Component({ selector: 'app-hello-world', templateUrl: './hello-world.component.html', styleUrls: ['./hello-world.component.css']})export class HelloWorldComponent implements OnInit { constructor() { } ngOnInit() { }}
注意,TypeScript 文件的后缀是.ts 而不是.js。问题在于浏览器并不知道该如何解释 TypeScript 文件。为了解决这个问题,
ng serve
命令会自动把.ts 文件编译为.js 文件。
这个代码片段乍一看可能有点恐怖,但别担心,我们接下来就会一步步讲解它。
1.3.2 导入依赖
import
语句定义了我们写代码时要用到的那些模块。这里我们导入了两样东西:Component
和OnInit
。
我们从"@angular/core"
模块中导入了组件(import Component
)。"@angular/core"
部分告诉程序到哪里查找所需的这些依赖。这个例子中,我们告诉编译器:"@angular/core"
定义并导出了两个 JavaScript/TypeScript 对象,名字分别是Component
和OnInit
。
同样,我们还从这个模块中导入了OnInit
(import OnInit
)。稍后你就会知道,OnInit
能帮我们在组件的初始化阶段运行某些代码。不过现在先不用管它。
注意这个import
语句的结构是import { things } from wherever
格式。我们把{ things }
这部分的写法叫作解构。解构是由 ES6 和 TypeScript 提供的一项特性,下一章会深入讲解。
import
的用法很像 Java 中的import
或 Ruby 中的require
:从另一个模块中拉取这些依赖,并且让这些依赖在当前文件中可用。
1.3.3 Component
注解
导入依赖后,我们还要声明该组件。
code/first_app/angular2_hello_world/src/app/hello-world/hello-world.component.ts
@Component({ selector: 'app-hello-world', templateUrl: './hello-world.component.html', styleUrls: ['./hello-world.component.css']})
如果你习惯用 JavaScript 编程,那么下面这段代码可能看起来有点怪异:
@Component({ // ...})
这是什么?如果你有 Java 开发背景,应该会很熟悉:它们是注解。
AngularJS 的依赖注入技术在幕后使用了注解的概念。也许你还不熟悉它们,但注解其实是让编译器为代码添加功能的途径之一。
我们可以把注解看作添加到代码上的元数据。当在HelloWorld
类上使用@Component
时,就把HelloWorld
“装饰”(decorate)成了一个Component
。
这个标签表示我们希望在 HTML 中使用该组件。要实现它,就得配置
@Component
并把selector
指定为app-hello-world
。
@Component({ selector: 'app-hello-world' // ... more here})
有很多种方式来配置选择器(selector),类似于 CSS 选择器、XPath 或 JQuery 选择器。Angular 组件对选择器的混用方式添加了一些特有的限制,稍后会谈到。现在,只要记住我们正在定义一个新的 HTML 标签就可以了。
这里的selector
属性用来指出该组件将使用哪个 DOM 元素。如果模板中有标签,就用该
Component
类及其组件定义信息对其进行编译。
1.3.4 用templateUrl
添加模板
在这个组件中,我们把templateUrl
指定为./hello-world.component.html
。这意味着我们将从与该组件同目录的 hello-world.component.html 文件中加载模板。下面来看看这个文件。
code/first_app/angular2_hello_world/src/app/hello-world/hello-world.component.html
hello-world works!
这里定义了一个p
标签,其中包含了一些简单的文本。当Angular加载该组件时,就会读取此文件的内容作为组件的模板。
1.3.5 添加template
我们有两种定义模板的方式:使用@Component
对象中的template
属性;指定templateUrl
属性。
我们可以通过传入template
选项来为@Component
添加一个模板:
@Component({ selector: 'app-hello-world', template: ` hello-world works inline!
`})
注意,我们在反引号中(` ... `)定义了template
字符串。这是 ES6 中的一个新特性(而且很棒),允许使用多行字符串。使用反引号定义多行字符串,可以让我们更轻松地把模板放到代码文件中。
你真的应该把模板放进代码文件中吗?答案是:视情况而定。在很长一段时间里,大家都觉得最好把代码和模板分开。这对于一些开发团队来说确实更容易,不过在某些项目中会增加成本,因为你将不得不在一大堆文件之间切换。
个人观点:如果模板行数短于一页,我更倾向于把模板和代码放在一起(也就是.ts 文件中)。这样就能同时看到逻辑和视图部分,同时也便于理解它们之间如何互动。
把视图和代码内联在一起的最大缺点是,很多编辑器仍然不支持对内部 HTML 字符串进行语法高亮。我们期待能尽快看到有更多编辑器支持对模板字符串内嵌 HTML 的语法高亮。
1.3.6 用styleUrls
添加 CSS 样式
注意styleUrls
属性:
styleUrls: ['./hello-world.component.css']
这段代码的意思是,我们要使用 hello-world.component.css 文件中的 CSS 作为该组件的样式。Angular 使用一项叫作样式封装(style-encapsulation)的技术,它意味着在特定组件中指定的样式只会应用于该组件本身。14.1节会深入讨论它。
目前还用不到任何“组件局部样式”,你只要先知道它就行了(或整体删除此属性)。
你可能注意到了该属性与
template
有个不同点:它接收一个数组型参数。这是因为我们可以为同一个组件加载多个样式表。
1.3.7 加载组件
现在,我们已经写完了第一个组件的代码,那该如何把它加载到页面中呢?
如果再次在浏览器中访问此应用,我们会看到一切照旧。这是因为我们仅仅创建了该组件,但还没有使用它。
为了解决这一点,需要把该组件的标签添加到一个将要渲染的模板中去。打开文件 first_app/angular2_hello_world/src/app/app.component.html。
记住,因为我们为HelloWorldComponent
配置了app-hello-world
选择器,所以要在模板中使用。让我们把
标签添加到 app.component.html 中。
code/first_app/angular2_hello_world/src/app/app.component.html
{{title}}
现在,刷新该页面就会看到如图1-4所示结果。
图1-4 “Hello world”一切正常
工作正常!
1.4 把数据添加到组件中
现在,该组件渲染了一个静态模板。这表示我们的组件还不够有趣。
设想有一个应用会显示一个用户列表,并且我们想在其中显示用户的名字。在渲染整个列表之前,需要先渲染一个单独的用户。因此,我们来创建一个新的组件,它将显示用户的名字。
再次使用ng generate
命令:
ng generate component user-item
记住,想看到我们创建好的组件,就要把它添加到一个模板中。
让我们把app-user-item
标签添加到app.component.html中,以便看到所作的改动。把app.component.html修改成下面这样。
code/first_app/angular2_hello_world/src/app/app.component.html
{{title}}
然后刷新页面,并确认你在该页看到文本 user-item works!。
我们希望UserItemComponent
显示一个指定用户的名字。
因此,引入name
并声明为组件的一个新属性。有了name
属性,我们就能在不同的用户之间复用该组件了(但要求页面脚本、逻辑和样式相同)。
为了添加名字,我们要在UserItemComponent
类上引入一个属性,来声明该组件有一个名叫name
的局部变量。
code/first_app/angular2_hello_world/src/app/user-item/user-item.component.ts
export class UserItemComponent implements OnInit { name: string; // <-- added name property constructor() { this.name = 'Felipe'; // set the name } ngOnInit() { }}
注意,我们改变了以下两点。
name
属性我们往
UserItemComponent
类添加了一个属性。注意,这相对于 ES5 JavaScript 来说是个新语法。在name:string;
中,name
是我们想设置的属性名,而string
是该属性的类型。为
name
指定类型是 TypeScript 中的特性,用来确保它的值必须是string
。这些代码在UserItemComponent
类的实例中设置了一个名为name
的属性,并且编译器会确保name
是一个string
。构造函数
在
UserItemComponent
类中,我们定义了一个构造函数。这个函数会在创建这个类的实例时自动调用。在我们的构造函数中,可以通过
this.name
来设置name
属性。如果这样写:
code/first_app/angular2_hello_world/src/app/user-item/user-item.component.ts
constructor() { this.name = 'Felipe'; // set the name }
就表示当一个新的
UserItemComponent
组件被创建时,把name
设置为'Felipe'
。渲染模板
填好这个值之后,我们可以用模板语法(也就是双花括号语法
{{ }}
)在模板中显示该变量的值。code/first_app/angular2_hello_world/src/app/user-item/user-item.component.html
Hello {{ name }}
注意,我们在
template
中引入了一个新的语法:{{ name }}
。这些括号叫作“模板标签”(也叫“小胡子标签”)。模板标签中间的任何东西都会被当作一个表达式来展开。这里,因为template
是绑定到组件上的,所以name
将会被展开为this.name
的值,也就是'Felipe'
。
试试看
进行这些修改之后,重新加载页面,页面上应该显示 Hello Felipe,如图1-5所示。
图1-5 带有数据的应用
TypeScript
-
-
- 2.1 Angular 是用 TypeScript 构建的
- 2.2 TypeScript 提供了哪些特性
- 2.3 类型
- 尝试 REPL
- 2.4 内置类型
- 2.4.1 字符串
- 2.4.2 数字
- 2.4.3 布尔类型
- 2.4.4 数组
- 2.4.5 枚举
- 2.4.6 任意类型
- 2.4.7 “无”类型
- 2.5 类
- 2.5.1 属性
- 2.5.2 方法
- 2.5.3 构造函数
- 2.5.4 继承
- 2.6 工具
- 2.6.1 胖箭头函数
- 2.6.2 模板字符串
- 2.7 总结
-
2.1 Angular 是用 TypeScript 构建的
Angular 是用一种类似于 JavaScript 的语言——TypeScript{1[http://www.typescriptlang.org/]}——构建的。
或许你会对用新语言来开发 Angular 心存疑虑,但事实上,在开发 Angular 应用时,我们有充分的理由用 TypeScript 代替普通的 JavaScript。
TypeScript 并不是一门全新的语言,而是 ES6 的超集。所有的 ES6 代码都是完全有效且可编译的TypeScript 代码。图2-1展示了它们之间的关系。
图2-1 ES5、ES6 和 TypeScript
什么是 ES5?什么是 ES6?ES5 是 ECMAScript 5 的缩写,也被称为“普通的 JavaScript”。ES5 就是大家熟知的 JavaScript,它能够运行在大部分浏览器上。ES6 则是下一个版本的 JavaScript,在后续章节中我们还会深入讨论它。
在本书出版的时候,支持 ES6 的浏览器还很少,更不用说 TypeScript 了。我们用转译器来解决这个问题。TypeScript 转译器能把 TypeScript 代码转换为几乎所有浏览器都支持的 ES5 代码。
从 TypeScript 代码到 ES5 代码的唯一转换器是由 TypeScript 核心团队编写的。然而,将 ES6 代码(不是 TypeScript 代码)转换到 ES5 代码则有两个主要的转换器:Google 开发的 Traceur{2[https://github.com/google/traceur-compiler]}与 JavaScript 社区创建的 Babel{3[https://babeljs.io/]}。在本书中我们并不会直接使用它们,但它们也是值得了解的不错项目。
我们在上一章安装了 TypeScript 环境,如果你是从本章开始学习的,那么可以这样安装 TypeScript 环境:
npm install -g typescript
。
TypeScript 是 Microsoft 和 Google 之间的官方合作项目。有这两家强有力的科技巨头在背后支撑,对于我们来说是个好消息,因为这表示 TypeScript 将会得到长期的支持。这两家公司都承诺全力推动 Web 技术的发展,我们这些开发人员显然会获益匪浅。
另外,转译器的好处还在于:它允许小型团队对语言进行改善,而不必要求所有人都去升级他们的浏览器。
需要指出的是:TypeScript 并不是开发 Angular 应用的必选语言。我们同样可以使用 ES5 代码(即“普通”JavaScript)来开发 Angular 应用。Angular 也为全部功能提供了 ES5 API。那么为什么我们还要使用 TypeScript 呢?这是因为 TypeScript 有不少强大的功能,能极大地简化开发。
2.2 TypeScript 提供了哪些特性
TypeScript 相对于 ES5 有五大改善:
类型
类
注解
模块导入
语言工具包(比如,解构)
接下来我们逐个介绍。
2.3 类型
顾名思义,相对于 ES6,TypeScript 最大的改善是增加了类型系统。
有些人可能会觉得,缺乏类型检查正是 JavaScript 这些弱类型语言的优点。也许你对类型检查心存疑虑,但我仍然鼓励你试一试。类型检查的好处有:
(1) 有助于代码的编写,因为它可以在编译期预防 bug;
(2) 有助于代码的阅读,因为它能清晰地表明你的意图。
另外值得一提的是,TypeScript 中的类型是可选的。如果希望写一些快速代码或功能原型,可以首先省略类型,然后再随着代码日趋成熟逐渐加上类型。
TypeScript 的基本类型与我们平时所写 JavaScript 代码中用的隐式类型一样,包括字符串、数字、布尔值等。
直到 ES5,我们都在用var
关键字定义变量,比如var name;
。
TypeScript 的新语法是从 ES5 自然演化而来的,仍沿用var
来定义变量,但现在可以同时为变量名提供可选的变量类型了:
var name: string;
在声明函数时,也可以为函数参数和返回值指定类型:
function greetText(name: string): string { return "Hello " + name;}
这个例子中,我们定义了一个名为greetText
的新函数,它接收一个名为name
的参数。name: string
语法表示函数想要的name
参数是string
类型。如果给该函数传一个string
以外的参数,代码将无法编译通过。对我们来说,这是好事,否则这段代码将会引入 bug。
或许你还注意到了,greetText
函数在括号后面还有一个新语法:string {
。冒号之后指定的是该函数的返回值类型,在本例中为string
。这很有用,原因有二:如果不小心让函数返回了一个非string
型的返回值,编译器就会告诉我们这里有错误;使用该函数的开发人员也能很清晰地知道自己将会拿到什么类型的数据。
我们来看看如果写了不符合类型声明的代码会怎样:
function hello(name: string): string { return 12;}
当尝试编译代码时,将会得到下列错误:
$ tsc compile-error.tscompile-error.ts(2,12): error TS2322: Type 'number' is not assignable to type 'string'.
这是怎么回事?我们尝试返回一个number
类型的12
,但hello
函数期望的返回值类型为string
(它是在参数声明的后面以): string {
的形式声明的)。
要纠正它,可以把函数的返回值类型改为number
:
function hello(name: string): number { return 12;}
虽然这只是一个小例子,但足以证明类型检查能为我们节省大量调试 bug 的时间。
现在知道了如何使用类型,但怎么才能知道有哪些可用类型呢?接下来我们就会罗列出这些内置的类型,并教你如何创建自己的类型。
尝试 REPL
为了运行本章中的例子,我们要先安装一个小工具,名为 TSUN{4[https://github.com/HerringtonDarkholme/typescript-repl]}(TypeScript Upgraded Node,支持 TypeScript 的升级版 Node):
$ npm install -g tsun
接着启动它:
$ tsunTSUN : TypeScript Upgraded Nodetype in TypeScript expression to evaluatetype :help for commands in repl>
这个小小的>
是一个命令提示符,表示 TSUN 已经准备好接收命令了。
对于本章后面的大部分例子,你都可以复制粘贴到这个终端窗口中运行。
2.4 内置类型
2.4.1 字符串
字符串包含文本,声明为string
类型:
var name: string = 'Felipe';
2.4.2 数字
无论整数还是浮点,任何类型的数字都属于number
类型。在 TypeScript 中,所有的数字都是用浮点数表示的,这些数字的类型就是number
:
var age: number = 36;
2.4.3 布尔类型
布尔类型(boolean
)以true
(真)和false
(假)为值。
var married: boolean = true;
2.4.4 数组
数组用Array
类型表示。然而,因为数组是一组相同数据类型的集合,所以我们还需要为数组中的条目指定一个类型。
我们可以用Array
或者type[]
语法来为数组条目指定元素类型:
var jobs: Array = ['IBM', 'Microsoft', 'Google'];var jobs: string[] = ['Apple', 'Dell', 'HP'];
数字型数组的声明与之类似:
var jobs: Array = [1, 2, 3];var jobs: number[] = [4, 5, 6];
2.4.5 枚举
枚举是一组可命名数值的集合。比如,如果我们想拿到某人的一系列角色,可以这么写:
enum Role {Employee, Manager, Admin};var role: Role = Role.Employee;
默认情况下,枚举类型的初始值是0。我们也可以调整初始化值的范围:
enum Role {Employee = 3, Manager, Admin};var role: Role = Role.Employee;
在上面的代码中,Employee
的初始值被设置为3
而不是0
。枚举中其他项的值是依次递增的,意味着Manager
的值为4
,Admin
的值为5
。同样,我们也可以单独为枚举中的每一项指定值:
enum Role {Employee = 3, Manager = 5, Admin = 7};var role: Role = Role.Employee;
还可以从枚举的值来反查它的名称:
enum Role {Employee, Manager, Admin};console.log('Roles: ', Role[0], ',', Role[1], 'and', Role[2]);
2.4.6 任意类型
如果我们没有为变量指定类型,那它的默认类型就是any
。在TypeScript中,any
类型的变量能够接收任意类型的数据:
var something: any = 'as string';something = 1;something = [1, 2, 3];
2.4.7 “无”类型
void
意味着我们不期望那里有类型。它通常用作函数的返回值,表示没有任何返回值:
function setName(name: string): void { this.name = name;}
2.5 类
JavaScript ES5 采用的是基于原型的面向对象设计。这种设计模型不使用类,而是依赖于原型。
JavaScript 社区采纳了大量最佳实践,以弥补 JavaScript 缺少类的问题。这些最佳实践已经被总结在 Mozilla 的开发指南中了{5[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide]},你还可以找到一篇关于 JavaScript 面向对象设计的优秀概述{6[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Introduction_to_Object-Oriented_JavaScript]}。
不过,在 ES6 中,我们终于有内置的类了。
用class
关键字来定义一个类,紧随其后的是类名和类的代码块:
class Vehicle {}
类可以包含属性、方法以及构造函数。
2.5.1 属性
属性定义了类实例对象的数据。比如名叫Person
的类可能有first_name
、last_name
和age
属性。
类中的每个属性都可以包含一个可选的类型。比如,我们可以把first_name
和last_name
声明为字符串类型(string
),把age
声明为数字类型(number
)。
Person
类的声明是这样的:
class Person { first_name: string; last_name: string; age: number;}
2.5.2 方法
方法是运行在类对象实例上下文中的函数。在调用对象的方法之前,必须要有这个对象的实例。
要实例化一个类,我们使用
new
关键字。比如new Person()
会创建一个Person
类的实例对象。
如果我们希望问候某个Person
,就可以这样写:
class Person { first_name: string; last_name: string; age: number; greet() { console.log("Hello", this.first_name); } }
注意,借助this
关键字,我们能用this.first_name
表达式来访问Person
类的first_name
属性。
如果没有显式声明过方法的返回类型和返回值,就会假定它可能返回任何东西(即any
类型)。然而,因为这里没有任何显式的return
语句,所以实际返回的类型是void
。
注意,
void
类型也是一种合法的any
类型。
调用greet
方法之前,我们要有一个Person
类的实例对象。代码如下:
// declare a variable of type Person var p: Person; // instantiate a new Person instance p = new Person(); // give it a first_name p.first_name = 'Felipe'; // call the greet method p.greet();
我们还可以将对象的声明和实例化缩写为一行代码:
var p: Person = new Person();
假设我们希望Person
类有一个带返回值的方法。比如,要获取某个Person
在数年后的年龄,我们可以这样写:
class Person { first_name: string; last_name: string; age: number; greet() { console.log("Hello", this.first_name); } ageInYears(years: number): number { return this.age + years; } } // instantiate a new Person instance var p: Person = new Person(); // set initial age p.age = 6; // how old will he be in 12 years? p.ageInYears(12); // -> 18
2.5.3 构造函数
构造函数是当类进行实例化时执行的特殊函数。通常会在构造函数中对新对象进行初始化工作。
构造函数必须命名为constructor
。因为构造函数是在类被实例化时调用的,所以它们可以有输入参数,但不能有任何返回值。
我们要通过调用
new ClassName()
来执行构造函数,以完成类的实例化。
当类没有显式地定义构造函数时,将自动创建一个无参构造函数:
class Vehicle {}var v = new Vehicle();
它等价于:
class Vehicle { constructor() { }}var v = new Vehicle();
在 TypeScript 中,每个类只能有一个构造函数。
这是违背 ES6 标准的。在 ES6 中,一个类可以拥有不同参数数量的多个构造函数重载实现。
我们可以使用带参数的构造函数来将对象的创建工作参数化。
比如,我们可以对Person
类使用构造函数来初始化它的数据:
class Person { first_name: string; last_name: string; age: number; constructor(first_name: string, last_name: string, age: number) { this.first_name = first_name; this.last_name = last_name; this.age = age; } greet() { console.log("Hello", this.first_name); } ageInYears(years: number): number { return this.age + years; } }
用下面这种方法重写前面的例子要容易些:
var p: Person = new Person('Felipe', 'Coury', 36);p.greet();
当创建这个对象的时候,其姓名、年龄都会被初始化。
2.5.4 继承
面向对象的另一个重要特性就是继承。继承表明子类能够从父类得到它的行为。然后,我们就可以在这个子类中重写、修改以及添加行为。
如果要深入了解 ES5 的继承是如何工作的,可以参考 Mozilla 开发文档中的文章“Inheritance and the prototype chain”{7[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain]}。
TypeScript 是完全支持继承特性的,并不像 ES5 那样要靠原型链实现。继承是 TypeScript 的核心语法,用extends
关键字实现。
要说明这一点,我们来创建一个Report
类:
class Report { data: Array; constructor(data: Array) { this.data = data; } run() { this.data.forEach(function(line) { console.log(line); }); } }
这个Report
类有一个字符串数组类型的data
的属性。当我们调用run
方法时,它会循环这个data
数组中的每一项数据,然后用console.log
打印出来。
.forEach
是Array
中的一个方法,它接收一个函数作为参数,并对数组中的每一个条目逐个调用该函数。
给Report
增加几行数据,并调用run
把这些数据打印到控制台:
var r: Report = new Report(['First line', 'Second line']);r.run();
运行结果如下:
First lineSecond line
现在,假设我们希望有第二个报表,它需要增加一些头信息和数据,但我们仍想复用现有Report
类的run
方法来向用户展示数据。
为了复用Report
类的行为,要使用extends
关键字来继承它:
class TabbedReport extends Report { headers: Array; constructor(headers: string[], values: string[]) { super(values) this.headers = headers; } run() { console.log(this.headers); super.run(); } }var headers: string[] = ['Name'];var data: string[] = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship'];var r: TabbedReport = new TabbedReport(headers, data)r.run();
2.6 工具
ES6 和 TypeScript 提供了许多语法特性,让编码成为一种享受。其中最重要的两点是:
胖箭头函数语法
模板字符串
2.6.1 胖箭头函数
胖箭头(=>
)函数是一种快速书写函数的简洁语法。
在 ES5 中,每当我们要用函数作为方法参数时,都必须用function
关键字和紧随其后的花括号({}
)表示。就像这样:
// ES5-like examplevar data = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship'];data.forEach(function(line) { console.log(line); });
现在我们可以用=>
语法来重写它了:
// Typescript examplevar data: string[] = ['Alice Green', 'Paul Pfifer', 'Louis Blakenship'];data.forEach( (line) => console.log(line) );
当只有一个参数时,圆括号可以省略。箭头(=>
)语法可以用作表达式:
var evens = [2,4,6,8];var odds = evens.map(v => v + 1);
也可以用作语句:
data.forEach( line => { console.log(line.toUpperCase())});
=>
语法还有一个重要的特性,就是它和环绕它的外部代码共享同一个this
。这是它和普通function
写法最重要的不同点。通常,我们用function
声明的函数有它自己的this
。有时在 JavaScript 中能看见如下代码:
var nate = { name: "Nate", guitars: ["Gibson", "Martin", "Taylor"], printGuitars: function() { var self = this; this.guitars.forEach(function(g) { // this.name is undefined so we have to use self.name console.log(self.name + " plays a " + g); }); }};
由于胖箭头会共享环绕它的外部代码的this
,我们可以这样改写:
var nate = { name: "Nate", guitars: ["Gibson", "Martin", "Taylor"], printGuitars: function() { this.guitars.forEach( (g) => { console.log(this.name + " plays a " + g); }); }};
可见,箭头函数是处理内联函数的好办法。这也让我们在 JavaScript 中更容易使用高阶函数。
2.6.2 模板字符串
ES6 引入了新的模板字符串语法,它有两大优势:
(1) 可以在模板字符串中使用变量(不必被迫使用+
来拼接字符串);
(2) 支持多行字符串。
字符串中的变量
这种特性也叫字符串插值(string interpolation)。你可以在字符串中插入变量,做法如下:
var firstName = "Nate";var lastName = "Murray";// interpolate a stringvar greeting = `Hello ${firstName} ${lastName}`;console.log(greeting);
注意,字符串插值必须使用反引号,不能用单引号或双引号。
多行字符串
反引号字符串的另一个优点是允许多行文本:
var template = `
Hello
This is a great website
`// do something with `template`当我们要插入模板这样的长文本字符串时,多行字符串会非常有帮助。
2.7 总结
在 TypeScript 和 ES6 中还有很多其他的优秀语法特性,如:
接口
泛型
模块的导入、导出
标注
解构
我们会在本书的后续章节中讲到这些概念并使用它们。目前,本章的这些基本知识已经足够你开始学习 Angular 了。
言归正传,让我们回到 Angular 吧!
编写你的第一个 Angular Web 应用(中)
编写你的第一个 Angular Web 应用(下)
Angular 的工作原理(上)
Angular 的工作原理(下)
内置指令
Angular 中的表单(上)
Angular 中的表单(下)
HTTP(上)
HTTP(下)
路由(上)
路由(中)
路由(下)
依赖注入(上)
依赖注入(下)
Angular 数据架构
使用可观察对象的数据架构 第1部分:服务(上)
使用可观察对象的数据架构 第1部分:服务(中)
使用可观察对象的数据架构 第1部分:服务(下)
使用可观察对象的数据架构 第2部分:视图组件(上)
使用可观察对象的数据架构 第2部分:视图组件(下)
基于 TypeScript 的 Redux 简介(上)
基于 TypeScript 的 Redux 简介(中)
基于 TypeScript 的 Redux 简介(下)
在 Angular 中引入 Redux (上)
在 Angular 中引入 Redux (中)
在 Angular 中引入 Redux (下)
高级组件(上)
高级组件(中)
高级组件(下)
测试(上)
测试(中)
测试(下)
把 AngularJS 应用升级到 Angular(上)
把 AngularJS 应用升级到 Angular(中)
把 AngularJS 应用升级到 Angular(下)
阅读全文: http://gitbook.cn/gitchat/geekbook/5a5826ece286423809d4a847