背景
AngularJS 誕生於2009年,由Misko Hevery 等人建立,後為Google所收購。是一款優秀的前端JS框架,已經被用於Google的多款產品當中。
最近搗鼓一個前端專案,用的就是這個框架,其中有大量的在路由過程中使用 resolve 配置的程式碼,到底這個屬性是什麼作用,煩擾了好幾天終於弄清楚了它的作用和流程。
reslove 配置項resolve 在 state 配置引數中,是一個物件 (key-value) ,每一個 value 都是一個可以依賴注入的函式,並且返回一個物件。
配置項中的各個方法將在 Controller 控制器被例項化之前執行完成。接下來我們看看使用 resolve 的兩種常見場景。
AngularJS路由語法使用 AngularJS 實現簡單頁面導航,離不開 $stateProvider 的路由配置,而路由配置時,如果我們需要動態新增路由頁面所依賴的控制器,或者控制器例項化時的一些引數需要注入的話,我們就可以使用 resolve 來完成。
首先,來看看動態載入依賴的用法,demo 的主頁面 index.html 中的路由配置為:
"use strict";//index.html頁面的導航路由配置//關注$stateProvider.state的resolve:添加了對控制器模組的依賴define(['admin-app'], function (adminApp) { console.log('載入 adminAppRouter.js'); /* 定義admin app 的路由 */ adminApp.config(['$stateProvider', '$urlRouterProvider', function ($stateProvider, $urlRouterProvider) { console.log('adminAppRouter... 路由配置'); //路由配置 $urlRouterProvider.otherwise('/myhome'); //1、ui-sref對應state的第一個引數即名稱 //2、請求URL路徑,這裡沒有引數,如果有引數則依次用&連線;例如:/viewshome?id&name&age //3、templateUrl,與請求URL對應的需要載入的頁面 //4、resolve屬性,用於延遲載入,它使用了$q服務,返回一個Promise物件,這裡並沒有使用 //5、控制器程式碼在templateUrl頁面載入時會執行,完成資料繫結過程 $stateProvider .state('myhome',{ url: '/myhome', templateUrl: 'page/my_home.tpl.html', controller: 'MyHomeController', controllerAs: 'ctrl', resolve: { //新增動態依賴時,需要提供返回一個promise的物件,下面這裡應該是通用的邏輯。 loadJs: ['$q', '$rootScope', function($q, $rootScope) { console.log("resolve loadJs define.") //建立一個deferred物件 var defer = $q.defer(); //呼叫定義好的viewsHomeController.js模組 console.log('adminAppRouter... 路由配置 resolve start.'); require(['viewsHome-controller'], function() { //scopt.$apply方法是在資料發生變化後更新到繫結的頁面元素上 $rootScope.$apply(function() { console.log('scope apply resolve start .'); defer.resolve(); console.log('scope apply resolve end .'); }); //即使註釋掉上面的apply,但是放在這個非同步回撥裡面,能夠正確渲染頁面 defer.resolve(); console.log('adminAppRouter... 路由配置 viewsHome-controller function'); }); //將defer.resolve();放在此處,能夠載入頁面,但是資料得不到渲染,異常報錯Controller方法未定義 console.log('adminAppRouter... 路由配置 resolve finish.'); return defer.promise; }] } }) .state('otherHome',{ url: '/otherHome', templateUrl: 'views/other_home.html', controller: 'OtherHomeController', resolve: { load: ['$q', '$rootScope', function($q, $rootScope) { var defer = $q.defer(); require(['other-controller'], function() { $rootScope.$apply(function() { defer.resolve(); }); }); return defer.promise; }] } }) } ])})
關注 resolve 的 load 定義,這裡使用 require 動態載入了路由頁面依賴的控制器定義檔案,並在其回撥函式中執行資料監控,該函式返回一個 Promise 物件。
那麼這個 load 方法是怎麼被使用的呢?
路由過程是這樣的:
1、執行 resolve 中的每個方法,並將各個方法的結果儲存到集合中。2、resolve中 所有方法執行完成後,開始例項化 controller 物件3、例項化 controller 物件的時候會使用 resolve 的結果集作為引數來例項化。這裡我們使用 resolve 只是希望在真正訪問某個 html 頁面時再載入其依賴,load 方法的返回值並不需要作為控制器的引數,所以這個方法名稱可以隨便定義。解決控制器注入問題如果一個控制器的引數資料需要外界注入,那麼就可以透過 resolve 提供這些引數注入工廠完成。例如,我們定義一個 Controller 類,依賴一個 userData 引數和personInfo:
previewApp.controller('MyController', ['$scope', '$state', 'userData','personInfo',MyController]);function MyController($scope, $state, $stateParams,userData,personInfo){}
那麼我們在使用 MyController 之前,必須解決這兩個引數的注入問題:
resolve: { load: ['$q', '$rootScope', function($q, $rootScope) { var defer = $q.defer(); require(['other-controller'], function() { $rootScope.$apply(function() { defer.resolve(); }); }); return defer.promise; }], userData:function(){ return {name:'wang',age:10} }, personInfo:function(){ return "Hello"; } }})
resolve 配置中需要提供一個與控制器引數名稱一樣的屬性,其值是一個工廠方法返回一個數據,那麼最終該工廠資料會作為引數傳遞給 Controller。
這種需要注入控制器依賴資料的情況下提供的工廠方法名稱必須跟引數名稱一致,就不能瞎起名了。
啟示錄resolve 的作用是資料預準備,只有資料準備好了之後才會觸發 $stateChangeSuccess事件進行切換路由操作,接著例項化 Controller,然後更新模板內容。