특정 규모의 프로젝트를 작업할 때 일반적으로 다음과 같은 목표를 달성하려고 합니다. 1. 복잡한 페이지 로직 지원(권한, 데이터 상태 등과 같은 비즈니스 규칙에 따른 콘텐츠의 동적 표시) . 프론트엔드와 백엔드 분리의 기본 원칙을 준수합니다. (분리되지 않은 경우 템플릿 엔진을 사용하여 백엔드에서 직접 좋은 페이지를 생성할 수 있습니다.) 3. 페이지 로딩 시간이 짧습니다. (복잡한 비즈니스 로직이 필요합니다.) 타사 라이브러리를 참조하지만 로드된 라이브러리가 사용자의 현재 작업과 관련이 없을 가능성이 매우 높습니다.) 4. 코드는 유지 관리가 쉬워야 합니다(새 로직을 추가할 때 가능한 한 적은 수의 파일에 영향을 미침). ).
이러한 목표를 동시에 달성하려면 페이지에 표시되는 콘텐츠와 모든 종속 파일을 비즈니스 로직 요구에 따라 로드할 수 있어야 합니다. 최근에는 모든 개발이 Anglejs를 기반으로 이루어지기 때문에 이 글에서는 주로 Anglejs가 제공하는 다양한 메커니즘에 초점을 맞춰 온디맨드 로딩을 완벽하게 구현하는 방법을 모색합니다.
1. 단계별 구현
기본 아이디어: 1. 먼저 몇 가지 기본 비즈니스 로직을 완성하고 확장 메커니즘을 지원할 수 있는 프레임워크 페이지를 개발합니다. , 일부 로직은 요청 시 로드되는 하위 페이지로 분할되어야 합니다. 3. 하위 페이지의 표시 콘텐츠도 복잡해졌으며 요청 시 분할 및 로드되어야 합니다. 하위 페이지가 복잡합니다. 외부 모듈에 의존하려면 요청 시 각도 모듈을 로드해야 합니다.
1. 프레임 페이지
요즘은 프론트엔드 온디맨드 로딩이라고 하면 AMD(Asynchronous Module Definition)가 떠오릅니다. 많은 requirejs가 사용됩니다. 따라서 먼저 요구사항 도입을 고려하세요.
index.html
<script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js"></script>
참고: Angular는 수동으로 시작되므로 html에 ng-app이 없습니다.
spa-loader.js
require.config({ paths: { "domReady": '/static/js/domReady', "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min", }, shim: { "angular": { exports: "angular" }, "angular-route": { deps: ["angular"] }, }, deps: ['/test/lazyspa/spa.js'], urlArgs: "bust=" + (new Date()).getTime() });
spa.js
define(["require", "angular", "angular-route"], function(require, angular) { var app = angular.module('app', ['ngRoute']); require(['domReady!'], function(document) { angular.bootstrap(document, ["app"]); /*手工启动angular*/ window.loading.finish(); }); });
2. 요청 시 하위 페이지 로드
angular의 routeProvider ng-view는 이미 직접 사용할 수 있는 완전한 하위 페이지 로딩 방법을 제공합니다.
html5Mode를 설정해야 합니다. 그렇지 않으면 RouteProvider가 URL 변경 후 URL을 가로채지 않습니다.
index.html
<div> <a href="/test/lazyspa/page1">page1</a> <a href="/test/lazyspa/page2">page2</a> <a href="/test/lazyspa/">main</a> </div> <div ng-view></div>
spa.js
app.config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) { /* 必须设置生效,否则下面的设置不生效 */ $locationProvider.html5Mode(true); /* 根据url的变化加载内容 */ $routeProvider.when('/test/lazyspa/page1', { template: '<div>page1</div>', }).when('/test/lazyspa/page2', { template: '<div>page2</div>', }).otherwise({ template: '<div>main</div>', }); }]);
3. 요청 시 하위 페이지의 콘텐츠 로드
사용 RouteProvider의 전제는 URL을 변경해야 하지만 때로는 하위 페이지의 일부만 변경해야 한다는 것입니다. 이러한 변경 사항이 주로 바인딩된 데이터와 관련되어 있고 페이지 레이아웃에 영향을 주지 않거나 영향이 매우 작은 경우 ng-if와 같은 태그가 기본적으로 문제를 해결할 수 있습니다. 그러나 사용자가 로그인하기 전과 후의 로컬 변경 등 페이지 상태에 따라 로컬 콘텐츠를 완전히 변경해야 하는 경우도 있습니다. 이는 로컬 레이아웃이 상당히 복잡할 수 있으며 독립적인 단위로 처리해야 함을 의미합니다. .
페이지에 부분 콘텐츠를 로드하는 문제를 해결하려면 ng-include를 사용하세요. 그러나 우리는 좀 더 복잡한 상황을 생각해 볼 수 있습니다. 이 페이지 조각에 해당하는 코드는 백엔드에서 동적으로 생성되며 html뿐만 아니라 js에도 해당 코드 조각에 해당하는 컨트롤러가 정의되어 있습니다. 이 경우 HTML을 동적으로 로딩하는 문제뿐만 아니라 컨트롤러를 동적으로 정의하는 문제도 고려해야 합니다. Controller는 Angle의 ControllerProvider의 Register 메소드를 통해 등록되므로 ControllerProvider의 인스턴스를 얻어야 합니다.
spa.js
app.config(['$locationProvider', '$routeProvider', '$controllerProvider', function($locationProvider, $routeProvider, $controllerProvider) { app.providers = { $controllerProvider: $controllerProvider //注意这里!!! }; /* 必须设置生效,否则下面的设置不生效 */ $locationProvider.html5Mode(true); /* 根据url的变化加载内容 */ $routeProvider.when('/test/lazyspa/page1', { /*!!!页面中引入动态内容!!!*/ template: '<div>page1</div><div ng-include="\'page1.html\'"></div>', controller: 'ctrlPage1' }).when('/test/lazyspa/page2', { template: '<div>page2</div>', }).otherwise({ template: '<div>main</div>', }); app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) { /* 用这种方式,ng-include配合,根据业务逻辑动态获取页面内容 */ /* !!!动态的定义controller!!! */ app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) { $scope.openAlert = function() { alert('page1 alert'); }; }]); /* !!!动态定义页面的内容!!! */ $templateCache.put('page1.html', '<div ng-controller="ctrlPage1Dyna"> <button ng-click="openAlert()">alert</button></div>'); }]); }]);
4. 동적 로딩 모듈
은 위의 하위 페이지를 채택합니다. 프래그먼트 로딩 방법에는 제한이 있습니다. 즉, 시작 모듈에 다양한 로직(js)을 추가해야 하며, 이로 인해 하위 페이지 프래그먼트의 독립적인 캡슐화에는 여전히 제한이 있습니다. 특히 서브페이지 프래그먼트에 타사 모듈을 사용해야 하는데 이 모듈이 시작 모듈에 미리 로드되지 않은 경우 해결책이 없습니다. 따라서 모듈을 동적으로 로드할 수 있어야 합니다. 모듈의 동적 로딩을 구현하는 것은 Angular 시작 중에 모듈을 로딩하는 방법을 추출한 다음 몇 가지 특수한 상황을 처리하는 것입니다.
그런데 실제로 실행해 보니 기사에 나온 코드에 문제가 있는 걸 발견했습니다. "$injector"가 정확히 뭔가요? Angle의 소스 코드 injector.js를 연구한 후 대략 무슨 일이 일어나고 있는지 파악했습니다.
애플리케이션에는 두 개의 $injector(ProviderInjector와 instanceInjector)가 있습니다. 호출큐는 공급자인젝터를 사용하고, runBlocks는 인스턴스프로바이더를 사용합니다. $injector를 잘못 사용하면 필요한 서비스를 찾을 수 있습니다.
routeProvider에서 모듈 파일의 동적 로드.
template: '<div ng-controller="ctrlModule1"><div>page2</div><div> <button ng-click="openDialog()">open dialog</button></div></div>', resolve: { load: ['$q', function($q) { var defer = $q.defer(); /* 动态加载angular模块 */ require(['/test/lazyspa/module1.js'], function(loader) { loader.onload && loader.onload(function() { defer.resolve(); }); }); return defer.promise; }] }
Angular 모듈의 동적 로딩
angular._lazyLoadModule = function(moduleName) { var m = angular.module(moduleName); console.log('register module:' + moduleName); /* 应用的injector,和config中的injector不是同一个,是instanceInject,返回的是通过provider.$get创建的实例 */ var $injector = angular.element(document).injector(); /* 递归加载依赖的模块 */ angular.forEach(m.requires, function(r) { angular._lazyLoadModule(r); }); /* 用provider的injector运行模块的controller,directive等等 */ angular.forEach(m._invokeQueue, function(invokeArgs) { try { var provider = providers.$injector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } catch (e) { console.error('load module invokeQueue failed:' + e.message, invokeArgs); } }); /* 用provider的injector运行模块的config */ angular.forEach(m._configBlocks, function(invokeArgs) { try { providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]); } catch (e) { console.error('load module configBlocks failed:' + e.message, invokeArgs); } }); /* 用应用的injector运行模块的run */ angular.forEach(m._runBlocks, function(fn) { $injector.invoke(fn); }); };
모듈 정의
module1.js
define(["angular"], function(angular) { var onloads = []; var loadCss = function(url) { var link, head; link = document.createElement('link'); link.href = url; link.rel = 'stylesheet'; head = document.querySelector('head'); head.appendChild(link); }; loadCss('//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css'); /* !!! 动态定义requirejs !!!*/ require.config({ paths: { 'ui-bootstrap-tpls': '//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min' }, shim: { "ui-bootstrap-tpls": { deps: ['angular'] } } }); /*!!! 模块中需要引用第三方的库,加载模块依赖的模块 !!!*/ require(['ui-bootstrap-tpls'], function() { var m1 = angular.module('module1', ['ui.bootstrap']); m1.config(['$controllerProvider', function($controllerProvider) { console.log('module1 - config begin'); }]); m1.controller('ctrlModule1', ['$scope', '$uibModal', function($scope, $uibModal) { console.log('module1 - ctrl begin'); /*!!! 打开angular ui的对话框 !!!*/ var dlg = '<div class="modal-header">'; dlg += '<h3 class="modal-title">I\'m a modal!</h3>'; dlg += '</div>'; dlg += '<div class="modal-body">content</div>'; dlg += '<div class="modal-footer">'; dlg += '<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>'; dlg += '<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>'; dlg += '</div>'; $scope.openDialog = function() { $uibModal.open({ template: dlg, controller: ['$scope', '$uibModalInstance', function($scope, $mi) { $scope.cancel = function() { $mi.dismiss(); }; $scope.ok = function() { $mi.close(); }; }], backdrop: 'static' }); }; }]); /* !!!动态加载模块!!! */ angular._lazyLoadModule('module1'); console.log('module1 loaded'); angular.forEach(onloads, function(onload) { angular.isFunction(onload) && onload(); }); }); return { onload: function(callback) { onloads.push(callback); } }; });
2. 전체 코드
index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta content="width=device-width,user-scalable=no,initial-scale=1.0" name="viewport"> <base href='/'> <title>SPA</title> </head> <body> <div ng-controller='ctrlMain'> <div> <a href="/test/lazyspa/page1">page1</a> <a href="/test/lazyspa/page2">page2</a> <a href="/test/lazyspa/">main</a> </div> <div ng-view></div> </div> <div class="loading"><div class='loading-indicator'><i></i></div></div> <script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js?_=3"></script> </body> </html>
spa-loader.js
window.loading = { finish: function() { /* 保留个方法做一些加载完成后的处理,我实际的项目中会在这里结束加载动画 */ }, load: function() { require.config({ paths: { "domReady": '/static/js/domReady', "angular": "//cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "//cdn.bootcss.com/angular.js/1.4.8/angular-route.min", }, shim: { "angular": { exports: "angular" }, "angular-route": { deps: ["angular"] }, }, deps: ['/test/lazyspa/spa.js'], urlArgs: "bust=" + (new Date()).getTime() }); } }; window.loading.load();
spa.js
'use strict'; define(["require", "angular", "angular-route"], function(require, angular) { var app = angular.module('app', ['ngRoute']); /* 延迟加载模块 */ angular._lazyLoadModule = function(moduleName) { var m = angular.module(moduleName); console.log('register module:' + moduleName); /* 应用的injector,和config中的injector不是同一个,是instanceInject,返回的是通过provider.$get创建的实例 */ var $injector = angular.element(document).injector(); /* 递归加载依赖的模块 */ angular.forEach(m.requires, function(r) { angular._lazyLoadModule(r); }); /* 用provider的injector运行模块的controller,directive等等 */ angular.forEach(m._invokeQueue, function(invokeArgs) { try { var provider = providers.$injector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } catch (e) { console.error('load module invokeQueue failed:' + e.message, invokeArgs); } }); /* 用provider的injector运行模块的config */ angular.forEach(m._configBlocks, function(invokeArgs) { try { providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]); } catch (e) { console.error('load module configBlocks failed:' + e.message, invokeArgs); } }); /* 用应用的injector运行模块的run */ angular.forEach(m._runBlocks, function(fn) { $injector.invoke(fn); }); }; app.config(['$injector', '$locationProvider', '$routeProvider', '$controllerProvider', function($injector, $locationProvider, $routeProvider, $controllerProvider) { /** * config中的injector和应用的injector不是同一个,是providerInjector,获得的是provider, 而不是通过provider创建的实例 * 这个injector通过angular无法获得,所以在执行config的时候把它保存下来 */ app.providers = { $injector: $injector, $controllerProvider: $controllerProvider }; /* 必须设置生效,否则下面的设置不生效 */ $locationProvider.html5Mode(true); /* 根据url的变化加载内容 */ $routeProvider.when('/test/lazyspa/page1', { template: '<div>page1</div><div ng-include="\'page1.html\'"></div>', controller: 'ctrlPage1' }).when('/test/lazyspa/page2', { template: '<div ng-controller="ctrlModule1"><div>page2</div><div> <button ng-click="openDialog()">open dialog</button></div></div>', resolve: { load: ['$q', function($q) { var defer = $q.defer(); /* 动态加载angular模块 */ require(['/test/lazyspa/module1.js'], function(loader) { loader.onload && loader.onload(function() { defer.resolve(); }); }); return defer.promise; }] } }).otherwise({ template: '<div>main</div>', }); }]); app.controller('ctrlMain', ['$scope', '$location', function($scope, $location) { console.log('main controller'); /* 根据业务逻辑自动到缺省的视图 */ $location.url('/test/lazyspa/page1'); }]); app.controller('ctrlPage1', ['$scope', '$templateCache', function($scope, $templateCache) { /* 用这种方式,ng-include配合,根据业务逻辑动态获取页面内容 */ /* 动态的定义controller */ app.providers.$controllerProvider.register('ctrlPage1Dyna', ['$scope', function($scope) { $scope.openAlert = function() { alert('page1 alert'); }; }]); /* 动态定义页面内容 */ $templateCache.put('page1.html', '<div ng-controller="ctrlPage1Dyna"> <button ng-click="openAlert()">alert</button></div>'); }]); require(['domReady!'], function(document) { angular.bootstrap(document, ["app"]); }); });
module1.js
'use strict'; define(["angular"], function(angular) { var onloads = []; var loadCss = function(url) { var link, head; link = document.createElement('link'); link.href = url; link.rel = 'stylesheet'; head = document.querySelector('head'); head.appendChild(link); }; loadCss('//cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css'); require.config({ paths: { 'ui-bootstrap-tpls': '//cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min' }, shim: { "ui-bootstrap-tpls": { deps: ['angular'] } } }); require(['ui-bootstrap-tpls'], function() { var m1 = angular.module('module1', ['ui.bootstrap']); m1.config(['$controllerProvider', function($controllerProvider) { console.log('module1 - config begin'); }]); m1.controller('ctrlModule1', ['$scope', '$uibModal', function($scope, $uibModal) { console.log('module1 - ctrl begin'); var dlg = '<div class="modal-header">'; dlg += '<h3 class="modal-title">I\'m a modal!</h3>'; dlg += '</div>'; dlg += '<div class="modal-body">content</div>'; dlg += '<div class="modal-footer">'; dlg += '<button class="btn btn-primary" type="button" ng-click="ok()">OK</button>'; dlg += '<button class="btn btn-warning" type="button" ng-click="cancel()">Cancel</button>'; dlg += '</div>'; $scope.openDialog = function() { $uibModal.open({ template: dlg, controller: ['$scope', '$uibModalInstance', function($scope, $mi) { $scope.cancel = function() { $mi.dismiss(); }; $scope.ok = function() { $mi.close(); }; }], backdrop: 'static' }); }; }]); angular._lazyLoadModule('module1'); console.log('module1 loaded'); angular.forEach(onloads, function(onload) { angular.isFunction(onload) && onload(); }); }); return { onload: function(callback) { onloads.push(callback); } }; });
위는 온디맨드 로딩 루틴_AngularJS를 완벽하게 구현하기 위해Angularjs requirejs를 탐색하는 내용입니다. 내용은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!