65.9K
CodeProject 正在变化。 阅读更多。
Home

Angularjs 使用 Requirejs 和 OcLazyload 实现懒加载

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (13投票s)

2015年11月1日

CPOL

9分钟阅读

viewsIcon

62714

downloadIcon

1164

在 anuglarjs 应用程序中使用 Requirejs 和 OcLazyLoad 实现懒加载

引言

关于如何实现 AngularJS 的文章有很多。在使用 AngularJS 开发 Web 应用程序后,应用程序需要更快响应是常见的需求。懒加载就是其中一种方法。

本文的主要目标是在我们的应用程序中实现懒加载(仅在我们需要时加载依赖模块和文件(css 或 js))。本文将介绍如何使用 RequireJS 和 ocLazyLoad 的基本概念。

读者应具备 AngularJS 基础知识。本文仅帮助您实现懒加载。

背景

AngularJS 是实现单页应用程序的正确选择之一。当我们开始开发应用程序时,我们将所有 css 和 js 文件放在 index.html 页面中并加载应用程序。

这是一个简单且良好的入门方式。但我们的应用程序不会长时间保持小规模。它会随着时间的推移而增长(可伸缩性)。

将所有文件复制到 index.html 页面本身会使着陆页花费更多时间。因此,当用户访问我们的页面时,他/她可能会厌倦等待,甚至可能在看到我们的着陆页之前就离开 :-(。

众所周知,AngularJS 开发团队正在开发原生懒加载实现。

但即使现在,也可以通过使用以下两个插件来实现

RequireJS vs OcLazyLoad

当我开始实现懒加载时,我非常困惑该选择哪一个,是 Requirejs 还是 OcLazyLoad,或者两者都用。

在看了更多的视频和文字教程后,我得出结论,我们应该两者兼顾。这是一个正确的想法。

原因

RequireJS

  • RequireJS 主要用于加载依赖的 js 文件。我们可以称它为“优秀的组织者”。
  • 我们不能加载 CSS 文件(我们可以,但不能像加载 Js 文件那样。需要做一些技巧)
  • 我们不能动态注入 AngularJS 模块。它只在需要时加载文件。

OcLazyLoad

  • OcLazyLoad 用于加载依赖文件,也用于动态注入 AngularJS 模块。
  • 加载 Js 和 Css 文件毫不费力。
  • 我们无法维护依赖文件结构。

从以上几点来看

我在这里感到非常困惑。在 OcLazyLoad 中实现和学习非常容易,它与 RequireJs 做同样的事情(加载依赖文件),甚至可以注入 angular 模块。那么,为什么我还要选择 RequireJs 呢?答案是维护依赖文件和重用代码。

实现

RequireJS

使用 RequireJS,我们在 Index.html 页面上定义一个单一的入口点。在 RequireJS 中,我们将代码作为模块来维护,这有助于它们具有单一职责。

现在我们需要配置我们的依赖文件结构。这将帮助我们加载一个文件及其依赖项。主要的是,配置依赖项后,记住所有文件的依赖项的担忧将消失。

例如,

考虑任何依赖于 angular.js 的 AngularJS 插件。这里我们会感到困惑。例如,Login page 是我们的着陆页。当我们到达着陆页时,angular.js 文件将被加载。然后假设我们要去 dashboard page。所以在这里我们不需要再次加载 angular.js 文件,因为我们已经加载了。但是如果我们在仪表盘页面刷新浏览器(所有脚本文件都将消失),那么只加载与 dashboard page 相关的​​文件。对于所有这些文件,依赖项是 angular.js 文件。那么现在我们的目的地是什么?angular.js,对吧?

这是一个简单的例子。所以在这里我们可能会想,我们为此付出了多大的努力呢? :-) 显然没有(仅限于此)。我们可以满足于这个简单的应用程序,但我们的客户不会。如果不断加载或依赖许多 AngularJs 插件,那么很有可能忘记依赖链。

现在我们来谈谈实现。我们单个脚本标签上的 data-main 属性告诉 Require.js 加载 data-main 路径中提到的脚本。

<script src="scripts/require.js" data-main="app/main.js"></script>

这里的 main.js 是我们的配置文件。文件名不一定必须是 main.js。加载 main.js 后,RequireJS 会自动依赖于 js 文件。在提及文件名时,我们不需要提及文件扩展名。

以下是 main.js 文件结构。

  • urlArgs    -  这是为了表示版本控制
  • paths      -   这是我们需要提及文件路径的地方
  • shim        -    shim 是我们需要提及依赖项的地方
  • deps       -    这将启动我们的应用程序
  • require    -    这将加载我们需要的任何其他脚本。它将在我们的主应用程序加载之前加载必要的文件。
