最近公司用webpack和angular2来开发手机app页面,发现ES6的写法很是方便和清晰,因而我想到在angular1中也能够用ES6语法写,webpack来进行打包编辑。因而我查阅了相关资料以后终于实现了ES6语法来写angular1。css
关于webpack的配置,网上有许多的教程,在github上也有至关多的项目例子能够借鉴,我这就不详细说webpack的配置了。若是你是新手,我推荐webpack的官网,若是你对英文不感冒你能够查看webpack的中文文档学习相关知识。固然若是你实在对webpack的配置烦恼的话,我推荐GitHub上AngularClass组织的angular-starter项目,这是webpack配置angualr2的例子,它里面的配置很是详细,对每一个插件的使用有相关的说明,对学习webpack的配置颇有帮助,本项目也是参照该例子,主要的目录结构以下。html
angular-webpack/ ├──config/ * 配置文件目录 │ ├──helpers.js * helper 方法用来获取目录以及一些相关配置参数 │ ├──webpack.common.js * webpack 通用的配置信息 │ ├──webpack.dev.js * webpack 配置开发环境文件 │ ├──webpack.prod.js * webpack 配置生产环境文件 │ └──webpack.test.js * webpack 配置测试环境文件 ├──src/ * web文件的主目录 │ ├──app.module.js * angularjs 主module │ │ │ ├──index.html * 项目的主入口 │ │ │ ├──app/ * 文件主目录 │ │ ├──config/ * angularjs的config文件 │ │ │ │ │ ├──router/ * angularjs的路由配置 │ │ │ │ │ └──views/ * angularjs的页面 │ │ │ └──style/ * 项目的样式文件 │ ├──package.json * npm 配置文件 └──webpack.config.js * webpack 主配置文件
对一个项目来讲,目录组织是很是重要的,它能让你一目了然的了解项目信息。在angularjs的项目中,有不一样的目录搭建,对于有些人喜欢把module、controller、directive、service等文件分别放在module、controller、directive、service目录下统一管理。html页面统一放在HTML目录下它的目录结构看起来是这样的:webpack
app/ ├──controller/ │ ├──*.controller.js │ ├──*.controller.js │ ├──*.controller.js │ │.... ├──directive/ │ ├──*.directive.js │ ├──*.directive.js │ │.... ├──html/ │ ├──*.html │ ├──*.html │ │.... ├──module/ │ ├──*.module.js │ ├──*.module.js │ │.... │ ├──service/ │ ├──*.service.js │ ├──*.service.js │ │....
这一种目录结构相对来讲不是很好,尤为是对模块化打包来讲结构很不清楚,不利于模块化,页面一多很难管理,另外一种目录结构是把每个相关的页面controller、service、module、html放到对应的页面目录下,它的结构是这样的:git
app/ ├──login/ │ ├──login.module.js │ ├──login.controller.js │ ├──login.directive.js │ ├──login.router.js │ ├──login.html │ ├──login.css │ ├──home/ │ ├──home.module.js │ ├──home.controller.js │ ├──home.directive.js │ ├──home.router.js │ ├──home.html │ ├──home.css ├──other/ │ ├──other.module.js │ ├──other.controller.js │ ├──other.directive.js │ ├──other.router.js │ ├──other.html │ ├──other.css
这样的目录结构有利于模块的移植。angularjs
在ES6中有了新方法:Class来声明类,class写法看起来更简洁,方便。在angular1中把function方法定义的controller,service等修改成Class声明,具体各修改方式以下:github
ES6关于angualr的module写法没有大的变化,注意ES6中须要对angular模块导入以后才能使用angular,其余模块的注入也是同样的。web
//--------ES6以前 angular.module('webapp',['oc.lazyLoad',]); //--------ES6 import angular from 'angular'; //---- 或者 var angular = require('angular'); require('oclazyload'); angular.module('webapp',['oc.lazyLoad',]);
<div ng-controller='loginController as vm'> <input type="tel" placeholder="手机号" ng-model="vm.username"> <input type="password" placeholder="密码" ng-model="vm.userPassword"> <input type="button" value="登陆" ng-click="vm.login()"> </div>
login.controller.jsnpm
//ES5 controller的通常写法 LoginController.$inject = ['$state', 'loginService']; ngModule.controller('loginController', LoginController); function LoginController($state, loginService) { var vm = this; vm.login = login; function login() { if (!vm.username) { alert('请输入手机号'); return; } if (!vm.userPassword) { alert('请输入密码'); return; } loginService.login({ username: vm.username, userPassword: vm.userPassword }).then(function(res){ $state.go('page'); }, function(res){ alert('登陆失败'); }) } } //------------------ES6----------------------- class LoginController { constructor($state, loginService) { this.loginService = loginService; //把依赖注入的方法放到this中,变为this的属性,使用this."property"来调用该变量 //与以前比最大的不一样是依赖注入不在是该函数内的“全局变量” this.$state = $state; } login() { if (!this.username) { alert('请输入手机号'); return; } if (!this.userPassword) { alert('请输入密码'); return; } this.loginService.login({ username: this.username, userPassword: this.userPassword }).then((res) => { this.$state.go('page'); }, (res) => { alert('登陆失败'); }) } } LoginController.$inject = ['$state', 'loginService']; ngModule.controller('loginController', LoginController);
注意在function声明中LoginController.$inject = ['$state', 'loginService']不论是放在function前面仍是后面都不会有影响,可是在class声明中LoginController.$inject = ['$state', 'loginService']只能放在class后面定义,主要缘由是function会在做用域中进行提高而class不会。json
angular中service的写法与controller类似,能够说没有大区别数组
login.service.js
//ES5 service写法 LoginService.$inject = ['$http']; function LoginService($http){ var self=this; self.login=login; function login(params) { /*return $http({ url: '****!/!***!/login', method: 'GET', params: params })*/ /*用ES6 Promise方法模仿$http,也可用angular中的$q实现*/ return new Promise(function (resolve, reject) { if (params) { resolve('success'); } else { reject('fail'); } }); } } //------------------ES6----------------------- class LoginService { constructor($http) { this.$http = $http; } login(params) { /*return this.$http({ url: '****!/!***!/login', method: 'GET', params: params })*/ /*用ES6 Promise方法模仿$http,也可用angular中的$q实现*/ return new Promise((resolve, reject) => { if (params) { resolve('success'); } else { reject('fail'); } }); } } LoginService.$inject = ['$http']; ngModule.service('loginService', LoginService);
//------------------ES6----------------------- class LoginProvider { constructor($http) { this.$http = $http; } $get(){ let that=this; login(params) { return that.$http({ //注意this的指向问题 url: '****!/!***!/login', method: 'GET', params: params }) } return { login:login; } } } LoginProvider.$inject = ['$http'] ngModule.provider('LoginProvider', LoginProvider);
注意,在provider中须要定义$get函数
关于factory的写法稍微与前面的不一样,不少人想固然的会这和service同样写factory
class LoginFactory { constructor($http) { this.$http = $http; } login(params) { ... ... ... } } LoginFactory.$inject = ['$http']; ngModule.factory('LoginFactory', LoginFactory); //TypeError: Cannot call a class as a function
class的定义是类似的,可是在module引入factory时若是按上面的写法写就会报TypeError: Cannot call a class as a function错误
可能有人会很奇怪,为何controller,service,provider能够,可是到了factory就会错误。这个和factory的机制有关。编译器转ES6语法到ES5后class会变成这样:
var Loginfactory= function () { function Loginfactory($http) { _classCallCheck(this, Loginfactory); this.$http= $http; } _createClass(Loginfactory, [{ key: 'login', value: function login() { } }]); return Loginfactory; }();
关键是_classCallCheck方法,用来判断this是否为Loginfactory的实例,可是在factory中,angular内部更改了factory的返回对象,使this不在是factory的实例,因此会报错。咱们须要手动new factory()方法,应该改为这样:
//LoginFactory.$inject = ['$http'];不须要了 ngModule.factory('LoginFactory', ['$http',($http)=>new LoginFactory($http)]);
注:controller,service,provider也能够按factory的注入方法写
但是这么写若是有好多依赖要注入,会使ngModule.factory('LoginFactory', ['$http'....,($http,...)=>new LoginFactory($http,...)])看起来很是长并且不易阅读,怎样才能像controller同样以LoginFactory.$inject = ['$http']注入,能够先获取Class上的$inject的注入数组,在由该数组和Class的实例组成['a','b',function(a,b){}]样式,具体实现以下:
function constructorFn(configFn) { let args = configFn.$inject || []; let factoryFunction = (...args) => new configFn(...args); /** * 主要检测controller、directive的注入 * service、factory、provider、config在程序运行时只运行一次 * controller、directive每次页面载入时会从新注入,须要把上一个注入的function从数组中移除 * */ if(typeof args[args.length-1]==='function'){ args.splice(-1,1); } /** * args.push(factoryFunction)相似于['a',function(a){}] * */ return args.push(factoryFunction) && args; //return args; } LoginFactory.$inject = ['$http']; ngModule.factory('LoginFactory', constructorFn(LoginFactory));
在写factory时我还发现一个小问题,在写angular的Http拦截器时候会出现意料以外的状况
export class HttpInterceptor { constructor($q, $location) { this.$q = $q; this.$location = $location; } request(config){ console.log(this.$q); //error //this对象不是HttpInterceptor的实例而是undefined return config; } } HttpInterceptor.$inject = ['$q', '$location'];
在调用request、requestError等方法时,this指向丢失了,应该是angular在调用时,this对象被修改了。若是你在request、requestError等方法中没有用到this对象,能够这么写。若是用到this对象,则须要变换一下:
export class HttpInterceptor { constructor($q, $location) { this.$q = $q; this.$location = $location; this.request=(config)=>{ console.log(this.$q); //success return config; }; } } HttpInterceptor.$inject = ['$q', '$location'];
注意,this指向更改可能出现的状况不止在这里,须要当心
directive写法稍有不一样,因为directive须要返回一个包含restrict、template 、scope 等属性的对象,因此须要在constructor中定义这些熟悉,还有directive会有link,compile等函数方法
//vm.title <my-page page-title='vm.title'></my-page>
//------------------ES6----------------------- class PageDirective { constructor($timeout) { this.restrict = "EA"; this.template = `{{vm.name}} Hello {{vm.pageTitle}}`; this.controller = PageDController; this.controllerAs = "vm"; this.bindToController = true; this.scope = { pageTitle:'=' }; this.$timeout=$timeout; } link(scope,element,attr){ this.$timeout(()=>scope.vm.name='',1000); } } PageDirective.$inject = ['$timeout']; class PageDController{ constructor(){ this.name='pageDirective' } } ngModule.directive('myPage', constructorFn(PageDirective));
webpack打包时会把HTML和js代码都放到一个js文件中,使这个文件很是大,致使打开页面加载会很慢,影响用户体验。因此咱们须要把HTML以及部分js代码抽出放到块文件中。幸运的是咱们能够用oclazyload配合webpack进行anglar路由的按需加载,oclazyload的官方文档有详细的说明和例子,我这边列了一个简单的例子:
路由配置
login.router.js
import angular from 'angular' //import LoginModule from './login.module' { state: 'login', url: '/login', controller: 'loginController', controllerAs: 'vm', /*template: require('./login.html'),*/ templateProvider: ['$q', ($q)=> { //templateProvider来动态的加载template let defer = $q.defer(); //require.ensure:webpack的方法在须要的时候才下载依赖的模块, require.ensure([''], () => { //只有require函数调用后再执行下载的模块 let template = require('./login.html'); //require调用后加载login页面 defer.resolve(template); }); return defer.promise; }], title: '登陆', resolve: { deps: ['$q', '$ocLazyLoad', ($q, $ocLazyLoad)=> { let defer = $q.defer(); require.ensure([], ()=> { /** *let module = LoginModule(angular); *注意import导入LoginModule方法与require('./login.module')直接引用LoginModule方法是有区别的, *import导入LoginModule方法不能分离js */ let module = require('./login.module').LoginModule(angular); //动态加载Module $ocLazyLoad.load({ name: 'login' //name就是你module的名称 }); defer.resolve(module); }); return defer.promise; }], }
login.module.js
import {loginControllerFunc} from "./login.controller"; import {loginServiceFunc} from "./login.service"; export function LoginModule(Angular) { const loginModule = Angular.module('login', []); //login名称就是$ocLazyLoad.load中的name; loginControllerFunc(loginModule); //注入模块controller loginServiceFunc(loginModule); //注入模块service }
注:$ocLazyLoad动态加载的module不须要在其余模块中引入,如angular.module('webapp',['login']),这是错误的
到这里用ES6构建anguar1项目就基本告一段落了。若是有更好的ES6写法,欢迎各位评说。
文章有不对的地方,望大神们指出。
项目地址请戳这里