Angular-ui-router + oclazyload + requirejs实现资源随route懒加载

来自ling
跳转至: 导航搜索

问题

目前大部分AngularJS的应用用requirJS组织模块,但是很多都没有使用lazyload功能,在app.js中启动时将全部依赖加载进来,在模块功能较少,前端资源少的情况下没问题。那么问题来了,依赖资源过多时怎么办?

build时利用grunt-contrib-requirejs提取合并文件,减少http请求,但是存在问题:build后文件大;线上调试不方便,尤其在html2js后问题更明显。 开发中尽量在原有文件里进行新业务添加,不增加额外的http请求压力,但是存在问题:多人合作时需要经常处理代码冲突;单个文件(control,directive等)很大,看的是眼花缭乱,容易出问题。

解决问题

基本思路:在路由切换时加载所需要资源。 工具:Angular-ui-router,oclazyload,requirejs。

配置oclazyload

在引入oclazyload文件后配置主要参数项

  • 全局设置示例
 app.config([‘$ocLazyLoadProvider‘,function($ocLazyLoadProvider){
        $ocLazyLoadProvider.config({
            loadedModules: [‘monitorApp‘],//主模块名,和ng.bootstrap(document, [‘monitorApp‘])相同
            jsLoader: requirejs, //使用requirejs去加载文件
            files: [‘modules/summary‘,‘modules/appEngine‘,‘modules/alarm‘,‘modules/database‘], //主模块需要的资源,这里主要子模块的声明文件
            debug: true
        });
   }]);

/* Configure ocLazyLoader(refer: https://github.com/ocombe/ocLazyLoad) */
MetronicApp.config(['$ocLazyLoadProvider', function($ocLazyLoadProvider) {
    $ocLazyLoadProvider.config({
        // global configs go here
    });
}]);


  • 子路由文件加载
/***
Metronic AngularJS App Main Script
***/
/* Metronic App */
var MetronicApp = angular.module("MetronicApp", [
    "ui.router", 
    "ui.bootstrap", 
    "oc.lazyLoad",  
    "ngSanitize"
]); 

/* Configure ocLazyLoader(refer: https://github.com/ocombe/ocLazyLoad) */
MetronicApp.config(['$ocLazyLoadProvider', function($ocLazyLoadProvider) {
    $ocLazyLoadProvider.config({
        // global configs go here
    });
}]);
/********************************************
 BEGIN: BREAKING CHANGE in AngularJS v1.3.x:
*********************************************/
/**
`$controller` will no longer look for controllers on `window`.
The old behavior of looking on `window` for controllers was originally intended
for use in examples, demos, and toy apps. We found that allowing global controller
functions encouraged poor practices, so we resolved to disable this behavior by
default.

To migrate, register your controllers with modules rather than exposing them
as globals:

Before:

```javascript
function MyController() {
  // ...
}
```

After:

```javascript
angular.module('myApp', []).controller('MyController', [function() {
  // ...
}]);

Although it's not recommended, you can re-enable the old behavior like this:

```javascript
angular.module('myModule').config(['$controllerProvider', function($controllerProvider) {
  // this option might be handy for migrating old apps, but please don't use it
  // in new ones!
  $controllerProvider.allowGlobals();
}]);
**/

//AngularJS v1.3.x workaround for old style controller declarition in HTML
MetronicApp.config(['$controllerProvider', function($controllerProvider) {
  // this option might be handy for migrating old apps, but please don't use it
  // in new ones!
  $controllerProvider.allowGlobals();
}]);

/********************************************
 END: BREAKING CHANGE in AngularJS v1.3.x:
*********************************************/

