如何使用angularjs處理動態菜單?
場景是這樣的:用戶登錄後,根據不同的用戶角色,展示不同的菜單。但angular本身一般只做靜態的路由和controller,導致無法動態生成菜單和菜單對應的路由url以及url對應的子頁面。在網上查了很多解決方案,很多都是用requirejs加angular-ui-router,但都不完整。如何才能做到「動態發請求取到菜單信息,動態生成菜單dom,動態生成路由,點擊菜單後動態載入controller和模板頁面」?各位有什麼好辦法嗎?如果有案例的最好能提供一下github地址。如果沒有方案,是不是我這種思路有問題,這種場景是傳統的jQuery負責的,angular並不應該處理這種場景?
--------------------------------------可能是我沒描述清楚。是這樣的,菜單對應url,指向一個頁面,這不就是路由應該處理的嗎?動態生成菜單意味著菜單的url對應的路由和頁面都得動態載入吧。
這個需求中,兩個關鍵點:
菜單所需代碼的動態載入
菜單路由的動態配置
這個地方,其實不是很有必要去按照ui-router或者什麼來配置路由,完全可以自己實現一個功能載入器:
$stateProvider
//動態菜單
.state("Menu", {
url: "/menu/:code",
templateUrl: "modules/menuloader.html"
});
然後這個menuloader.html裡面,只放一個ng-include,它的地址關聯到一個動態變數,這個變數根據傳入的那個code去讀取。
在這個state的resolve裡面,根據code獲取到菜單對應的html地址,js地址,然後用動態載入控制器的方式把js載入完成,然後把html地址賦值給上一段里提到的那個變數。
這樣,你就沒有定義多個路由,而是直接用一個路由的配置完成了所有的動態獲取過程,菜單以後可以任意無限加,不用改任何公共代碼。1. 既然你使用了 ui-router,說明你的應用是 單頁程序,既然是單頁程序,首先要考慮動態路由是否有必要?只要根據用戶角色 顯示該角色可以訪問的菜單即可,在每次路由切換的時候判斷下是否有訪問此路由的許可權,沒有就跳轉到指定頁面即可;
2. 至於你說的動態載入 是想根據用戶角色動態返回指定角色的路由、模板、controller js嗎?一般項目把所有的js和模板都打包壓縮成一個js,反而效果更好;3. 如果你非要想根據角色動態生成路由,可以在angular還沒有啟動的時候獲取該用戶角色的所有路由(也可以說菜單),然後循環菜單 通過 ui-router 動態加入即可;4. 這樣只有路由是動態載入了,關於每個路由對應的js,如果你也想實現 動態載入的話,估計就需要使用類似 requirejs 的東西,比如 marcoslin/angularAMD · GitHub 或者 atian25/angular-lazyload · GitHub 或者自己寫一個也可以,原理就是在 路由的 resolve 中載入對應的js5. 模板感覺就不需要動態載入了吧,因為使用ng,模板都是前端模板,如果要根據角色動態生成模板豈不是變成服務端渲染了。
說了這麼多 還是不建議使用 ng的時候採用動態載入技術,其中包括 動態載入js、模板、路由。 上面說的 3、4、5 我自己並沒有嘗試過,理論上應該是可以的,如果你非要做可以自己嘗試,想找完整的示例估計很難,因為很少人這麼做。這個問題我遇到過 我來說說我的解決方案
首先我的項目採用angularJS +requireJS + ui-router實現了 動態按需求載入html 和controller 以及動態配置路由 本人文筆有限 直接說我是怎樣實現的。首先在 mian.js中初始化模塊require(["domReady!",app],function( document){
angular.bootstrap(document, [myModule])
})
初始化模塊以後 需要重新註冊各項服務 關於為何要這樣 可以詳細看答案 各位大神已經詳細說明了
AngularJS按需動態載入template和controller?AngularJS按需動態載入template和controller? - 前端開發框架和庫
var app = angular.module("myModule", [ui.router]);
app.config(function($controllerProvider,$compileProvider,$filterProvider,$provide,$stateProvider){
app.register = {
//得到$controllerProvider的引用
controller : $controllerProvider.register,
//同樣的,這裡也可以保存directive/filter/service的引用
directive: $compileProvider.directive,
filter: $filterProvider.register,
service: $provide.service,
factory:$provide.factory,
stateProvider:$stateProvider
};
這個文件返回 app 對象, 在另外一個文件中單獨返回app.register , 這樣 其他需要使用這些服務的 就不用寫 app.register.controller("balabala 反正我是這樣寫的 。
define(["app"],function(app){ return app.register;
})
在另一個文件中寫了一個公共方法 作為ui-router 配置的公共調用方法
代碼如下這裡return 了一個routerState 方法 ,外部可以調用這個方法對其傳參 這個方法可以實現自動配置路由
關鍵是調用這個方法 這個是在register 裡面配置了的app.stateProvider.state()define(["config/appregister"],function(app) {
return {
routerState:function(state,url,ctrl,params) {
if(!angular.isString(state)||!angular.isString(url) || !angular.isString(ctrl)){
return
}
var strurl = ctrl;
var ctrlName = strurl.substring(strurl.lastIndexOf(/)+1);
//todo 字元串匹配校驗待做!
app.stateProvider.state(state,{
url:"/"+ state,
controller: ctrlName,
templateUrl: url,
resolve: {
loadCtrl: ["$q", function($q) {
var deferred = $q.defer();
//非同步載入controller/directive/filter/service
require([
ctrl
], function() { deferred.resolve(); });
return deferred.promise;
}]
}
})
}
}
})
這樣 寫好了公共文件 。現在加入我的業務分成幾個模塊
我要實現 當用戶 點擊具體模塊的時候再 載入器對應的模塊下的路由配置看到這裡其實就講完了 下面的嘮叨可以不看了 `-` `-` `-` `-` `-` `-` `-` `-` `-` `-` `-`
下面在業務模塊中寫了個方法具體來調用上面的方法 實現路由配置
我做的demo 目錄大概是這樣的
下面這個文件是我一個業務的模塊的主文件, 在載入這個主文件的時候 , 先調用上面的方法生成該業務下的路由 。代碼如下:先調用上面的routerState 方法 對其傳參 ,執行完以後就配置好了 。define([config/routerconfig],function(router){
router.routerState("routertest","app/business/home/partials/routertest.html","business/home/controllers/routertestCtrl");
})
define(["business/home/config/routerconfig",config/appregister],function(routercongfig,app){
app
.controller(localCtrl,function($scope,$state){
$scope.str = 作為主文件同時配置home 業務模塊下的細分模塊;
$scope.state = function(){
$state.go("routertest");
}
})
})
先寫到這兒 ,現在項目才立項 ,剛好昨天實現了這個 後面有優化方案 再來填坑吧
------------------------------------2016/8/13補充一點 ,
1 之前有朋友下載了我的demo 打包出現問題 解釋一下 ,因為angularJS控制器裡面是注入服務多數情況下不是嚴格的 ["$scope",function($scope){}] 按照這種方式來寫的 ,二是直接寫成 app.controller(function($scope,$q,balabala){}) , 這種推斷注入在項目運行時框架內可以識別 但是打包後 都變成 a ,b , c 了 所以最後會報注入錯誤, 項目打包的時候需要先用ngAnnotate 處理一下注入問題, 我是用的 gulp 工具做的處理 方法如下var gulp = require(gulp);var ngAnnotate = require(gulp-ng-annotate);gulp.task(default, function () {
return gulp.src(dashboard/app/**/*.js).pipe(ngAnnotate())
.pipe(gulp.dest(dist));}); 通過這個任務流處理以後 就可以將原來推斷注入全部轉換成顯式注入 然後呢 ,就可以愉快的打包了 。2 ,項目後期可以做通過控制路由來配置角色許可權 以及其他操作$rootScope.$on($stateChangeStart, function(event, toState, toParams, fromState, fromParams) {
$rootScope.routerToStateName = toState.name;
if(toState.name =="login"){
auth.setFormState(fromState.name);
}
if(fromState.name.indexOf("search")&>-1toState.name.indexOf("search")&<0){
$rootScope.GLOBALKEYWORD = ""
}
//鑒別當前角色是否具有路由訪問許可權
if( !auth.isAccessUrl(toState.name)){
$state.go("indexCtrl");
event.preventDefault();
}
。。。
如果動態菜單數量可控,可以用不同的頁面,然後登錄時候跳轉到相應頁面。這對緩存有好處
動態生產菜單我有個例子 https://github.com/hjzheng/angular-cuf-nav ,至於路由的話是提前配置好的,當然你可以用gulp插件生產配置。
這個跟路由有什麼關。。。
你居然問如何才能做到動態發請求取到菜單信息,難道不是ajax請求取得數據嗎?既然取得數據了,你居然還問,如果動態生成dom,你需要生成dom嗎?你都有數據了,ng-repeat不會嗎?你不是在用angular嗎?這種生成的動態菜單,可以點擊展開和收起嗎?使用了ng-repeat後,數據全部取到,但是無法跳轉,再問一個問題,如何在ng-repeat載入數據時,添加樣式,比如class名???
@小豬 同意你的說法呀。感覺完全沒必要麼。
ui-router提供傳入參數的,不能用參數來作區分嗎,一定要在路由的層面去解決?
推薦閱讀:
※angular中控制器之間的傳值該怎麼實現?
※關於ng-click中this的指向問題?
※angularjs中不同頁面controller中數據傳遞的問題?
※angularJS適不適合做互聯網金融產品?