Angularjs 使用 Requirejs 和 OcLazyload 实现懒加载






4.93/5 (13投票s)
在 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://clintberry.com/2013/modular-angularjs-application-design/
关注点
在本文中,我们讨论了如何使用 RequireJS 和 OcLazyLoad 实现懒加载。
我希望这对那些对如何开始懒加载感到困惑的开发者有所帮助。
这里附上我们讨论过的所有内容的演示代码。
注意:您必须在 IIS 中配置代码才能正常工作。不要从物理路径运行它。
历史
- 2015 年 10 月 15 日 - 第一版
- 2015 年 10 月 19 日 - 第二版
- 2015 年 11 月 1 日 - 第三版
- 2015 年 11 月 20 日 - 第四版