// contents of main:
require.config({
    urlArgs: "bust=" + (new Date()).getTime(),
    paths: {
        // Jquery
        'jquery': '../../scripts/jquery-1.9.1.min',

        // angular
        'angular': '../../scripts/angular.min',
        'angular_aria': '../../scripts/angular-aria',
        'angular_route': '../../scripts/angular-route.min',
        'angular_cookie': '../../scripts/angular-cookies',

        'angular_translate': '../../scripts/angular-translate/AngularTranslate',
        'angular_translate_cookie': '../../scripts/angular-translate-storage-cookie.min',

        'angular_animate': '../../scripts/angular-animate.min',

        // angular local storage
        'angular-local-storage': '../../scripts/angular-local-storage',

        // Angular bootstrap
        'angular_ui_bootstrap': '../../scripts/angular-ui/ui-bootstrap-tpls.min',

        'angular_ocLazyLoad': '../../scripts/ocLazyLoad/ocLazyLoad',

        //General
        'HomeController': 'home/homeController',
        'LandingModule': 'Landing/landingController',

        // For page1, the service has mentioned in the service files itself
        'indexController': 'indexController',
        'Page1Controller': 'Page1/page1Controller',

        'Page2Controller': 'Page2/page2Controller',
        'Page2Service': 'Page2/page2Service'
    },

    // Mention the dependencies
    shim: {
        'angular': {
            exports: 'angular'
        },
        'jquery': {
            exports: "$"
        },
        'angular_ocLazyLoad': {
            deps: ['angular']
        },
        'angular_route': {
            deps: ['angular']
        },
        'angular_cookie': {
            deps: ['angular']
        },
        'angular_ui_bootstrap': {
            deps: ['angular']
        },
       'app': {
            deps:['angular_route', 'jquery', 'angular_ocLazyLoad', 'angular_ui_bootstrap']
        },
        'HomeController': {
            deps: ['app']
        },
        'Page1Controller': {
            deps: ['app']
        },
        'Page2Controller': {
            // Page 2 depends on the page1 file..
            // because the SampleApp.Pages module defined files is available there
            deps: ['Page1Controller', 'Page2Service']
        },
        'indexController': {
            deps: ['app']
        },
    },
    deps:['app']
});

require(['indexController'], function () {
    angular.bootstrap(document, ['SampleApp']);
});

这里重要的是 requirejs 将查看 app 目录中的所有文件。例如,当我们尝试加载 homecontroller 文件时,RequireJS 将根据路径加载,例如 App/home/homecontroller.js。如果我们想更改 config 路径,那么我们需要使用 settings。设置此 config 后,RequireJS 将查看以下路径。也就是说,RequireJS 假设 homecontroller.js 文件位于 Project/home/homecontroller.js 或我们 可以使用 ../../ 来绕过它们。

baseUrl:'Project'
  • 在这里,在初始应用程序启动时,我们需要设置我们的主应用程序。(即)我们需要加载并注入必要的模块到我们的主应用程序中。为此,我们必须加载必要的文件。
  • 我们的主应用程序依赖于模块 'ngRoute', 'ui.bootstrap', 'oc.lazyLoad'。 所以我们必须加载以下文件
    • angular.js、jquery.js、ngRoute.js、OcLazyLoad.js 和 UiBootstrap.js。在 shim 中,您可以看到 app。这里我们提到了主应用程序的所有依赖项。
    • 我将 indexController.js 文件与主应用程序一起加载。原因是,我将 IndexController 定义为所有控制器的父控制器。

app.js

以下是我们的 app.js 结构。

