最近因為要用到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控制器和服務的按需加載就完成了。還有過濾器的模塊化應該和服務是一樣的。