前端架构:requirejs+angularjs实现懒加载angular模块

require.js + angular.js の一种前端架构

新项目要完全采用 restful api 的方式来进行开发,既然前端只有我一个就我说了算ԅ(≖‿≖ԅ),工作以来还未在项目中用过 angular 开发,先简单介绍一下 angular.js ,我理解的她是一种重型前端 mvc 框架,对写 jQuery 的人来说可能上手比较困难,不过一旦理解她的思想就容易了,她内部已经做了很多工作,用她来开发的话你要关心的是数据的变动,而不是像 jQuery 一样去操作 DOM 元素,如果你对 jQuery 很熟悉的话想上手 angular 我推荐理解一下 stackoverflow 上这个问题,__如果我对 jQuery 开发很熟如何理解 angular __。

requirejs 不用说都知道是用来前端懒加载 scripts 的,我在网上看到的使用 require + angular 的组合的案例模式都是一样的,无论你访问网站的哪个 url ,都会把 angular 的所有模块全部通过 require 加载下来,虽然 angular 与 backbone 这些框架貌似更适合做单页应用,但是我认为单页应用也没必要把所有的模块都加载下来,这样首屏加载就会很慢,用户体验很不好;于是我 google 了一通,终于找到了解决方法,通过 require 配合 angular route 的 resolve 方法,实现单模块加载,这样做的好处显而易见,坏处可以说没有,硬要说的话还是 angular 之前的缺点:seo 方面要单做。

废话不多说,show you my code:

首先项目目录结构是这样滴:

app-|
    |-package.json
    |-index.html
    |-views
    |-src-+
          |-img
          |-css
          |-js-+
          |    |-controllers-+...
          |    |-services-+...
          |    +application.js
          |    +require-config.js
          |    +routeResolve.js
          |-lib-+
                +angular.min.js
                +angular-route.min.js
                +angular-animate.min.js
                +angular-resourse.min.js
                +require.js

index.html 长这样:

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
    <head>
        <meta charset="UTF-8">
        <title>application</title>
        <script src="/src/lib/require.js" data-main="/src/js/require-config.js"></script>
    </head>
    <body ng-view></body>
</html>

require-config.js :

require.config({
    baseUrl: '/src',
    paths: {
        'angular': 'lib/angular.min',
        'ngRoute': 'lib/angular-route.min',
        'ngAnimate': 'lib/angular-animate.min',
        'ngResource': 'lib/angular-resource.min'
    },
    shim: {
        angular: {
            exports : 'angular'
        },
        ngRoute: {
            deps: ['angular'],
            exports: 'ngRoute'
        },
        ngAnimate: {
            deps: ['angular'],
            exports: 'ngAnimate'
        },
        ngResource: {
            deps: ['angular'],
            exports: 'ngResource'
        }
    }
    ,urlArgs: 'v='+(+new Date())
});
require([
    'js/application'
], function(application){
    angular.element(document).ready(function(){
        angular.bootstrap(document, ['application']);
    });
})

application.js :

define([
    'angular',
    'ngRoute',
    'ngAnimate',
    'ngResource',
    'js/routeResolve'
], function( angular, ngRoute, ngAnimate, ngResource, routeResolver ) {
    var application = angular.module('application', ['ngRoute', 'ngAnimate', 'ngResource']);
    window.application = application;
    application
        .config(['$routeProvider', '$controllerProvider', '$compileProvider', '$filterProvider', '$provide',
            function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
                var route = routeResolver;
                $routeProvider
                    .when('/', {
                        reloadOnSearch: false,
                        templateUrl:'views/index.html',
                        resolve: route.resolve([
                            'js/services/peopleService',
                            'js/controllers/popup',
                            'js/controllers/paginator',
                            'js/controllers/index'
                        ])
                    })
                    .otherwise({
                        redirectTo:'/'
                    });
                window.application.components = {
                    controller : $controllerProvider.register,
                    directive  : $compileProvider.directive,
                    filter     : $filterProvider.register,
                    factory    : $provide.factory,
                    service    : $provide.service
                }
        }]);
    return application;
})

routeResolve.js :

define(function ( require ) {
    return function () {
        var resolve = function (deps, preFetchFuncs) {
            var routeDef = {
                load: ['$q', '$rootScope', function ($q, $rootScope) {
                    var dependencies = deps;
                    return resolveDependencies($q, $rootScope, dependencies);
                }]
            };
            if ( preFetchFuncs ) {
                angular.merge( routeDef, preFetchFuncs );
            }
            return routeDef;
        },
        resolveDependencies = function ($q, $rootScope, dependencies) {
            var defer = $q.defer();
            require(dependencies, function () {
                defer.resolve();
                $rootScope.$apply()
            });
            return defer.promise;
        };
        return {
            resolve: resolve
        }
    }();
});

其实这就是所有的内容了,当访问首页即 ‘/‘ 时,angular route 的 resolve 方法__通过__我们封装的__routeResolve__来懒加载模块代码,当然模块代码写的时候就不是 define function 里再写 angular.controller 了,直接写 window.application.components.controller ,好那么问题来了,为什么要暴露 angular 的那些方法给 window 呢,原因是 angular 的工作方式,angular 在启动之后是不能动态添加 controller 啊 service 啊之类的,在 angular 程序启动后,要添加只能通过内部的 register 方法(controller 的 register),所以你懂的。

OK,采用这种架构之后,前端__所有__代码都可以扔到 cdn 上去了,接口全部 rest api。

以上。

(ps: 这种架构到底有哪些坑本人还不得而知,若有很明显的坑麻烦还请告知一下我(๑╹ڡ╹)╭)

参考链接:

http://weblogs.asp.net/dwahlin/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs