AngularJS Material 是基于 Material Design 设计方式,使用 AngularJS 实现的,该项目提供了一套基于 Material Design 系统设计的可重复使用的,易于测试的 UI 组件。它的 layout 使用的是 flex 来布局的,因此大家就知道了他的兼容性了,但是这个是未来的方向。
AngularJS Material
和 AngularJS UI Bootstrap 相比,AngularJS Material 组件更丰富,比如统一外观的 Select(下拉框)、移动设备系统才有的 Switch(开关)、Swipe(侧滑)、Bottom Sheet(底部弹出)等组件。AngularJS Material 交互性更好、动感更强,更适合做响应式 Web App。
看了一下 AngularJS Material 官网的 Demo,不管是 Dialog 还是 Panel 组件似乎缺少封装。如果你和我一样在项目中需要自定义 AngularJS Material 模态框,请往下看。
自定义模态框
我觉得模态框的头部是最需要封装的。想一想模态框头部是不是都是标题加关闭按钮。标题最好能接受父作用域设置的参数,头部以外的区域很多时候会变动,因此我们可以使用 transclude
将指令内部的元素嵌入到你的模板中去。看下面代码:
/**
* 模态窗指令:对应模态窗服务的视图
* @param dialogTitle {String} 设置障碍模态窗标题
*/
angular
.module('myApp')
.directive('riaDialog', function() {
return {
scope: {
dialogTitle:'@'
},
restrict: "EA",
transclude: true,
replace: true,
template: '<div class="dialog" role="dialog" aria-label="Dialog" layout="column" ng-cloak>\n' +
' <md-toolbar class="dialog-header">\n' +
' <div class="md-toolbar-tools" layout="row" layout-align="space-between center">\n' +
' <h4>{{::dialogTitle}}</h4>\n' +
' <md-button class="md-icon-button" title="Close"\n' +
' aria-label="Close dialog"\n' +
' ng-click="$parent.closeDialog();">\n' +
' <md-icon>\n' +
' <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fit="" preserveAspectRatio="xMidYMid meet" focusable="false">\n' +
' <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path>\n' +
' </svg>\n' +
' </md-icon>\n' +
' </md-button>\n' +
' </div>\n' +
' </md-toolbar>\n' +
' <ng-transclude></ng-transclude>\n' +
'</div>\n'
};
});
我们只对 riaDialog
指令设置了一个独立 Scope(作用域)属性 dialogTitle
。点击关闭按钮将会调用父作用域中的 closeDialog
方法,这种行为通常是统一的,因此我们用一个服务来封装它。看下面代码:
/**
* 模态窗服务
*/
angular
.module('myApp')
.factory('dialog', ["$mdPanel", function($mdPanel) {
return {
/**
* 打开带模板的模态窗
* @param templateUrl {String} 内容模块地址
* @param locals {Object} 指定共享数据
* @param clickOutsideToClose {Boolean} 点击模态窗外面关闭
*/
open: function(templateUrl, locals, clickOutsideToClose) {
var panelRef = null;
var position = $mdPanel.newPanelPosition()
.absolute()
.center();
var config = {
attachTo: 'body',
controller: ['$scope', function($scope) {
$scope.locals = locals;
$scope.closeDialog = function() {
panelRef && panelRef.close().then(function() {
panelRef.destroy();
});
}
}],
templateUrl: templateUrl,
hasBackdrop: true,
panelClass: 'ria-dialog',
position: position,
trapFocus: false,
zIndex: 40,
clickOutsideToClose: clickOutsideToClose || false,
escapeToClose: true,
focusOnOpen: true
};
return $mdPanel.open(config).then(function(mdPanelRef) {
panelRef = mdPanelRef;
});
}
}
}]);
dialog
服务是对 $mdPanel
的封装,只有一个 open
方法,本质上是对 $mdPanel.open
方法的调用。为什么不选用 dialog?个人觉得 mdPanel
更底层一点,配置更灵活。我们这里使用了三个参数,只有第一个是必选的。模态框关联的控制器可以对返回的 panelRef
的注入引用, 这个引用可以关闭、隐藏和显示模态框。模态框头部与嵌入内容是平级的,因此关闭按钮要调用 closeDialog
函数,加上 $parent
属性就行了,没必要再加一个独立作用域属性。
注意:由于 codepen 对文件的限制,本来是可以通过md-svg-icon
设置 svg 按钮图标,最终改成直接嵌入 svg 代码。还有本来中通过templateUrl
弹出动态内容,最终使用$templateCache
前将模板缓存到一个定义模板的 JavaScript 文件中,这样就不需要通过 XHR 来加载模板了。
打开模态框是一个异步的过程,因此如果模态框的内容没加载完,表单验证会有问题。这又是 AngularJS 人一个“坑”。怎样解决这一个问题呢?可以使用 angular.bootstrap 手动启动该 AngularJS 应用。
参考实例
访问 Codepen 查看代码及最终效果。
评论 (0)