Angular.js 按需加载 Controller

3,018 阅读1分钟
原文链接: mp.weixin.qq.com

使用angularjs + requirejs来开发大型单页应用应该说是比较好的组合,通过requirejs异步加载angular的各个 模块一定程度上可以提高应用的加载速度。但是一般情况下,我们所有模块的controller也会在页面初加载 的时候被加载进来,这样就会有不必要的加载,因为有些模块用户可能根本不会使用到,尤其是对于有好 几十个模块的大型应用来讲,尤其不能接受

借助angular中$routeProvider的配置参数resolve + requirejs 我们可以轻松实现模块的按需加载


我们先来看看一般的 angularjs + requirejs 方案

如开头描述,这里的四个模块aModbMod、cMod、dMod在页面初始加载时就会被加载进来,导致资源浪费, 用户体验下降

require.config({ 
    paths: { 
        angular: 'http://img6.didistatic.com/static/tms/cdn/z/angular/angular/1.5.0/angular.min', 
        angularRoute: 'http://img6.didistatic.com/static/tms/cdn/z/angular-route/angular-route/1.3.5/angular-route.min', 
    }, 
    shim: { 
        angular: { 
            exports: 'angular' 
        }, 
        angularRoute: ['angular'], 
        aMod: ['angularRoute'], 
        bMod: ['angularRoute'], 
        cMod: ['angularRoute'], 
        dMod: ['angularRoute'] 
    } 
}); 
 
require( 
    [ 
        //四个模块的controller文件在页面初始化时就载入了进来 
        'aMod', 
        'bMod', 
        'cMod', 
        'dMod' 
    ], 
    function () { 
        var app = angular.module('app', ['ngRoute', 'aMod', 'bMod', 'cMod', 'dMod']); 
 
        app.config(['$routeProvider', function ($routeProvider) { 
 
            $routeProvider 
                .when('/a', { 
                    templateUrl: 'aMod.html', 
                    controller: 'aModController' 
                }) 
                .when('/b', { 
                    templateUrl: 'bMod.html', 
                    controller: 'bModController' 
                }) 
                .when('/c', { 
                    templateUrl: 'cMod.html', 
                    controller: 'cModController' 
                }) 
                .when('/d', { 
                    templateUrl: 'dMod.html', 
                    controller: 'dModController' 
                }) 
                .otherwise({ redirectTo: '/a' }); 
        }]); 
 
        angular.bootstrap(document, ['app']); 
    } 
);


angular $routeProvider配置参数resolve + requirejs方案


目录结构


各文件源码解析(按源码执行顺序)


main.js

main.js是requirejs的入口文件,其中加载了angular,ngRoute,以及angular入口文件app,当所有文件 加载完毕后,会在回调函数里调用angular.bootstrap来启动应用

require.config({ 
    paths: { 
        angular: 'http://img6.didistatic.com/static/tms/cdn/z/angular/angular/1.5.0/angular.min', 
        angularRoute: 'http://img6.didistatic.com/static/tms/cdn/z/angular-route/angular-route/1.3.5/angular-route.min', 
        app: 'app' 
    }, 
    shim: { 
        angular: { 
            exports: 'angular' 
        }, 
        angularRoute: ['angular'], 
        app: ['angular', 'angularRoute'] 
    } 
}); 
 
require( 
    [ 
        'app' 
    ], 
    function () { 
        angular.bootstrap(document, ['app']); 
    });

app.js

app.js是应用的主模块定义文件,其中包含路由定义,以及向各个动态加载的模块提供动态注入register对象

在定义路由规则时,会调用routeResolverProvider来返回动态的路由配置

'use strict'; 
 
define(['services/routeResolver'], function () { 
 
    var app = angular.module('app', ['ngRoute', 'resolverMod']); 
 
    app.config(['$routeProvider', 'routeResolverProvider', '$controllerProvider', function ($routeProvider, routeResolverProvider, $controllerProvider) { 
 
        /* 
         向app模块注册动态加载的controller 
        */ 
        app.register = { 
            controller: $controllerProvider.register 
        }; 
 
        /*  
        routeResolverProvider为处理动态加载controller的provider  
        resolve方法,会返回一个包含resolve的配置对象 
        */ 
        var resolve = routeResolverProvider.resolve; 
 
        $routeProvider 
            .when('/a', resolve({baseName: 'a'})) 
            .when('/b', resolve({baseName: 'b'})) 
            .when('/c', resolve({baseName: 'c'})) 
            .when('/d', resolve({baseName: 'd'})) 
            .otherwise({ redirectTo: '/a' }); 
    }]); 
 
    return app; 
});

routeResolver.js

该模块是按需加载controller的核心模块,以provider的形式提供路由配置

'use strict'; 
 
define([], function () { 
 
    var resolverMod = angular.module('resolverMod', []); 
 
    var resolverProvider = function () { 
 
        this.$get = function () { 
            return this; 
        }; 
 
        this.resolve = function (opt) { 
 
            var defaults = { 
                baseName: '',   //约定的模块名 
                path: '',       //子目录 
                controllerSuffix: '',   //控制器后缀 
                controllerFileSuffix: '.js',    //控制器文件后缀 
                templateFileSuffix: '.html',    //模板文件后缀 
                templateDir: '/js/business/',   //模板目录 
                controllerDir: '/js/business/'  //控制器目录 
            }; 
 
            var config = angular.extend({}, defaults, opt); 
 
            //路由配置 
            config.routeDef = angular.extend({}, config.routeDef, { 
                templateUrl: config.templateDir + config.path + config.baseName + config.templateFileSuffix, 
                controller: config.baseName + config.controllerSuffix, 
            }); 
 
            config.routeDef.resolve = angular.extend({}, config.routeDef.resolve, { 
                //按需加载核心 
                load_on_need: ['$q', function ( $q ) { 
 
                    var defer = $q.defer(); 
                    var controllerUrl = config.controllerDir + config.path + config.baseName + config.controllerFileSuffix; 
                    var dependencies = [controllerUrl]; 
 
                    require(dependencies, function () { 
                        //加载成功后改变promise状态为resolve 
                        defer.resolve(); 
                    }); 
 
                    return defer.promise; 
                }] 
            }); 
 
            return config.routeDef; 
        } 
    }; 
 
    resolverMod.provider('routeResolver', resolverProvider); 
});

a.js

定义业务模块a的controller,依赖主模块app

通过调用之前在app模块中的register对象的controller方法来将自己动态注入app模块中

'use strict'; 
 
define(['app'], function (app) { 
 
    app.register.controller('a', ['$scope', function ($scope) { 
        $scope.title = 'this is a controller'; 
     }]); 
});

$routeProvider配置参数resolve

详细用法可参阅angular官方文档

docs.angularjs.org/api/ngRoute…

总结

至此,使用以上代码,我们就可以实现当页面初始化时,加载必要的模块,其他模块只需在需要 的时候在去动态加载

小伙伴们是不都已经迫不及待的想尝试尝试呢,我搞了一个demo大家可以run起来(注意需要在根目录起个 server环境哦) 

github:github.com/suweiwua/an…