最近因为要用到angularJS开发项目,因为涉及到的静态资源比较多,所以想把js文件通过requireJS来按需加载,这两个框架以前都使用过,但是结合到一起还没有用过,那就试一下,看能否达到目的。
requireJS是为了实现js文件异步加载和管理模块之间依赖性的框架,详情请看阮一峰 require.js的用法和RequireJS 中文网这里就不做介绍了。
我们先来创建模版容器index.html
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="js/require/require.js" data-main="js/main"></script>
</head>
<body>
<div ng-view></div>
</body>
</html>
唯一的一个js文件引入是require.js文件,这个是requireJS核心文件,这个script标签的属性data-main用来指定requireJS的入口文件,下方div是angularjs的dom容器,这里因为要用到angularjs的手动调用,所以不用在div上指定ng-app属性。
创建requireJS入口文件main.js
(function(){
require.config({
baseUrl : "js",
paths : {
"jquery" : "jquery/jquery-1.12.2.min",
"angular" : "angular/angular",
"angular-route" : "angular/angular-route",
"domReady" : "require/domReady",
"controllerModel" : "controller/controller"
},
shim : {
"angular" : {
exports : "angular"
},
"angular-route" : {
deps : ["angular"],
exports : "angular-route"
}
},
deps : ['app']
});
})();
- baseUrl 用来指定加载模块的目录,后期涉及到路径都以这个目录为相对路径。
- paths 指定模块的加载路径。
- shim 配置不兼容模块
- deps 指定依赖模块,requireJS会加载这个文件并执行。
我们现在的文件目录接口是这样的:js文件目录是这样的
创建angularJS执行文件app.js
接下来我们要创建angularJS模块并且配置路由然后通过内置方法bootstrap来手动触发angularJS。 app.js文件是通过requireJS来动态加载的,所以要按照AMD规范写。
(function(){
define(["angular","angular-route","mainController",'domReady!'],function(angular){
//创建angularJS模块
var app = angular.module("webapp",[
'ngRoute',
'webapp.controllers'
]);
//设置angularJS路由
app.config(function($routeProvider,$locationProvider){
$routeProvider.when("/",{
templateUrl : "tpl/sy.html",
controller : "syCtrl"
}).when("/login",{
templateUrl : "tpl/login.html",
controller : "loginCtrl"
});
$locationProvider.html5Mode(false).hashPrefix("!");
});
//手动触发angularJS
angular.bootstrap(document,['webapp']);
return app;
});
})();
app.js依赖的js模块有
- angular angularJS核心文件
- angular-route angularJS路由文件
- domReady! 这个是requireJS的插件可以让回调函数在页面DOM结构加载完成后再运行。
- mainController 这个是我们接下来要写的控制器
创建模版
在创建控制器前我们先创建下模版,按照上面路由描述,有两个页面一个是sy.html,一个是login.html,创建好这两个模版,并把他们放到tpl文件夹中。
sy.html
<h1>{{data}}</h1>
<a href="javascript:void(0)" ng-click="goLogin()">去login页面</a>
login.html
<h1>{{data}}</h1>
<a href="javascript:void(0)" ng-click="goSy()">去sy页面</a>
创建控制器mainController
在app.js文件中,通过requireJS加载mainController模块,mainController模块可以认为控制器的入口,这里去异步加载所有控制器。
按照路由描述规则,我们需要创建syCtrl和loginCtrl这两个控制器,并且添加到控制器入口文件mainController.js中。
mainController.js
(function(){
define([
'controller/syCtrl',
'controller/loginCtrl'
],function(){
});
})();
接下来我们创建syCtrl和loginCtrl这两个控制器,在创建这两个控制器前,我们先看下angularJS是怎么创建控制器的。
var angularController = angular.module("angularController",[]);
angularController.controller("ctrlName",function(){
});
从上面代码可以看出angularJS的控制器都是先创建angular模块,然后执行当前模块controller方法来绑定控制器,在app.js文件中创建的新模块依赖注入当前模块angularController。
在app.js文件中我们指定依赖的控制器模块是webapp.controllers。我们需要创建一个文件,这个文件是为所有控制器文件提供webapp.controller模块同时把所有控制器都绑定到webapp.controllers模块中,然后app.js文件中设置依赖注入后控制器就可以执行了。 我们创建controller文件夹放到js文件夹中,创建如下文件:
controller.js文件就是我们要创建的控制器模块公用文件,下方两个文件都是单个控制器文件,这两个文件都依赖controller.js提供的模块。
我们在main.js文件中配置controller.js名称是controllerModel所以在需要依赖controller.js都可以直接依赖controllerModel。
controller.js
(function(){
define(['angular'], function (angular) {
return angular.module('webapp.controllers', []);
});
})();
controllerModel模块返回创建的angularJS模块webapp.controllers,app.js文件中放到module方法第二个参数中设定依赖注入。
接下来我们再创建syCtrl.js和login.js文件
syCtrl.js
(function(){
define(['controllerModel'],function(controllers){
controllers.controller('syCtrl',function($scope,$location){
$scope.data = "我是sy";
$scope.goLogin = function(){
$location.path("/login")
}
})
})
})();
login.js
(function(){
define(['controllerModel'],function(controllers){
controllers.controller('loginCtrl',function($scope,$location){
$scope.data = "我是login";
$scope.goSy = function(){
$location.path("/")
}
})
})
})();
这两个控制器都依赖controllerModel,然后调用controllerModel模块的controller方法来创建控制器,剩下内容就是在控制器中绑定数据。
貌似搞砸了
完成上面所有步骤后,这个应用终于完成了,我们在打开页面看下效果
打开首页后是这样的
点击去login页面后跳转到login页面然后点击去sy页面也能跳转到sy页面
功能貌似没有问题,我要的按需加载实现了吗?我们看下文件的加载情况,我需要的功能是在访问sy页面的时候调用syCtrl控制器,访问login页面调用loginCtrl控制器。可惜,在访问首页的时候控制器就全部被加载,这样的结果是必然的,因为我们通过requireJS加载控制器的时候mainController.js文件是将所有的控制器都加载过来的。而mainController.js模块在创建webapp模块的时候加载执行,只执行一次,所以必须将所有控制器加载并且绑定到webapp模块上。
我的本意是将各个控制器分成单独的文件,然后需要加载某个控制器的时候再去调用。在实际开发中,涉及到的模块太多的时候我们希望通过单独的文件来管理单独的模块,然后通过grunt等工具在线下合并成一个文件在生产环境中使用,这样合并后的文件如果很大的话会影响页面的加载速度,如果不合并的话请求数又会太多,所以通过requireJS来异步加载各个模块,我们上面所做的不是没有意义,毕竟我们解决了加载文件太大的问题。
angularAMD 实现按需加载
我们做了很多工作,但是没有解决最根本的问题——按需加载。接下来我们要解决这个问题。 我们用另外一个事例来简单说明angularJS控制器、服务的按需加载,指令的按需加载我认为没有必要,自定义的指令大多作为公用功能来处理,这样的功能本来就是全局的,所以在实创建angular模块的时候指定requireJS依赖关系直接调用就行。
安装
bower install angularAMD
bower install angular-ui-router
bower 会自动加载依赖的js文件,所有文件都放入bower_components文件夹
创建模版文件和js入口文件
index.html
<!DOCTYPE html>
<html>
<head>
<!-- meta -->
<meta charset="utf-8">
<!-- title -->
<title></title>
<!-- script -->
<script data-main="main.js" src="bower_components/requirejs/require.js"></script>
</head>
<body>
<!-- content -->
<div ui-view></div>
</body>
</html>
模版文件和上次创建的模版文件一样,都是通过requireJS入口文件来处理接下来的工作。
main.js
(function(){
require.config({
paths: {
"angular": "bower_components/angular/angular",
"angular-ui-router": "bower_components/angular-ui-router/release/angular-ui-router",
"angularAMD": "bower_components/angularAMD/angularAMD",
"ngload": "bower_components/angularAMD/ngload"
},
shim: {
"angular": { exports: "angular" },
"angular-ui-router": ["angular"],
"angularAMD": ["angular"],
"ngload": ["angularAMD"]
},
deps : ["app"]
});
})();
main.js文件的内容还是和以前一样,配置好各个模块的url,并且指定依赖模块app.js文件,app.js是创建angularJS模块的入口。
接下来我们创建app.js文件
app.js
(function(){
define(["angular", "angularAMD", "angular-ui-router"], function (angular, angularAMD) {
//设置路由
var registerRoutes = function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/home");
$stateProvider
// home
.state("home", angularAMD.route({
url: "/home",
templateUrl: "home.html",
controllerUrl: "home.js"
}))
// home
.state("about", angularAMD.route({
url: "/about",
templateUrl: "about.html",
controllerUrl: "about.js"
}))
;
};
//实例化angularJS
var app = angular.module("app", ["ui.router"]);
//配置
app.config(["$stateProvider", "$urlRouterProvider", registerRoutes]);
//手动启动angularJS 并返回angularJS实例对象
return angularAMD.bootstrap(app);
});
})();
我们这里创建的app.js文件和前文说道的app.js文件功能一样,唯一的区别是依赖的模块内容变化,同样在使用对应模块的时候也是不一样的,但是实现的功能是一样的。
同时这里需要注意,配置路由的时候,不光要指定ur和模版url还要指定每个模版对应的控制器的url
没错,这样简单的配置就可以实现控制器的按需加载。我们访问home页面angularJS会自动加载home.html模版和home.js控制器,加载完成后的执行方式和前边是一样的。
添加模版文件home.html和控制器文件home.js
home.html
<h1>{{ title }}</h1>
<br/>
<button ui-sref="about">About</button>
home.js
(function(){
define([], function () {
return ["$scope", function ($scope) {
$scope.title = "This is Home page";
}];
});
})();
home.js是home控制器,可以看到写法和方法的sy控制器、login控制器都不一样,首先通过requrieJS来确定依赖模块,然后在回调函数返回数组,数组的最后一个元素是控制器的执行函数,前边的都是控制器需要加载的服务,其实返回值就是创建控制器函数controller的第二个参数,第一个参数是控制器名称,这个应该是内部自动指定了。
这样我们第一个路由就创建好了,访问页面如图
添加about模版文件about.html和控制器about.js
about.html
<h1>{{ title }}</h1>
<h1>{{ user.name}}</h1>
<br/>
<button ui-sref="home">Home</button>
about.js
(function(){
define([], function () {
return ["$scope", function ($scope) {
$scope.title = "This is About page";
$scope.user = "tudou";
}];
});
})();
about.js和html.js内容一样,不做解释了。
控制器按需加载了
访问home页面js资源加载情况如下前面的文件是依赖模块加载,home.js和home.html是当前页面需要的模版和资源,没有加载about页面的内容,我们点击按钮去about页面看下。可以看到在about页面新加载了两个文件about.js和about.html
我们controller的按需加载实现了,接下来我们要实现服务的按需加载
服务按需加载
创建服务UserRepository.js文件
(function(){
define(["app"],function(app){
app.factory("UserRepository", function(){
return {name:"tudou"};
});
});
})();
服务的创建依赖angularJS模块,上文是在controller/controller.js中创建了一个专门针对控制器的angularJS模块,这次我们直接调用app.js返回的angularJS模块,原理和上文说到的一样。 执行模块的factory方法申明一个服务,这个服务就生成了。
调用服务
其实在创建about.js和home.js时我们已经调用了$scope服务,不过$scope服务是angularJS内置的服务,所以我们要调用自定义服务需要先加载文件,然后在申明调用的服务传递到控制器里面就可以直接使用了。 在define方法的第一个参数中声明依赖的js文件,在回调函数返回的数组中申明服务,about.js修改成下方代码
(function(){
define(["UserRepository"], function (UserRepository) {
return ["$scope", "UserRepository",function ($scope,UserRepository) {
$scope.title = "This is About page";
console.log(UserRepository);
$scope.user = UserRepository;
}];
});
})();
这样我们angularJS控制器和服务的按需加载就完成了。还有过滤器的模块化应该和服务是一样的。