/* Setup global settings */
MetronicApp.factory('settings', ['$rootScope', function($rootScope) {
    // supported languages
    var settings = {
        layout: {
            pageSidebarClosed: false, // sidebar menu state
            pageContentWhite: true, // set page content layout
            pageBodySolid: false, // solid body color state
            pageAutoScrollOnLoad: 1000 // auto scroll to top on page load
        },
        assetsPath: '../assets',
        globalPath: '../assets/global',
        layoutPath: '../assets/layouts/layout',
    };

    $rootScope.settings = settings;

    return settings;
}]);
MetronicApp.factory('securityInterceptor', function ($rootScope) {
    return {
        'request': function (config) {
            if (config.url.split('?')[0].endsWith(".ljson")) {
                if (config.quiet) {
                    $rootScope.quietAppLoading = true;
                } else {
                    $rootScope.appLoading = true;
                }
            }
            return config;
        },
        'response': function (response) {
            if (response.config.url.split('?')[0].endsWith(".ljson")) {
                $rootScope.appLoading = false;
                $rootScope.quietAppLoading = false;
            }
            return response;
        },
        'requestError': function (rejection) {
            if (rejection.config.url.split('?')[0].endsWith(".ljson")) {
                $rootScope.appLoading = false;
                $rootScope.quietAppLoading = false;
            }
            return rejection;
        },
        'responseError': function (rejection) {
            if (rejection.config.url.split('?')[0].endsWith(".ljson")) {
                $rootScope.appLoading = false;
                $rootScope.quietAppLoading = false;
            }
            if (rejection.status == 401) {
                window.location.href = path + "pages/onLineShop/index.html";
            } else {
                return rejection;
            }
            return rejection;
        }
    };
});
MetronicApp.config(["$urlRouterProvider", "$stateProvider", "$httpProvider", function ($urlRouterProvider, $stateProvider, $httpProvider) {
    //$urlRouterProvider.otherwise("/main");

//    $httpProvider.defaults.transformRequest = function (data) {
//        if (data === undefined) {
//            return data;
//        }
//        return $.param(data);
//    };
 // Override $http service's default transformRequest  
    $httpProvider.defaults.transformRequest = [function(data)  
    {  
        /** 
         * The workhorse; converts an object to x-www-form-urlencoded serialization. 
         * @param {Object} obj 
         * @return {String} 
         */  
        var param = function(obj)  
        {    
            var query = '';  
            var name, value, fullSubName, subName, subValue, innerObj, i;  
            //console.log(obj);  
            for(name in obj)  
            {  
                value = obj[name];  
                //console.log(value);  
                if(value instanceof Array)  
                {  
                    //console.log("Array");  
                    for(i=0; i<value.length; ++i)  
                    {  
                        subValue = value[i];  
                        fullSubName = name + '[' + i + ']';  
                        innerObj = {};  
                        innerObj[fullSubName] = subValue;  
                        query += param(innerObj) + '&';  
                    }  
                }  
                else if(value instanceof Object)  
                {  
                     //console.log("object");  
                    for(subName in value)  
                    {  
                        subValue = value[subName];  
                        if(subValue != null){  
                            // fullSubName = name + '[' + subName + ']';  
                            //user.userName = hmm & user.userPassword = 111  
                            fullSubName = name + '.' + subName;  
                            // fullSubName =  subName;  
                            innerObj = {};  
                            innerObj[fullSubName] = subValue;  
                            query += param(innerObj) + '&';  
                        }  
                    }  
                }  
                else if(value !== undefined && value !== null) //&& value !== null  
                {  
                    //console.log("undefined");  
                    query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';  
                }  
            }  
            return query.length ? query.substr(0, query.length - 1) : query;  
        };  
        return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;  
    }]  
    $httpProvider.defaults.useXDomain = true; 
    $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'; 
    $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';  
    $httpProvider.defaults.headers.common['Cache-Control'] = 'no-cache, no-store, must-revalidate';
    $httpProvider.defaults.headers.common['Pragma'] = 'no-cache';
    $httpProvider.defaults.headers.common['Expires'] = '0';
    $httpProvider.interceptors.push('securityInterceptor');
}]);
/* Setup App Main Controller */
MetronicApp.controller('AppController', ['$scope', '$rootScope', function($scope, $rootScope) {
    $scope.$on('$viewContentLoaded', function() {
        //App.initComponents(); // init core components
        //Layout.init(); //  Init entire layout(header, footer, sidebar, etc) on page load if the partials included in server side instead of loading with ng-include directive 
    });
}]);

/***
Layout Partials.
By default the partials are loaded through AngularJS ng-include directive. In case they loaded in server side(e.g: PHP include function) then below partial 
initialization can be disabled and Layout.init() should be called on page load complete as explained above.
***/

/* Setup Layout Part - Header */
MetronicApp.controller('HeaderController', ['$scope', function($scope) {
    $scope.$on('$includeContentLoaded', function() {
        Layout.initHeader(); // init header
    });
}]);

