我最近在试玩 Angular 2。刚开始感觉很奇怪,和我们钟爱的第 1 版完全不同。第 1 版是用 ES5 标准的纯 javascript 编写,而第 2 版采用了 typescript 和 es 2015。不过,你已经可以采取一些步骤,让你的 Angular 1 代码(或用 Angular 1 创建的新项目)更加接近 Angular 2。
我为什么要为 Angular 1 迁移到 Angular 2 做准备
首先,当时机成熟了,你打算用 Angular 2 作为框架时,肯定想让代码迁移更容易些。目前,Angular 小组已经提供了一些迁移策略,你可以混合使用 Angular 1 和 Angular 2 组件,但目标是要将代码库统一,最终只使用一个框架。
其次,在 Angular 2 中更多的是写纯 javascript,然后才是使用专有的框架代码。
再次,社区和浏览器厂商将逐步拥抱 Ecmascript 的最新标准,所以,坚持使用标准编码,尽可能让代码库可复用,而不管选择的框架是什么。
迁移到 Angular 2 的步骤
采取这些策略可以让你的代码更加接近 Angular 2,使转换变得容易。
1. 开始用 Ecmascript 2015
Angular 2 使用 Typescript 编写,Typescript 是 Ecmascript 2015 的超集,带有更多的特性。不过,如果你不喜欢 Typescript, 也可以只用 Ecmascript 2015 编写 angular 2。 目前,代码最终都会编译成 Ecmascript 5。所以实际上你也可以用 Ecmascript 5 来编写 Angular 2。
但是,在我看来,使用 Ecmascript 2015 的新特性,可以减少代码量(有些时候…)、增强代码可读性、用上令人兴奋的特性,如解构。
如果想使用 Ecmascript 2015 的特性,你需要一个转换器来编译代码。目前最流行的转换器是 babel。babel 在很多流行的构建脚本中都可以配置,如 gulp、webpack、browserify 及其它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 对象属性增强 var exports = { search: search, setType: setType, setDuration: setDuration }; // 可以写成这样 var exports = { search, setType, setDuration }; ///////////// // 使用“胖箭头” => 可以简化代码并增强可读性 var videoIds = response.data.items.map(function(video){ return video.id[idPropertyName[activeType]]; }).join(','); // 使用了胖箭头符号 var videoIds = response.data.items.map((video) => { return video.id[idPropertyName[activeType]]; }).join(','); |
2. 使用 “angular.service” 替换 “angular.factory”
使用 Ecmascript 2015 意味着我们可以用新的 “class” 关键字来创建新对象甚至扩展其它对象。我曾经写过,比起继承我更热衷于组合,所以我看不出用 “extend” 实现继承有什么用处,不过通过 class 的特性的确可以为创建对象增加好用的语法糖(简化代码)。
在 angular 1 中的 “service” 和 “factory” 的区别是实例化方法:
“service” 使用 “new” 关键字调用(仅一次)
“factory” 使用普通函数调用 — 不需要 “new” 关键字。
在 Angular 2 中,Services 使用了 Ecmascript 2015 类编写。这会导致你需要将 Angular 1 代码中的 factories 转化成 services,并且使用 “class” 替代 function。
例如在我的开源项目-Echoes Player 中,我使用了“class” 和 Angular“service” 编写 youtube api 服务(我以前用的是 factory):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
(function() { 'use strict'; /* @ngInject */ class YoutubePlayerApi { /* @ngInject */ constructor ($window, $q) { /*jshint validthis: true */ this.deferred = $q.defer(); //当 API 准备好时,Youtube 回调 $window.onYouTubeIframeAPIReady = () => { this.deferred.resolve() }; } // 注入 YouTube 的 iFrame API load () { let validProtocols = ['http:', 'https:']; let url = '//www.youtube.com/iframe_api'; // 我们愿意使用相关的 url 协议,但为避免协议不可用,还是回退到 ‘http:’ if (validProtocols.indexOf(window.location.protocol) < 0) { url = 'http:' + url; } let tag = document.createElement('script'); tag.src = url; let firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); return this.deferred.promise; } } angular .module('youtube.player') .service('YoutubePlayerApi', YoutubePlayerApi); })(); |
3. 编写 Controllers 时使用 “Class” 替换 “function”
这一步和上一步多少有些相似。Angular 1 中的 Controllers 总是不停被重建(或 “新建”)- 因为它们不是单例。
Angular 2 几乎不用 controllers。
反之,Angular 2 是基于组件的。每个组件都有一个简单的类(包含少量 es7 注解)来控制。如果你的 Angular 1 代码是用 web 组件方式来编写的,那么很可能每个指令(directive)都对应一个 controller 函数来控制。
有个很重的点必须意识到 - 指令的概念在 Angular 2 中更加简单:
- 使用了元素选择器的指令都是组件。
- 剩下的都是指令。
Angular 2 仍然会在内部初始化 services 和 controllers,不要自己去初始化,因为那样会导致代码很难测试。不过 Angular 2 还是易于测试并对 TDD (测试驱动开发) 和 BDD(行为驱动开发)友好。我还写过一篇文章,内容是讲为什么应当封装 “new” 关键字,从而写出更容易测试的代码。
例如把 controller 写成类,会使代码迁移到 angular 2 组件变得非常容易:
我最近在试玩 Angular 2。刚开始感觉很奇怪,和我们钟爱的第 1 版完全不同。第 1 版是用 ES5 标准的纯 javascript 编写,而第 2 版采用了 typescript 和 es 2015。不过,你已经可以采取一些步骤,让你的 Angular 1 代码(或用 Angular 1 创建的新项目)更加接近 Angular 2。
我为什么要为 Angular 1 迁移到 Angular 2 做准备
首先,当时机成熟了,你打算用 Angular 2 作为框架时,肯定想让代码迁移更容易些。目前,Angular 小组已经提供了一些迁移策略,你可以混合使用 Angular 1 和 Angular 2 组件,但目标是要将代码库统一,最终只使用一个框架。
其次,在 Angular 2 中更多的是写纯 javascript,然后才是使用专有的框架代码。
再次,社区和浏览器厂商将逐步拥抱 Ecmascript 的最新标准,所以,坚持使用标准编码,尽可能让代码库可复用,而不管选择的框架是什么。
迁移到 Angular 2 的步骤
采取这些策略可以让你的代码更加接近 Angular 2,使转换变得容易。
1. 开始用 Ecmascript 2015
Angular 2 使用 Typescript 编写,Typescript 是 Ecmascript 2015 的超集,带有更多的特性。不过,如果你不喜欢 Typescript, 也可以只用 Ecmascript 2015 编写 angular 2。 目前,代码最终都会编译成 Ecmascript 5。所以实际上你也可以用 Ecmascript 5 来编写 Angular 2。
但是,在我看来,使用 Ecmascript 2015 的新特性,可以减少代码量(有些时候…)、增强代码可读性、用上令人兴奋的特性,如解构。
如果想使用 Ecmascript 2015 的特性,你需要一个转换器来编译代码。目前最流行的转换器是 babel。babel 在很多流行的构建脚本中都可以配置,如 gulp、webpack、browserify 及其它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// 对象属性增强 var exports = { search: search, setType: setType, setDuration: setDuration }; // 可以写成这样 var exports = { search, setType, setDuration }; ///////////// // 使用“胖箭头” => 可以简化代码并增强可读性 var videoIds = response.data.items.map(function(video){ return video.id[idPropertyName[activeType]]; }).join(','); // 使用了胖箭头符号 var videoIds = response.data.items.map((video) => { return video.id[idPropertyName[activeType]]; }).join(','); |
2. 使用 “angular.service” 替换 “angular.factory”
使用 Ecmascript 2015 意味着我们可以用新的 “class” 关键字来创建新对象甚至扩展其它对象。我曾经写过,比起继承我更热衷于组合,所以我看不出用 “extend” 实现继承有什么用处,不过通过 class 的特性的确可以为创建对象增加好用的语法糖(简化代码)。
在 angular 1 中的 “service” 和 “factory” 的区别是实例化方法:
“service” 使用 “new” 关键字调用(仅一次)
“factory” 使用普通函数调用 — 不需要 “new” 关键字。
在 Angular 2 中,Services 使用了 Ecmascript 2015 类编写。这会导致你需要将 Angular 1 代码中的 factories 转化成 services,并且使用 “class” 替代 function。
例如在我的开源项目-Echoes Player 中,我使用了“class” 和 Angular“service” 编写 youtube api 服务(我以前用的是 factory):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
(function() { 'use strict'; /* @ngInject */ class YoutubePlayerApi { /* @ngInject */ constructor ($window, $q) { /*jshint validthis: true */ this.deferred = $q.defer(); //当 API 准备好时,Youtube 回调 $window.onYouTubeIframeAPIReady = () => { this.deferred.resolve() }; } // 注入 YouTube 的 iFrame API load () { let validProtocols = ['http:', 'https:']; let url = '//www.youtube.com/iframe_api'; // 我们愿意使用相关的 url 协议,但为避免协议不可用,还是回退到 ‘http:’ if (validProtocols.indexOf(window.location.protocol) < 0) { url = 'http:' + url; } let tag = document.createElement('script'); tag.src = url; let firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); return this.deferred.promise; } } angular .module('youtube.player') .service('YoutubePlayerApi', YoutubePlayerApi); })(); |
3. 编写 Controllers 时使用 “Class” 替换 “function”
这一步和上一步多少有些相似。Angular 1 中的 Controllers 总是不停被重建(或 “新建”)- 因为它们不是单例。
Angular 2 几乎不用 controllers。
反之,Angular 2 是基于组件的。每个组件都有一个简单的类(包含少量 es7 注解)来控制。如果你的 Angular 1 代码是用 web 组件方式来编写的,那么很可能每个指令(directive)都对应一个 controller 函数来控制。
有个很重的点必须意识到 - 指令的概念在 Angular 2 中更加简单:
- 使用了元素选择器的指令都是组件。
- 剩下的都是指令。
Angular 2 仍然会在内部初始化 services 和 controllers,不要自己去初始化,因为那样会导致代码很难测试。不过 Angular 2 还是易于测试并对 TDD (测试驱动开发) 和 BDD(行为驱动开发)友好。我还写过一篇文章,内容是讲为什么应当封装 “new” 关键字,从而写出更容易测试的代码。