如何使用angularjs处理动态菜单

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用angularjs处理动态菜单相关的知识,希望对你有一定的参考价值。

1. 既然你使用了 ui-router,说明你的应用是 单页程序,既然是单页程序,首先要考虑动态路由是否有必要?只要根据用户角色 显示该角色可以访问的菜单即可,在每次路由切换的时候判断下是否有访问此路由的权限,没有就跳转到指定页面即可;
2. 至于你说的动态加载 是想根据用户角色动态返回指定角色的路由、模板、controller js吗?一般项目把所有的js和模板都打包压缩成一个js,反而效果更好;
3. 如果你非要想根据角色动态生成路由,可以在angular还没有启动的时候获取该用户角色的所有路由(也可以说菜单),然后循环菜单 通过 ui-router 动态加入即可;
4. 这样只有路由是动态载入了,关于每个路由对应的js,如果你也想实现 动态载入的话,估计就需要使用类似 requirejs 的东西,比如 marcoslin/angularAMD · GitHub 或者 atian25/angular-lazyload · GitHub 或者自己写一个也可以,原理就是在 路由的 resolve 中加载对应的js
5. 模板感觉就不需要动态加载了吧,因为使用ng,模板都是前端模板,如果要根据角色动态生成模板岂不是变成服务端渲染了。
参考技术A 使用angularjs处理动态菜单:
$stateProvider
//动态菜单
.state("Menu",
url: "/menu/:code",
templateUrl: "modules/menuloader.html"
);

然后这个menuloader.html里面,只放一个ng-include,它的地址关联到一个动态变量,这个变量根据传入的那个code去读取。

在这个state的resolve里面,根据code获取到菜单对应的html地址,js地址,然后用动态加载控制器的方式把js加载完成,然后把html地址赋值给上一段里提到的那个变量。
参考技术B

用ng-repeat啊

举个例子( 只截取有用的部分)

这样子 菜单条目的多少就跟你的数组元素相关了 随你改变


<body ng-app="myApp" ng-controller="myCtrl">
<ul>
<li ng-repeat="x in records">x</li>
</ul>
<script>
var app = angular.module("myApp", []);
app.controller("myCtrl", function($scope) 
  $scope.records = [
    "菜鸟教程1",
    "菜鸟教程2",
    "菜鸟教程3",
    "菜鸟教程4",
  ]
);
</script>

</body>

参考技术C 这个需求中,两个关键点:
1.菜单所需代码的动态加载

2.菜单路由的动态配置
这个地方,其实不是很有必要去按照ui-router或者什么来配置路由,完全可以自己实现一个功能加载器:
$stateProvider

//动态菜单
.state("Menu",
url: "/menu/:code",
templateUrl: "modules/menuloader.html"
);

然后这个menuloader.html里面,只放一个ng-include,它的地址关联到一个动态变量,这个变量根据传入的那个code去读取。

在这个state的resolve里面,根据code获取到菜单对应的html地址,js地址,然后用动态加载控制器的方式把js加载完成,然后把html地址赋值给上一段里提到的那个变量。

这样,你就没有定义多个路由,而是直接用一个路由的配置完成了所有的动态获取过程,菜单以后可以任意无限加,不用改任何公共代码。
参考技术D 1. 首先来思考第一个问题,如何在前端进行初始值的回填

多级联动菜单最明显的特点是,上一级菜单更改后,下一级菜单会被(同步或异步地)重新渲染。在回填值的过程中,我们需要逐级回填,无法在页面加载时
(或路由加载或组件加载等等)时瞬间完成该过程。尤其在AngularJS中,option的渲染过程应该发生在ngModel的渲染之前,否则即使
option中有对应值,也会造成找不到匹配option的情况。
解决方案是在指令的link阶段,首先保存model的初始值,并将其赋为空值(可以调用$setViewValue),并在渲染完成后再异步地对其赋回原值。

2. 如何解耦子选项获取的具体逻辑,并同时支持同步、异步的方式

可以使用scope中的"="类属性,将一个外部函数暴露到directive的link方法中。每次在执行该方法后,判断其是否为promise
实例(或是否有then方法),根据判断结果决定同步或异步渲染。通过这样的解耦,使用者就可以在传入的外部函数中轻松地决定渲染方式了。为了使回调函数
不那么难看,我们还可以将同步返回也封装为一个带then方法的对象。如下所示:

?

1
2
3
4
5
6
7
8
9
10
11