define(['require'], function (require) {
    var SampleApp = angular.module('SampleApp', ['ngRoute', 'ui.bootstrap', 'oc.lazyLoad']);

    SampleApp.config(['$routeProvider', '$httpProvider', '$controllerProvider', '$provide',
    function ($routeProvider, $httpProvider, $controllerProvider, $provide) {

        // To register controller and services which will be loaded lazily
        SampleApp.registerController = $controllerProvider.register;
        SampleApp.$register = $provide;

        var version = "?bust=" + (new Date()).getTime();
        $routeProvider
            .when('/Page1', {
                title: 'Page1',
                templateUrl: 'App/Page1/Page1.html' + version,
                controller: 'page1Controller',
                caseInsensitiveMatch: true,
                resolve: {
                    loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                        debugger
                        var deferred = $q.defer();

                        // After loading the controller file we need to inject the module
                        // to the parent module
                        require(["Page1Controller"], function () {
                            // Using OcLazyLoad we can inject the any module to the parent module
                            $ocLazyLoad.inject('SampleApp.Pages');
                            deferred.resolve();
                        });
                        return deferred.promise;
                    }]
                }
            })
             .when('/Landing', {
                 title: 'Landing',
                 templateUrl: 'App/Landing/Landing.html' + version,
                 controller: 'landingController',
                 caseInsensitiveMatch: true,
                 resolve: {
                     loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                         debugger
                         var deferred = $q.defer();
                         require(["LandingModule"], function () { deferred.resolve(); });
                         return deferred.promise;
                     }]
                 }
             })
            .when('/home', {
                title: 'home',
                templateUrl: 'App/Home/Home.html' + version,
                controller: 'homeController',
                caseInsensitiveMatch: true,
                resolve: {
                    loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                        debugger
                        var deferred = $q.defer();
                        require(["HomeController"], function () {
                            $ocLazyLoad.inject('SampleApp.Home');
                            deferred.resolve();
                        });
                        return deferred.promise;
                    }]
                }
            }).otherwise({
                title: 'Landing',
                redirectTo: '/Landing'
            });

        $httpProvider.interceptors.push(
           ['$q', '$location',
           function ($q, $location) {
               return {
                   request: function (config) {
                       
                       return config;
                   },

                   response: function (result) {
                       return result;
                   },

                   responseError: function (rejection) {
                       console.log('Failed with', rejection.status, 'status');

                       return $q.reject(rejection);
                   }
               }
           }]);
    }]);
})

 

RequireJS 希望我们采用 AMD 方法异步模块定义 (AMD)是JavaScript 规范,定义了一个用于定义代码模块及其依赖项的 API ,并可根据需要异步加载它们。

这里的 SampleApp 是我们的主模块。

除了 indexController,我们还在懒加载其他控制器(Landing、home、page1 和 page2)。这些控制器不会绑定到 main 模块。当懒加载控制器时,它们应该对我们可用。

因此,在懒加载时,我们需要将它们注册到模块中。

我们可以使用以下方法注册控制器、工厂、服务。

  • $controllerProvider
  • $provide

在应用程序配置中,我们必须使用它们以备将来使用。

   // To register controller and services which will be loaded lazily
        SampleApp.registerController = $controllerProvider.register; // To register controller
        SampleApp.$register = $provide; // To register factory and service

这里我们将注册服务与名为 registerController $register 的主应用程序变量关联起来。

LoadingController.js

define(['angular'], function (angular) {
    var SampleApp = angular.module('SampleApp');
    debugger
    SampleApp.$register.factory('landingService', ['$http', '$rootScope', function ($http, $rootScope) {
        debugger
        return {
            GetQuestions: function () {
                return $http({
                    url: ""
                });
            }
        }
    }]);

    SampleApp.registerController('landingController',
        ['$scope', '$http', 'landingService', function (scope, http, landingService) {
            scope.GetQuestions = function () {
                landingService.GetQuestions().success(function (data) {

                }).
                error(function (data) {

                });
            }
      }]);
});

这是我们的着陆控制器。在这里,我们以 AMD 方式定义了着陆控制器和服务文件。我们调用了主应用程序并注册了 landingController 及其服务。

好的。这里我们以 AMD 方式定义了着陆控制器文件并将控制器注册到主应用程序。那么我们如何在路由 landing 时才加载这个 js 文件呢?

我们需要在路由上使用 resolve 属性。在 resolve 上使用带有 requirejs 的 promise,以便按如下方式加载文件。

 resolve: { loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                         var deferred = $q.defer();
                         require(["LandingModule"], function () { deferred.resolve(); });
                         return deferred.promise;
                     }]
                 }

OcLazyLoad 的必要性

在了解 OcLazyLoad 的用途之前,我们先简要讨论一下模块化。

模块化

模块化是将我们的模块逻辑上划分为更小的子模块,或者编写模块并将它们组合到我们的主模块中,这将有助于主模块的逻辑性。

将来,随着我们不断改进,我们的应用程序将不断壮大。正如我们到目前为止所讨论的,我们正在将控制器注册到主模块。

因此,在起始阶段就进行模块化,将有助于我们避免重写整个代码。

将应用程序模块化的主要目的是使其更容易重用、配置和测试应用程序中的组件。

模块化将帮助我们实现

  • 将代码拆分为逻辑模块
  • 将代码拆分为多个文件

懒加载文件将由 RequireJs 完成。考虑一下 NgDialog 插件,它用于模态弹出窗口(对话框)。我们可能除了一个或两个页面之外,不需要在所有页面中使用它。

假设我只为 page1 需要这个,那么我可以通过 RequireJs 轻松加载这个文件。那么,在我加载这个文件之后,它会工作吗?当然不会。

为什么?这里发生了什么?我们加载了 NgDialog 文件。但我们的应用程序不知道 NgDialog 是什么。除非将此模块注入到我们的主应用程序中,否则主应用程序不会知道 NgDialog。

