摘要:想同時實現這些目標,就必須有一套按需加載的機制,頁面上展現的內容和所有需要依賴的文件,都可以根據業務邏輯需要按需加載。最近都是基于做開發,所以本文主要圍繞提供的各種機制,探索全面實現按需加載的套路。注意必須設置,否則變化以后,不截獲。
在進行有一定規模的項目時,通常希望實現以下目標:1、支持復雜的頁面邏輯(根據業務規則動態展現內容,例如:權限,數據狀態等);2、堅持前后端分離的基本原則(不分離的時候,可以在后端用模版引擎直接生成好頁面);3、頁面加載時間短(業務邏輯復雜就需要引用第三方的庫,但很可能加載的庫和用戶本次操作沒關系);4,還要代碼好維護(加入新的邏輯時,影響的文件盡量少)。
想同時實現這些目標,就必須有一套按需加載的機制,頁面上展現的內容和所有需要依賴的文件,都可以根據業務邏輯需要按需加載。最近都是基于angularjs做開發,所以本文主要圍繞angularjs提供的各種機制,探索全面實現按需加載的套路。
一、一步一步實現基本思路:1、先開發一個框架頁面,它可以完成一些基本的業務邏輯,并且支持擴展的機制;2、業務邏輯變復雜,需要把部分邏輯拆分到子頁面中,子頁面按需加載;3、子頁面中的展現內容也變了復雜,又需要進行拆分,按需加載;4、子頁面的內容復雜到依賴外部模塊,需要按需加載angular模塊。
1、框架頁提到前端的按需加載,就會想到AMD( Asynchronous Module Definition),現在用requirejs的非常多,所以首先考慮引入requires。
index.html
注意:采用手動啟動angular的方式,因此html中沒有ng-app。
spa-loader.js
require.config({ paths: { "domReady": "/static/js/domReady", "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "http://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,否則url變化以后,routeProvider不截獲。
index.html
spa.js
app.config(["$locationProvider", "$routeProvider", function($locationProvider, $routeProvider) { /* 必須設置生效,否則下面的設置不生效 */ $locationProvider.html5Mode(true); /* 根據url的變化加載內容 */ $routeProvider.when("/test/lazyspa/page1", { template: "3、按需加載子頁面中的內容page1", }).when("/test/lazyspa/page2", { template: "page2", }).otherwise({ template: "main", }); }]);
用routeProvider的前提是url要發生變化,但是有的時候只是子頁面中的局部要發生變化。如果這些變化主要是和綁定的數據相關,不影響頁面布局,或者影響很小,那么通過ng-if一類的標簽基本就解決了。但是有的時候要根據頁面狀態,完全改變局部的內容,例如:用戶登錄前和登錄后局部要發生的變化等,這就意味著局部的布局可能也挺復雜,需要作為獨立的單元來對待。
利用ng-include可以解決頁面局部內容加載的問題。但是,我們可以再考慮更復雜一些的情況。這個頁面片段對應的代碼是后端動態生成的,而且不僅僅有html還有js,js中定義了代碼片段對應的controller。這種情況下,不僅僅要考慮動態加載html的問題,還要考慮動態定義controller的問題。controller是通過angular的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: "4、動態加載模塊page1", controller: "ctrlPage1" }).when("/test/lazyspa/page2", { template: "page2", }).otherwise({ template: "main", }); 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", ""); }]); }]);
采用上面子頁面片段的加載方式存在一個局限,就是各種邏輯(js)要加入到啟動模塊中,這樣還是限制子頁面片段的獨立封裝。特別是,如果子頁面片段需要使用第三方模塊,且這個模塊在啟動模塊中沒有事先加載時,就沒有辦法了。所以,必須要能夠實現模塊的動態加載。實現模塊的動態加載就是把angular啟動過程中加載模塊的方式提取出來,再處理一些特殊情況。
動態加載模塊深入分析可以參考這篇文章:
http://www.tuicool.com/articles/jmuymiE
但是,實際跑起來發現文章中的代碼有問題,就是“$injector”到底是什么?研究了angular的源代碼injector.js才大概搞明白是怎么回事。
一個應用有兩個$injector,providerInjector和instanceInjector。invokeQueue和用providerInjector,runBlocks用instanceProvider。如果$injector用錯了,就會找到需要的服務。
routeProvider中動態加載模塊文件。
template: "", 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; }] }page2
動態加載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("http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css"); /* !!! 動態定義requirejs !!!*/ require.config({ paths: { "ui-bootstrap-tpls": "http://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 = "二、完整的代碼"; dlg += ""; dlg += "I"m a modal!
"; dlg += "content"; dlg += " "; $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); } }; });
index.html
SPA
spa-loader.js
window.loading = { finish: function() { /* 保留個方法做一些加載完成后的處理,我實際的項目中會在這里結束加載動畫 */ }, load: function() { require.config({ paths: { "domReady": "/static/js/domReady", "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "http://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: "page1", controller: "ctrlPage1" }).when("/test/lazyspa/page2", { template: "", 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: "page2main", }); }]); 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", ""); }]); 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("http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css"); require.config({ paths: { "ui-bootstrap-tpls": "http://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 = "寫后感"; dlg += ""; dlg += "I"m a modal!
"; dlg += "content"; dlg += " "; $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); } }; });
年初定下的目標是堅持每周寫一篇自己在開發過程碰到的問題總結,本以為是個簡單的事情,寫起來才發現寫文章的時間比寫代碼的花的時間還要長。因為寫代碼的時候只要功能實現了就行了,但是,寫文章的時候就一定要把代碼搞清楚才敢寫,實際上就是逼著自己要認真研究源代碼,雖然壓力很大,但收獲更大。另一方面,發現找到一個好題目挺難的,只是簡單的貼別人的代碼沒意思,可是自己想出來有價值,有意思的問題挺難的。因此大家要是覺得有啥有意思,有價值前端問題,分享一下吧,給我的年度寫作計劃幫幫忙
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78737.html
摘要:現在微軟終于痛定思痛決定放棄了不支持的安全更新,對我們前端來說,真的是重大利好啊言歸正傳,這篇文章的目的就是把怎么用構建一個單頁面程序介紹以下,是對自己的一個總結,也喜歡對大家有一定的借鑒作用,寫的不好不對的地方希望大家多評論評論謝謝。 這篇文章是寫在公司項目結束之后的,因為我個人不太會把沒有實踐過的東西寫出來,實踐是檢驗真理的唯一標準么,用的怎么樣,好不好用,在成熟實踐過的項目上能體...
摘要:最近一段時間在學習,由于覺得直接使用它需要加載很多的文件,因此想使用來實現異步加載,并動態注入控制器。手動啟動,特別說明此處的不是那個框架,而是的一個手動啟動框架的函數中完成了各模塊的初始化,并且引入了。 最近一段時間在學習angularjs,由于覺得直接使用它需要加載很多的js文件,因此想使用requirejs來實現異步加載,并動態注入控制器。簡單搜索了下發現好多教程寫的都很復雜,所...
摘要:的成功離開不這三個東西,分層架構,路由系統,儲存系統。分層架構是我們組織復雜代碼的關鍵,路由系統是將多個頁面壓縮在一個頁面的關鍵。在這個種子工程中,我都調用了同一個方法,就比較適合目錄動態生成,需要按需調用不同的頁面的情況。 SPA的成功離開不這三個東西,分層架構,路由系統,儲存系統。分層架構是我們組織復雜代碼的關鍵,路由系統是將多個頁面壓縮在一個頁面的關鍵。 其中avalon路由用到...
摘要:特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 特意對前端學習資源做一個匯總,方便自己學習查閱參考,和好友們共同進步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入匯總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應和斧正,會及時更新,平時業務工作時也會不定期更...
摘要:目前已經在大大小小多個線上產品中使用了,也收集了一些有效的建議好了,該看下一個最簡單的組件長什么樣吧免費領取驗證碼內容安全短信發送直播點播體驗包及云服務器等套餐更多網易技術產品運營經驗分享請訪問網易云社區。文章來源網易云社區 本文由作者鄭海波授權網易云社區發布。 此文摘自regularjs的指南, 目前指南正在全面更新, 把老文檔的【接口/語法部分】統一放到了獨立的 Reference...
閱讀 659·2023-04-25 15:49
閱讀 3116·2021-09-22 15:13
閱讀 1251·2021-09-07 10:13
閱讀 3477·2019-08-29 18:34
閱讀 2560·2019-08-29 15:22
閱讀 510·2019-08-27 10:52
閱讀 687·2019-08-26 18:27
閱讀 3021·2019-08-26 13:44