/* Setup Layout Part - Sidebar */
MetronicApp.controller('SidebarController', ['$scope', function($scope) {
    $scope.$on('$includeContentLoaded', function() {
        Layout.initSidebar(); // init sidebar
    });
}]);

/* Setup Layout Part - Quick Sidebar */
MetronicApp.controller('QuickSidebarController', ['$scope', function($scope) {    
    $scope.$on('$includeContentLoaded', function() {
       setTimeout(function(){
            QuickSidebar.init(); // init quick sidebar        
        }, 2000)
    });
}]);

/* Setup Layout Part - Theme Panel */
MetronicApp.controller('ThemePanelController', ['$scope', function($scope) {    
    $scope.$on('$includeContentLoaded', function() {
        Demo.init(); // init theme panel
    });
}]);

/* Setup Layout Part - Footer */
MetronicApp.controller('FooterController', ['$scope', function($scope) {
    $scope.$on('$includeContentLoaded', function() {
        Layout.initFooter(); // init footer
    });
}]);

/* Setup Rounting For All Pages */
MetronicApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
    // Redirect any unmatched url
    $urlRouterProvider.otherwise("/category.html");  
    
    $stateProvider
	 // Dashboard
    .state('userManage', {
        url: "/userManage.html",
        templateUrl: "views/userManage.html",            
        data: {pageTitle: '商品分类管理'},
        resolve: {
            deps: ['$ocLazyLoad', function($ocLazyLoad) {
                return $ocLazyLoad.load({
                    name: 'MetronicApp',
                    insertBefore: '#ng_load_plugins_before', // load the above css files before a LINK element with this ID. Dynamic CSS files must be loaded between core and theme css files
                    files: [
                            'js/controllers/userManage.js'
                    ] 
                });
            }]
        }
    })
	    .state('category', {
	        url: "/category.html",
	        templateUrl: "views/category.html",            
	        data: {pageTitle: '商品分类管理'},
	        resolve: {
	            deps: ['$ocLazyLoad', function($ocLazyLoad) {
	                return $ocLazyLoad.load({
	                    name: 'MetronicApp',
	                    insertBefore: '#ng_load_plugins_before', // load the above css files before a LINK element with this ID. Dynamic CSS files must be loaded between core and theme css files
	                    files: [
	                        '../assets/global/plugins/morris/morris.css',                            
	                        '../assets/global/plugins/morris/morris.min.js',
	                        '../assets/global/plugins/morris/raphael-min.js',                            
	                        '../assets/global/plugins/jquery.sparkline.min.js',
	                        '../assets/global/plugins/angularjs/plugins/angular-file-upload/angular-file-upload.min.js',
	                        'js/controllers/CategoryController.js'
	                    ] 
	                });
	            }]
	        }
	    })
	    .state('productlist', {
	        url: "/productlist.html",
	        templateUrl: "views/productlist.html",            
	        data: {pageTitle: '商品列表'},
	        resolve: {
	            deps: ['$ocLazyLoad', function($ocLazyLoad) {
	                return $ocLazyLoad.load({
	                    name: 'MetronicApp',
	                    insertBefore: '#ng_load_plugins_before', // load the above css files before a LINK element with this ID. Dynamic CSS files must be loaded between core and theme css files
	                    files: [
							'../assets/global/plugins/morris/morris.css',                            
							'../assets/global/plugins/morris/morris.min.js',
							'../assets/global/plugins/morris/raphael-min.js',                            
							'../assets/global/plugins/jquery.sparkline.min.js',
							'../assets/global/plugins/datatables/datatables.min.css', 
							'../assets/global/plugins/datatables/plugins/bootstrap/datatables.bootstrap.css',
							'../assets/global/plugins/bootstrap-datepicker/css/bootstrap-datepicker3.min.css',
							
							'../assets/global/plugins/datatables/datatables.all.min.js',
							'../assets/global/plugins/bootstrap-datepicker/js/bootstrap-datepicker.min.js',
							'../assets/global/scripts/datatable.js',
							
							'js/scripts/table-ajax.js',
							'js/controllers/ProductListController.js'
	                    ] 
	                });
	            }]
	        }
	    })
}]);

/* Init global settings and run the app */
MetronicApp.run(["$rootScope", "settings", "$state", function($rootScope, settings, $state) {
    $rootScope.$state = $state; // state to be accessed from view
    $rootScope.$settings = settings; // state to be accessed from view
}]);