Wenn Sie an einem Projekt einer bestimmten Größenordnung arbeiten, möchten Sie normalerweise die folgenden Ziele erreichen: 1. Unterstützung komplexer Seitenlogik (dynamische Anzeige von Inhalten basierend auf Geschäftsregeln, wie Berechtigungen, Datenstatus usw.); . Halten Sie sich an das Grundprinzip der Trennung von Front- und Back-End-Prinzip (wenn nicht getrennt, können Sie die Template-Engine verwenden, um gute Seiten direkt im Backend zu generieren). 3. Die Seitenladezeit ist kurz (wenn die Geschäftslogik komplex ist). , Sie müssen auf eine Bibliothek eines Drittanbieters verweisen, aber es ist sehr wahrscheinlich, dass die geladene Bibliothek nichts mit der aktuellen Operation des Benutzers zu tun hat) 4. Der Code muss leicht zu warten sein (beim Hinzufügen neuer Logik sollten möglichst wenige davon betroffen sein). Dateien wie möglich).
Um diese Ziele gleichzeitig zu erreichen, muss ein On-Demand-Lademechanismus vorhanden sein. Der auf der Seite angezeigte Inhalt und alle abhängigen Dateien können bei Bedarf entsprechend den Anforderungen der Geschäftslogik geladen werden. In letzter Zeit basiert die gesamte Entwicklung auf AngularJS. Daher konzentriert sich dieser Artikel hauptsächlich auf die verschiedenen von AngularJS bereitgestellten Mechanismen, um Möglichkeiten zur vollständigen Implementierung des On-Demand-Ladens zu untersuchen.
1. Schritt-für-Schritt-Implementierung
Grundlegende Ideen: 1. Entwickeln Sie zunächst eine Framework-Seite, die einige grundlegende Geschäftslogiken vervollständigen und Erweiterungsmechanismen unterstützen kann , und einige Logik muss in Unterseiten aufgeteilt werden, die bei Bedarf geladen werden. 3. Der Anzeigeinhalt auf den Unterseiten ist ebenfalls komplex geworden und muss bei Bedarf aufgeteilt und geladen werden Unterseiten sind komplex. Um sich auf externe Module zu verlassen, müssen Sie das Winkelmodul bei Bedarf laden.
1. Frame-Seite
Wenn es um das On-Demand-Laden im Frontend geht, fällt mir heutzutage AMD (Asynchronous Module Definition) ein. Es werden viele Anforderungen verwendet. Denken Sie also zunächst über die Einführung von Anforderungen nach.
index.html
<script src="static/js/require.js" defer async data-main="/test/lazyspa/spa-loader.js"></script>
Hinweis: Angular wird manuell gestartet, daher gibt es keine NG-App im HTML.
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. Unterseiten nach Bedarf laden
Angulars routeProvider + ng-view bietet bereits eine vollständige Methode zum Laden von Unterseiten, die direkt verwendet werden kann.
Beachten Sie, dass html5Mode eingestellt sein muss, da der routeProvider sonst die URL nach der Änderung nicht abfängt.
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. Laden Sie Inhalte in Unterseiten bei Bedarf
Verwendung Die Prämisse von routeProvider ist, dass sich die URL ändern muss, manchmal jedoch nur Teile der Unterseite. Wenn sich diese Änderungen hauptsächlich auf die gebundenen Daten beziehen und sich nicht auf das Seitenlayout auswirken oder die Auswirkungen sehr gering sind, können Tags wie ng-if das Problem grundsätzlich lösen. Manchmal muss der lokale Inhalt jedoch basierend auf dem Seitenstatus vollständig geändert werden, z. B. lokale Änderungen vor und nach der Benutzeranmeldung usw. Dies bedeutet, dass das lokale Layout recht komplex sein kann und als unabhängige Einheit behandelt werden muss .
Verwenden Sie ng-include, um das Problem des Ladens von Teilinhalten auf der Seite zu lösen. Wir können jedoch eine komplexere Situation betrachten. Der diesem Seitenfragment entsprechende Code wird vom Backend dynamisch generiert und ist nicht nur HTML, sondern auch js. Der dem Codefragment entsprechende Controller ist in js definiert. In diesem Fall muss nicht nur das Problem des dynamischen Ladens von HTML berücksichtigt werden, sondern auch das Problem der dynamischen Definition des Controllers. Der Controller wird über die Registermethode des ControllerProviders von Angular registriert. Daher ist es erforderlich, eine Instanz des ControllerProviders abzurufen.
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. Dynamisches Lademodul
verwendet die obige Unterseite fragment Es gibt eine Einschränkung bei der Lademethode, das heißt, dem Startmodul müssen verschiedene Logiken (js) hinzugefügt werden, was die unabhängige Kapselung von Unterseitenfragmenten immer noch einschränkt. Insbesondere wenn das Unterseitenfragment die Verwendung eines Drittanbietermoduls erfordert und dieses Modul nicht vorab im Startmodul geladen wird, gibt es keine Lösung. Daher ist es notwendig, Module dynamisch laden zu können. Um das dynamische Laden von Modulen zu implementieren, müssen Sie die Methode zum Laden von Modulen während des Angular-Starts extrahieren und dann einige spezielle Situationen behandeln.
Als ich es jedoch tatsächlich ausführte, stellte ich fest, dass es ein Problem mit dem Code im Artikel gab. Was genau ist „$injector“? Nachdem ich den Quellcode von Angular Injector.js studiert hatte, konnte ich ungefähr herausfinden, was los war.
Eine Anwendung verfügt über zwei $-Injektoren, ProviderInjector und InstanzInjector. invokeQueue verwendet ProviderInjector und runBlocks verwendet InstanzProvider. Bei falscher Verwendung von $injector wird der benötigte Dienst gefunden.
Dynamisches Laden von Moduldateien in 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; }] }
Dynamisches Laden von Winkelmodulen
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); }); };
Modul definieren
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. Vollständiger Code
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); } }; });
Das Obige ist der Inhalt der Untersuchung von AngularJS + Requirejs zur vollständigen Implementierung der On-Demand-Laderoutine_AngularJS und Weitere Informationen zu verwandten Inhalten finden Sie auf der chinesischen PHP-Website (www.php.cn)!