从ng1到ng2的平滑升级

如果你想了解如何将现有的AngularJs 1.X(简称ng1)的项目平滑升级至Angular 2.X(简称ng2),这篇文章或许会对你有帮助。

本文假设你原有的ng1项目有如下属性:

使用的ng1版本为1.2.X+;

使用的Javascript版本为es3或少量es5特性;

暂时不考虑其他外部依赖,例如第三方ng1插件等;

如果你有一些typescript的知识,阅读本文将会更加轻松。

本文的目标是:

将ng1升级至ng2,为了实现平滑升级,我们还将尽量在升级过程中把步子迈小,同时使升级后的代码兼容ng1,从而在升级过程中不影响当前项目的正常开发和发布;

将Javascript升级至Typescript,平滑升级过程中还可以生成兼容ng1的typescript版本代码;

尽量尝试使用最佳实践。

让我们开始吧!

我们先来定义一个在本文中一直会使用到的ng1项目基础代码,后面的代码实例都基于这个基础代码开发:

// app.js/* 定义一个ng1 module,并假设该module为ng1项目的启动module,

例如在html标签中添加 ng-app="app"

*/var app = angular.module('app', []);

这个项目至少还要有一个controller:

// index.controller.js

/* 一个简单的controller */

app.controller('IndexCtrl', ['$scope', function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

}]);

我们还要有一个html页面:


...

...

这样,一个最简单的ng1项目就完成了。

我们从最常使用的controller开始下手。首先回顾一下刚才定义的controller:

// index.controller.js

/* 一个简单的controller */

app.controller('IndexCtrl', ['$scope', function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

}]);

仔细查看代码,我们可以发现,我们定义controller的方式使用的是angular.Module.controller方法,这样的定义方式与ng1完全耦合在一起,无法脱离ng1的依赖,因此,第一步我们现在将controller实体function从该方法中剥离出来。

1. 分离controller的实体function

分离的方式很简单,我们定义一个function变量,再将该function变量写在angular.Module.controller方法中:

// index.controller.js

/* 一个简单的controller */

app.controller('IndexCtrl', ['$scope', indexCtrl]);

var indexCtrl = function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

};

但是这样做还是不够,因为Index.controller.js文件中还是调用了app.controller方法,这个方法是ng1专有的,因此这个文件还是在依赖ng1,因此我们可以将所有使用app.controller方法注册controller的代码集中到一个app-controller.js中。

这样,我们将index.controller.js文件拆分成两个文件:

// app-controller.js

app.controller('IndexCtrl', ['$scope', indexCtrl])

.controller('LoginCtrl', ['$scope', loginCtrl])

.controller(...);

// index.controller.js

var indexCtrl = function ($scope) {

$scope.info = 'hello world';

console.log($scope.info);

};

修改之后的代码就清晰多了,app-controller.js专门负责注册项目中所有用到的controller,index.controller.js以及其他的类似login.controller.js等专门负责完成controller中的业务逻辑和交互逻辑。

这里有一个问题要注意,因为app-controller.js中要调用indexCtrl这个变量,因此一定要在调用之前定义这个变量,否则调用时indexCtrl为undefined。避免这个问题的方法,一种是在html中引用js文件时首先引用定义indexCtrl的js文件,最后引用app-controller.js文件;第二种是使用typescript中的模块管理功能管理相互之间的依赖。本文最终会形成typescript版本的代码,所以这个问题仅在改动过程中出现。

2. 不再使用丑陋的$scope

index.controller.js中的代码使用了$scope,而$scope是使用DI的方式注入到方法中,因此这里并没有对ng1产生直接的依赖,这也是依赖注入的一大好处。

但是controller方法中到处充满了$scope,不得不说代码会显得很混乱和丑陋。如果你对ng2有一些了解,那么应该知道ng2中是没有$scope概念的。为了升级至ng2,我们从现在开始对$scope说NO!

ng1中提供了一种可以避免使用$scope的方式,那就是as语法,我们先来看一下如何使用:


...

...

// index.controller.js

var indexCtrl = function () {

this.info = 'hello world';

console.log(this.info);

};

// app-controller.js

app.controller('IndexCtrl', [indexCtrl])

.controller('LoginCtrl', [loginCtrl])

.controller(...);