// scope.source为外部函数
var returned = scope.source ? scope.source(values) : false;
!returned || (returned = returned.then ? returned :
then: (function (data)
return function (callback)
callback.call(window, data);
;
)(returned)
).then(function (items)
// 对同步或异步返回的数据进行统一处理


3. 如何实现菜单间基于事件的通信

大体上还是通过订阅者模式实现,需要在directive上声明依赖;由于需要支持复杂的依赖关系,应该支持一个子集菜单同时有多个依赖。这样在任何一个所依赖的菜单变化时,我们都可以通过如下方式进行监听:

?

1
2
3
4
5
6
7
8
9
10
11
12
13

scope.$on('selectUpdate', function (e, data)
// data.name是变化的菜单,dependents是当前菜单所声明的依赖数组
if ($.inArray(data.name, dependents) >= 0)
onParentChange();

);
// 并且为了方便上文提到的source函数对于变动值的调用,可以对所依赖的菜单进行遍历并保存当前值
var values = ;
if (dependents)
$.each(dependents, function (index, dependent)
values[dependent] = selects[dependent].getValue();
);


4. 处理两类过期问题

容易想到的是异步过期的问题:设想第一级菜单发生变化,触发对第二级菜单内容的拉取,但网速较慢,该过程需要3秒。1秒后用户再次改变第一级菜单,
再次触发对第二级菜单内容的拉取,此时网速较快,1秒后数据返回,第二级菜单重新渲染;但是1秒后,第一次请求的结果返回,第二级菜单再次被渲染,但事实
上第一级菜单此后已经发生过变化,内容已经过期,此次渲染是错误的。我们可以用闭包进行数据过期校验。
不容易想到的是同步过期(其实也是异步,只是未经io交互,都是缓冲时间为0的timeout函数)的问题,即由于事件队列的存在,稍不谨慎就可能出现过期,代码中会有相关注释。

5. 支持空值选项的细节问题

对于空值的支持本来觉得是一个很简单的问题,<option value=""
ng-if="empty">empty</option>即可,但实际编码中发现,在directive的link中,由于
此option的link过程并未开始,option标签被实际上移除,只剩下相关注释占位。AngularJS认为该select不含有空值选项,于是
报错。解决方案是弃用ng-if,使用ng-show。这二者的关系极其微妙有意思,有兴趣的同学可以自己研究~

以上就是编码过程中遇到的主要问题,欢迎交流~

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

directive('multiLevelSelect', ['$parse', '$timeout', function ($parse, $timeout)
// 利用闭包,保存父级scope中的所有多级联动菜单,便于取值
var selects = ;
return
restrict: 'CA',
scope:
// 用于依赖声明时指定父级标签
name: '@name',
// 依赖数组,逗号分割
dependents: '@dependents',
// 提供具体option值的函数,在父级change时被调用,允许同步/异步的返回结果
// 无论同步还是异步,数据应该是[text: 'text', value: 'value',]的结构
source: '=source',
// 是否支持控制选项,如果是,空值的标签是什么
empty: '@empty',
// 用于parse解析获取model值(而非viewValue值)
modelName: '@ngModel'
,
template: ''
// 使用ng-show而非ng-if,原因上文已经提到
+ '<option ng-show="empty" value="">empty</option>'
// 使用朴素的ng-repeat
+ '<option ng-repeat="item in items" value="item.value">item.text</option>',
require: 'ngModel',
link: function (scope, elem, attr, model)
var dependents = scope.dependents ? scope.dependents.split(',') : false;
var parentScope = scope.$parent;
scope.name = scope.name || 'multi-select-' + Math.floor(Math.random() * 900000 + 100000);
// 将当前菜单的getValue函数封装起来,放在闭包中的selects对象中方便调用
selects[scope.name] =
getValue: function ()
return $parse(scope.modelName)(parentScope);

;
// 保存初始值,原因上文已经提到
var initValue = selects[scope.name].getValue();
var inited = !initValue;
model.$setViewValue('');
// 父级标签变化时被调用的回调函数
function onParentChange()
var values = ;
// 获取所有依赖的菜单的当前值
if (dependents)
$.each(dependents, function (index, dependent)
values[dependent] = selects[dependent].getValue();
);

// 利用闭包判断io造成的异步过期
(function (thenValues)
// 调用source函数,取新的option数据
var returned = scope.source ? scope.source(values) : false;
// 利用多层闭包,将同步结果包装为有then方法的对象
!returned || (returned = returned.then ? returned :
then: (function (data)
return function (callback)
callback.call(window, data);
;
)(returned)
).then(function (items)
// 防止由异步造成的过期
for (var name in thenValues)
if (thenValues[name] !== selects[name].getValue())
return;


scope.items = items;
$timeout(function ()
// 防止由同步(严格的说也是异步,注意事件队列)造成的过期
if (scope.items !== items) return;
// 如果有空值,选择空值,否则选择第一个选项
if (scope.empty)
model.$setViewValue('');
else
model.$setViewValue(scope.items[0].value);

// 判断恢复初始值的条件是否成熟
var initValueIncluded = !inited && (function ()
for (var i = 0; i < scope.items.length; i++)
if (scope.items[i].value === initValue)
return true;


return false;
)();
// 恢复初始值
if (initValueIncluded)
inited = true;
model.$setViewValue(initValue);

model.$render();
);
);
)(values);

// 是否有依赖,如果没有,直接触发onParentChange以还原初始值
!dependents ? onParentChange() : scope.$on('selectUpdate', function (e, data)
if ($.inArray(data.name, dependents) >= 0)
onParentChange();

);
// 对当前值进行监听,发生变化时对其进行广播
parentScope.$watch(scope.modelName, function (newValue, oldValue)
if (newValue || '' !== oldValue || '')
scope.$root.$broadcast('selectUpdate',
// 将变动的菜单的name属性广播出去,便于依赖于它的菜单进行识别
name: scope.name
);

);

;
]);
第5个回答  2016-06-15 发送ajax请求随时获取数据不可以么?用angularjs的$http

以上是关于如何使用angularjs处理动态菜单的主要内容,如果未能解决你的问题,请参考以下文章

c#中如何动态添加菜单项并实现其点击?

如何使用动态母版页为每个用户制作自定义菜单?

如何使用 AngularJS 和 CSS 滑动切换列表元素

如何在Qt中动态添加菜单

使用 angularjs 动态创建控件

Bootstrap + Angular动态菜单在展开后不折叠