通过 RequireJs 动态注入是不可能的。所以我们使用 OcLazyLoad 来注入模块。

在我们的案例中,让我们举一个简单的例子。

我们计划在逻辑上模块化我们的应用程序。从配置文件中,我们可能会明白我们将有多个页面。所以在这里我想创建一个名为 SampleApp.Pages 的新模块。

define(['angular'], function (angular) {
    var SampleApp = angular.module('SampleApp.Pages', []);

    SampleApp.config(['$routeProvider', '$httpProvider', '$controllerProvider', '$provide',
    function ($routeProvider, $httpProvider, $controllerProvider, $provide) {
        SampleApp.registerController = $controllerProvider.register;
        SampleApp.$register = $provide;
        var version = "?bust=" + (new Date()).getTime();
        $routeProvider
            .when('/Page2', {
                title: 'Page2',
                templateUrl: 'App/Page2/Page2.html' + version,
                controller: 'page2Controller',
                caseInsensitiveMatch: true,
                resolve: {
                    loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                        var deferred = $q.defer();

                        // After loading the controller file we need to inject the module
                        // to the parent module
                        require(["Page2Controller"], function () {
                            deferred.resolve();
                        });
                        return deferred.promise;
                    }]
                }
            });
    }]);

    SampleApp.controller('page1Controller',
        ['$scope', '$http', 'page1Service', function (scope, http, page1Service) {

            scope.GetQuestions = function () {
                page1Service.GetQuestions().success(function (data) {

                }).
                error(function (data) {

                });
            }

        }]);


    return SampleApp;
});

 

在“page1”路由上,我们将加载此文件。因此,加载文件后,我们的主应用程序不了解新模块。所以我们需要将其注入主模块。参考下面在 page1 路由 的配置文件中编写的代码

 resolve: {
                    loadModule: ['$ocLazyLoad', '$q', function ($ocLazyLoad, $q) {
                        debugger
                        var deferred = $q.defer();

                        // After loading the controller file we need to inject the module
                        // to the parent module
                        require(["Page1Controller"], function () {
                            // Using OcLazyLoad we can inject the any module to the parent module
                            $ocLazyLoad.inject('SampleApp.Pages');
                            deferred.resolve();
                        });
                        return deferred.promise;
                    }]
                }
 

注意:为了实验目的,我尝试了一件事。在这里你可以注意到 page2 路由是在 SampleApp.Pages 中提到的。这可能吗?是的,这是可能的。SampleApp.Pages 是独立的,不属于 SampleApp。我们称 SampleApp.Pages 为主模块的子模块。对吗?那么它为什么不属于主应用程序呢?是的,这里 SampleApp.Pages 是子模块,不是语法上的,而是逻辑上的。所以从语法上讲,这是一个独立的模块。这个子模块将有独立的路由。出于实验目的,我这样做了。在 page2 刷新浏览器时,它不会渲染 page2。它将转到 Landing 页面,因为路由无法与路由提供程序目录匹配。

我们如何解决这个问题?

我们可以模块化我们的应用程序。模块化后,我们可以将模块化文件与控制器和服务文件分离。分离后,我们需要加载我们提到配置和路由的模块化文件,并且应该在主应用程序加载时加载这些文件,并且应该注入这些分离的模块。我们可以根据需要稍后加载控制器和服务。

我们是否只使用 OcLazyLoad 来懒加载模块?根本不是。我们也可以使用 OcLazyLoad 来加载 css 文件。

参考文献

http://www.sitepoint.com/understanding-requirejs-for-effective-javascript-module-loading/

https://cdnjs.com/libraries/backbone.js/tutorials/organizing-backbone-using-modules

https://requirejs.node.org.cn/

https://en.wikipedia.org/wiki/Asynchronous_module_definition

模块化

https://www.safaribooksonline.com/blog/2014/03/27/13-step-guide-angularjs-modularization/

http://tutorials.jenkov.com/angularjs/dependency-injection.html

http://henriquat.re/modularizing-angularjs/modularizing-angular-applications/modularizing-angular-applications.html

http://clintberry.com/2013/modular-angularjs-application-design/

关注点

在本文中,我们讨论了如何使用 RequireJS 和 OcLazyLoad 实现懒加载。

我希望这对那些对如何开始懒加载感到困惑的开发者有所帮助。

这里附上我们讨论过的所有内容的演示代码。

注意:您必须在 IIS 中配置代码才能正常工作。不要从物理路径运行它。

历史

  • 2015 年 10 月 15 日 - 第一版
  • 2015 年 10 月 19 日 - 第二版
  • 2015 年 11 月 1 日 - 第三版
  • 2015 年 11 月 20 日 - 第四版
© . All rights reserved.