使用as语法,相当于在$scope中定义了一个属性$ctrl,并将这个属性指向indexCtrl方法作用域的this上,具体可以参见官方文档(https://docs.angularjs.org/api/ng/directive/ngController)。

因此,使用as语法后,controller方法就可以使用this来操作其中的变量,这样几乎与ng2中Component类的操作方法一致,在注册controller时也不用注入$scope依赖了。

同时在html中,调用controller方法中的变量时,需要使用$ctrl.foobar来实现(因为$ctrl为$scope的一个属性)。

这里使用的$ctrl属性可以根据需要调整名称,如ctrl、indexCtrl等,但统一使用$ctrl是最佳实践,一方面可以在所有html的controller中统一使用相同的属性名调用controller中的变量,另一方面使用$前缀可以作为ng1的一个标记,避免出现变量名冲突的情况。

这时我们再重新观察一下index.controller.js文件,它已经完全纯净,只是一个非常普通的方法,一点ng1的影子都看不出来了:

// index.controller.js

var indexCtrl = function () {

this.info = 'hello world';

console.log(this.info);

};

3. 向typescript进发

我们现在的js代码还停留在es3阶段,要靠近ng2,我们还需要使用typescript。

升级至typescript后,需要使用typescript compiler(http://www.typescriptlang.org/)编译为es3或es5版本。

注意:虽然代码升级为typescript,但仍然可以在编译后兼容ng1。

这里,我们使用typescript中的class来定义indexCtrl:

// index.controller.ts

// 为变量增加类型声明,同时也就不用再判断变量类型了

export default class IndexCtrl {

info : string = 'hello world';

constructor () {

console.log(this.info);

}

// 这里我们为controller添加一个可以在html中调用的方法

hasInfo () : boolean {

// 使用了typescript,这里就可以省略类型判断了

return this.info.length > 0;

}

}

我们使用以下几种方式实现IndexCtrl:

之前使用this定义的变量,我们在class的最顶端使用foo = 'bar'的方式声明为类属性;

初始化工作在class中的constructor中完成;

需要在html中调用的方法使用foo () {}的方式生命为类方法。

代码中export default使用了typescript中的模块管理功能,将类导出,并可以在其他文件中调用。

类似的,我们将其他文件也做相应的优化:

// app.ts

export default angular.module('app', []);

// app-controller.ts

// 这里使用import调用其他模块,从此就不用担心变量是否已定义的问题啦

import app from './app';

import IndexCtrl from './index.controller';

import LoginCtrl from './login.controller';

...

app.controller('IndexCtrl', [indexCtrl])

.controller('LoginCtrl', [loginCtrl])

.controller(...);

// 这个文件不需要其他文件调用,因此不需要export模块

...

 
   

ng-if="$ctrl.hasInfo()">  ...

虽然到此为止,代码在typescript下编译完成后,仍然可以生成兼容ng1的es3版本js代码。

4. 根据最佳实践进一步优化

这里有一些最佳实践,我们可以用来参考。

最佳实践:我们建议在constructor中,只对一些变量的值进行初始化,不执行具体的业务逻辑(例如本文中的console.log(this.info)),而将涉及到业务逻辑的初始化,则放到单独的初始化方法中完成。

这个最佳实践也正符合ng2的理念,在ng2中,每个component都有一系列生命周期方法,并通过ng2框架自动调用运行,我们涉及到业务逻辑的初始化工作在这里拆分到单独的初始化方法中,将更方便我们的代码升级至ng2:

// index.controller.ts

// 接下来优化typescript版本

export default class IndexCtrl {

info : string = 'hello world';

constructor () {

this._onInit();

}

// typescript中,类的方法默认为public类型,因此不需要显式声明

hasInfo () : boolean {

return this.info.length > 0;

}

// 这里我们将初始化方法同意命名为_onInit

// 这里用typescript中的private关键字将方法声明为私有方法

private _onInit () : void {

console.log(this.info);

}

}

最佳实践:初始化方法并不需要在网页中或其他模块中调用,因此我们将其设定为私有方法(以_开头),并写在所有公开方法后面。

5. 升级为ng2

到此为止,虽然我们对项目的修改已经如此巨大,但它依然可以兼容ng1(在typescript compiler帮忙编译的情况下),而代码却已经变得整洁而先进,对你现有的项目的运行完全没有任何影响。

准备工作做的这么充分,升级至ng2就指日可待啦:

// index.component.ts

// ng2中没有controller概念,我们将controller改造为component

import { Component, OnInit } from '@angular/core';

@Component({

selector: 'index-ctrl',

template: '{{ info }}'

})

export class IndexComponent implements OnInit {

info : string = 'hello world';

constructor () {

// 初始化方法为生命周期方法,由ng2框架自动调用,这里不需要手动调用

}

hasInfo () : boolean {

return this.info.length > 0;

}

// 这里用ng2中的生命周期方法

ngOnInit () : void {

console.log(this.info);

}

}

改到这里,index.component.ts的ng2升级就完成了,我们可以看到,除了添加一些annotation以外,component的核心代码几乎没有变动。

这说明经过优化的、完全兼容ng1的typescript版本核心代码(也就是此步骤之前的所有重构工作),可以直接升级至ng2,之要添加一些必要的annotation就可以了。有木有很平滑!

ngModule的ng2升级就留给你做练习吧。

原文:http://mp.weixin.qq.com/s?__biz=MzI5MDM2NjY5Nw==&mid=2247483843&idx=1&sn=90a1ac29272a0791464c934857ba0ee7&chksm=ec21b445db563d539ce014a1309cca36ed9c4c4d964fa4003a01f23396ea3da915f53107b883&mpshare=1&scene=1&srcid=1112L0HzskOj2DolrEDLs0vI#rd

(未完待续)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,914评论 25 707
  • 通过AngularJS仿豆瓣一刻的案例:https://github.com/zhongxiaolian/doub...
    中小恋阅读 1,753评论 1 21
  • AngularJS是什么?AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是...
    200813阅读 1,592评论 0 3
  • 下回还吃吗! 吃~
    Asue不2阅读 176评论 0 0