前言
除了 AngularJS 内置的指令外,我们还可以创建自定义指令。
通过 .directive() 函数来添加自定义的指令。
调用自定义指令时,需要在HTMl 元素上添加自定义指令名。
自定义指令命名规则:使用驼峰命名法来命名,即除第一个单词外的首字母需大写。如: myDirective。
在html页面调用该指令时需要以 - 分割,如: my-directive。示例代码:
<body ng-app="myApp"> <my-directive></my-directive> <script> var app = angular.module("myApp", []); app.directive("myDirective", function() { return { template : "<h1>模板:可以写自己的html页面代码</h1>" }; }); </script> </body>
html页面调用自定义指令的四种方式
通过在自定义指令里添加 restrict 属性,根据设置不同的值来决定html页面的调用方式,如:
var app = angular.module("myApp", []); app.directive("myDirective", function() { return { restrict : "A",//只能通过属性调用 template : "<h1>自定义指令!</h1>" }; });
restrict值的不同,决定了调用方式的不同
属性值
调用方式
示例
A (Attribute首字母)
属性名
<div my-directive></div>
C (Class 首字母)
类名
<div class='my-directive'></div>
E (Element 首字母)
元素名
<my-directive></my-directive>
M
注释
<!-- 指令: my-directive>
restrict 默认值为 EA, 即在html页面可通过元素名和属性名来调用自定义指令。
自定义指令属性详解
属性
值类型
说明
restrict
string
指令的调用方式,A、C、E、M
priority
number
指令执行的优先级
template
string
指令使用的模板,可将html页面代码写于此。只能与templateUrl二选其一
templateUrl
string
从指定的url地址加载模板。只能与template二选其一
replace
boolean
是否用模板替换当前元素。true : 将指令标签替换成temple中定义的内容,页面上不会再有<my-directive>标签;false :则append(追加)在当前元素上,即模板的内容包在<my-directive>标签内部。默认false。
transclude
boolean
是否将当前元素的内容转移到模板中
scope
boolean /object
指定指令的作用域。false(默认值): 使用父作用域作为自己的作用域(每个引用自定义指令的标签若其中一个标签改变某一变量值,则会影响其他标签的值 )。true: 新建一个作用域,该作用域继承父作用域(两个引用自定义指令的标签之间的变量互不影响)。JavaScript对象:与父作用域隔离,并指定可以从父作用域访问的变量
controller
function
定义与其他指令进行交互的接口函数
require
string
指定需要依赖的其他指令
link
function
以编程的方式操作DOM,包括添加监听器等
compile
function
编程的方式修改DOM模板的副本,可以返回链接函数
对表格里的知识进行延伸
1.templateUrl
如果template里拼写的html页面代码十分的多页复杂,拼字符串的话就太麻烦啦,这里我们就可以选择templateUrl。我们可以将要拼写的html页面代码独立到一个页面里,如template.html;然后再指定该html文件所在的路径即可,如templateUrl:”template.html”。用到该自定义指令时,会自动发一个http请求来获取template.html对应的模板内容。这样做的缺点是,多了一个http请求。别急,可以改进的:
angularjs规定了模板还可以用<Script>标签定义:
<script type="text/ng-template" id="template.html"> <div>自定义指令模板用Script标签定义的方式,须放在html页面ng-controller指令所在标签的内部</div> </script>
上面代码写在html页面的ng-controller指令所在标签的里面,这样就不用再去请求它了。示例:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="/UploadFiles/2021-04-02/angular.min.js">有多个模板时,我们可以将所有的模板集中在一个文件中,只需加载一次,然后根据id的不同调用不同的模板。
2.transclude
定义是否将当前元素(html页面的自定义指令)的内容转移到模板中。
模板中要接收当前元素内容的标签需要使用ng-transclude指令。<body > <div ng-app="myApp" ng-controller="myController"> <!-- 引用自定义指令 --> <my-directive>自定义指定令内容</my-directive> <!-- 模板代码 --> <script type="text/ng-template" id="template.html"> <div> 模板内容</div> <div ng-transclude></div>//模板接收上面自定义指令间的内容 </script> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope) { }); //创建自定义指令 app.directive("myDirective", function() { return { templateUrl : "template.html", transclude : true//转移到模板中 }; }); </script> </body>3.什么是scope的父作用域
引用自定义指令的html页面的控制器所能控制的范围。下面代码的父作用域就是myController所控制的范围
<body > <div ng-app="myApp" ng-controller="myController"> <my-directive></my-directive><!-- 引用自定义指令 --> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope){ }); //创建自定义指令 app.directive("myDirective", function() { return { template : "<h1>自定义指令!</h1>" }; }); </script> </body>4.scope属性的值是对象(object)时的用法
AngularJS内置指令的用法:ng-model=”obj”,通过obj这个变量双向绑定值,controller里变了,html页面也跟着变化。这说明,内置指令不仅可作为属性,还可动态改变值,这个要是不懂的,看看基础语法。如下代码:
<div ng-app="myApp" ng-controller="myController"> 要动态变化的内容: <input ng-model="obj"> </div> <script> var app = angular.module('myApp', []); app.controller('myController', function($scope) { $scope.obj = "这个字符串值会同步到html里"; }); </script>自定义指令当然也需要实现这种功能啦。scope属性为对象时,可为自定义指令指定一个可以绑定值的属性。这里还得说明一下的是,这个scope属性与自定义指令里link属性里的scope参数是一个变量。
<!-- =符号的用法--> <body > <div ng-app="myApp" ng-controller="myController"> <!-- 引用自定义指令:obj变量与控制器里的objc变量双向绑定了值 --> <my-directive speak="obj"></my-directive> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope) { $scope.obj="父作用域";//父作用域给自定义指令属性赋的值 }); //创建自定义指令 app.directive("myDirective", function() { return { template : "<p>模板内容</p>", scope:{ title:"=speak"//定义一个speak属性供html页面的自定义指令用。如果写成title:"="格式,则自定义指令里的属性名就是title。 }, link: function postLink(scope, iElement, iAttrs) { console.log(scope.title)//这里打印的值与控制器里的值一样 } }; }); </script> </body>有了前面的一个示例,下面再来说说绑定策略:即用符号前缀给自定义指令传值。它是一个键值对,键是在自定义指令中使用的,值里符号后面的字符串是html页面自定义指令的属性名;如果值里只有符号,则html页面自定义指令的属性名就是键名。
符号 说明 示例 @ 值传递,单向绑定。html页面自定义指令里的val属性的值可传给link的scope使用。第一种写法——str : “@”,这种写法html页面的指令属性名为str str : “@val”,属性名为val = 双向绑定数据到指令的属性中,数据值可以是任意类型的。第一种写法:name : “=”,这种写法html页面的自定义指令属性名就是name name : “=username”,属性名是username & 使用父作用域中的一个函数,可以在指令中调用。第一种写法:getName:”&”,这种写法html页面的自定义指令属性名就是gegName getName : “&getUserName”,属性名是getUserName其余两种符号用法:
<!-- @符号的用法 --> <body > <div ng-app="myApp" ng-controller="myController"> <!-- 引用自定义指令 --> <my-directive title="obj" str="abcd">自定义指定令的内容555</my-directive> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope) { $scope.obj="父作用域";//父作用域给自定义指令属性赋的值 }); //创建自定义指令 app.directive("myDirective", function() { return { template : "<p >模板内容</p>", scope:{ title:"=", str:"@" }, link: function postLink(scope, iElement, iAttrs) { console.log(scope.str) console.log(scope.title) } }; }); </script> </body><!-- &符号的用法 --> <body > <div ng-app="myApp" ng-controller="myController"> <!-- 引用自定义指令 --> <my-directive fun="test()"></my-directive> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope) { $scope.test = function(){ console.log('自定义指令会调用该法,所以这句话会打印到控制台上') } }); //创建自定义指令 app.directive("myDirective", function() { return { template : "<p >模板内容</p>", scope:{ fun:"&"//属性名直接是fun }, link: function postLink(scope, iElement, iAttrs) { scope.fun();//调用父作用域的方法,好似不能传参,未深究。 } }; }); </script> </body>5.controller属性
controller属性用于提供对外的接口,即该自定义指令会被其他自定义指令调用。所谓的接口,就是this后的变量或方法。
controller可以使用的参数,作用域、节点、节点的属性、节点内容的迁移,这些都可以通过依赖注入被传进来,所以你可以根据需要只写要用的参数,有$scope,Z$element, $attrs, $transclude。
调用该自定义指令的指令需要放在该指令之间。假定firstDirective指令是要被调用的自定义指令,expander是调用者指令。如下:
<first-directive> <expander ng-repeat="item in list" attribute="list">{{item.title}}:{{item.text}}</expander> </first-directive>既然firstDirective内部还有指令,则firstDirective必须配置transclude属性为true。代码如下:
//用于被调用的自定义指令 app.directive('firstDirective',function(){ return { template : '<div ng-transclude></div>', replace : true, transclude : true, controller :function(){ this.getData = function(val){ var data = 3 * val; return data; } this.a = "abc"; } } });调用其他指令的自定义指令必须配置require属性指定指令名。然后在link函数里就可注入要调用的指令。
//自定义指令 app.directive('expander',function(){ return { templateUrl : 'template.html', replace : true, transclude : true, require : '^"htmlcode"><body> <div ng-app="myApp" ng-controller="myController"> <level-one> <level-two> <level-three> Hello </level-three> </level-two> </level-one> </div> <script> //创建模块 var app = angular.module('myApp', []); //创建控制器 app.controller('myController', function($scope) { }); //自定义指令 function createDirective(name){ return function(){ return { restrict: 'E', compile: function(tElem, tAttrs){ console.log(name + ': compile => ' + tElem.html()); return { pre: function(scope, iElem, iAttrs){ console.log(name + ': pre link => ' + iElem.html()); }, post: function(scope, iElem, iAttrs){ console.log(name + ': post link => ' + iElem.html()); } } } } } } app.directive('levelOne', createDirective('levelOne')); app.directive('levelTwo', createDirective('levelTwo')); app.directive('levelThree', createDirective('levelThree')); </script> </body>注意打印结果:
levelOne: compile => <level-two> <level-three> Hello </level-three> </level-two> levelTwo: compile => <level-three> Hello </level-three> levelThree: compile => Hello levelOne: pre link => <level-two> <level-three> Hello </level-three> </level-two> levelTwo: pre link => <level-three> Hello </level-three> levelThree: pre link => Hello levelThree: post link => Hello levelTwo: post link => <level-three> Hello </level-three> levelOne: post link => <level-two> <level-three> Hello </level-three> </level-two>分析打印结果:
运行levelone指令中的compile函数,ng就会递归遍历它的dom节点,然后在level-two与level-three上面重复这些操作。所以会依次打印连续三个compile。
pre会在所有compile执行完后且在所有post之前执行。这样可以在执行post前执行一些其他代码,有些类似AOP。
由上面结果可知,post的执行顺序却是先levelthree最后levelone,即反向调用相关联的post-link函数。这么做的好处是,当我们运行levelone时,保证leveltwo与levelthree都已经执行过了,这样就会更安全。所以默认的link就是post。
一个我做过的分页例子
之所以展示这个代码,主要是给一些朋友看看真实的项目,,多余的东西删掉了,具体的注入这里就不在讲了。
html页面代码:
<div class="wp-20" ng-controller="AppStatisticController" ng-cloak> <div class="panel-footer"> <s-pagination conf="paginationConf"></s-pagination> </div> </div>控制器代码:
"use strict";//严格 define(["application-configuration", "s-pagination", "tableDataService"], function (app) { app.register.controller("AppStatisticController", ["$scope", "$rootScope", "$stateParams","$http", "tableDataService", function($scope, $rootScope, $stateParams, $http, tableDataService) { var getTableDataSuccess = function(result) { if(result.c == 1) { $scope.title = result.title; $scope.lists = result.pageList; $scope.total = result.data; $scope.paginationConf.totalItems = result.total; }else if(result.c == 2){ //弹出框,没有查到数据 } else { alert(result.i); } }; var getTableDataError = function(result) { alert(result); }; /*重要的代码,这个paginationConf与自定义指令双向绑定数据*/ $scope.paginationConf = { currentPage: 1, itemsPerPage: 10, pagesLength: 9, search: false, onChange: function() { var param = { "pageNo": this.currentPage, "pageSize": this.itemsPerPage, "timeType": $scope.formData.timeType, "adStyle":$scope.formData.adStyle, }; param.appId = $stateParams.appId; tableDataService.getTableData( param, "ims/appStat.do", getTableDataSuccess, getTableDataError ); } }; $scope.$watch("formData",function(newValue,oldValue, scope) { if(newValue.keywords == oldValue.keywords) { $scope.paginationConf.search = true; } }, true); }]); });自定义指令代码:也算是angularJS的分页插件
/** * 分页插件封装s-pagination.js * @date 2016-05-06 * @author Peter */ angular.module('s.pagination', []).directive('sPagination',[function(){//自定义指令 return { restrict: 'E',//仅限元素名调用 template: '<div class="page-list">' + '<ul class="pagination" ng-show="conf.totalItems > 0">' + '<li ng-class="{disabled: conf.currentPage == 1}" ng-click="prevPage()"><span>«</span></li>' + '<li ng-repeat="item in pageList track by $index" ng-class="{active: item == conf.currentPage, separate: item == \'...\'}" ' + 'ng-click="changeCurrentPage(item)">' + '<span>{{ item }}</span>' + '</li>' + '<li ng-class="{disabled: conf.currentPage == conf.numberOfPages}" ng-click="nextPage()"><span>»</span></li>' + '</ul>' + '<div class="page-total" ng-show="conf.totalItems > 0">' + '第<input type="text" ng-model="jumpPageNum" ng-keyup="jumpToPage($event)"/>页 ' + '每页<select ng-model="conf.itemsPerPage" ng-options="option for option in conf.perPageOptions "></select>' + '/共<strong>{{ conf.totalItems }}</strong>条' + '</div>' + '<div class="no-items" ng-show="conf.totalItems <= 0">暂无数据</div>' + '</div>', replace: true, scope: { conf: '='//双向绑定数据 }, link: function(scope, element, attrs){ // 变更当前页 scope.changeCurrentPage = function(item) { if(item == '...'){ return; }else{ scope.conf.currentPage = item; } }; // 定义分页的长度必须为奇数 (default:5) scope.conf.pagesLength = parseInt(scope.conf.pagesLength) ? parseInt(scope.conf.pagesLength) : 5 ; if(scope.conf.pagesLength % 2 === 0){ // 如果不是奇数的时候处理一下 scope.conf.pagesLength = scope.conf.pagesLength -1; } // conf.erPageOptions if(!scope.conf.perPageOptions){ scope.conf.perPageOptions = [10, 20, 30, 40, 50]; } // pageList数组 function getPagination(newValue, oldValue) { //新增属性search 用于附加搜索条件改变时触发 if(newValue[1] != oldValue[1] || newValue[2] != oldValue[2]) { scope.conf.search = true; } // conf.currentPage scope.conf.currentPage = parseInt(scope.conf.currentPage) ? parseInt(scope.conf.currentPage) : 1; // conf.totalItems scope.conf.totalItems = parseInt(scope.conf.totalItems) ? parseInt(scope.conf.totalItems) : 0; // conf.itemsPerPage (default:15) scope.conf.itemsPerPage = parseInt(scope.conf.itemsPerPage) ? parseInt(scope.conf.itemsPerPage) : 15; // numberOfPages scope.conf.numberOfPages = Math.ceil(scope.conf.totalItems/scope.conf.itemsPerPage); // judge currentPage > scope.numberOfPages if(scope.conf.currentPage < 1){ scope.conf.currentPage = 1; } // 如果分页总数>0,并且当前页大于分页总数 if(scope.conf.numberOfPages > 0 && scope.conf.currentPage > scope.conf.numberOfPages){ scope.conf.currentPage = scope.conf.numberOfPages; } // jumpPageNum scope.jumpPageNum = scope.conf.currentPage; // 如果itemsPerPage在不在perPageOptions数组中,就把itemsPerPage加入这个数组中 var perPageOptionsLength = scope.conf.perPageOptions.length; // 定义状态 var perPageOptionsStatus; for(var i = 0; i < perPageOptionsLength; i++){ if(scope.conf.perPageOptions[i] == scope.conf.itemsPerPage){ perPageOptionsStatus = true; } } // 如果itemsPerPage在不在perPageOptions数组中,就把itemsPerPage加入这个数组中 if(!perPageOptionsStatus){ scope.conf.perPageOptions.push(scope.conf.itemsPerPage); } // 对选项进行sort scope.conf.perPageOptions.sort(function(a, b){return a-b}); scope.pageList = []; if(scope.conf.numberOfPages <= scope.conf.pagesLength){ // 判断总页数如果小于等于分页的长度,若小于则直接显示 for(i =1; i <= scope.conf.numberOfPages; i++){ scope.pageList.push(i); } }else{ // 总页数大于分页长度(此时分为三种情况:1.左边没有...2.右边没有...3.左右都有...) // 计算中心偏移量 var offset = (scope.conf.pagesLength - 1)/2; if(scope.conf.currentPage <= offset){ // 左边没有... for(i =1; i <= offset +1; i++){ scope.pageList.push(i); } scope.pageList.push('...'); scope.pageList.push(scope.conf.numberOfPages); }else if(scope.conf.currentPage > scope.conf.numberOfPages - offset){ scope.pageList.push(1); scope.pageList.push('...'); for(i = offset + 1; i >= 1; i--){ scope.pageList.push(scope.conf.numberOfPages - i); } scope.pageList.push(scope.conf.numberOfPages); }else{ // 最后一种情况,两边都有... scope.pageList.push(1); scope.pageList.push('...'); for(i = Math.ceil(offset/2) ; i >= 1; i--){ scope.pageList.push(scope.conf.currentPage - i); } scope.pageList.push(scope.conf.currentPage); for(i = 1; i <= offset/2; i++){ scope.pageList.push(scope.conf.currentPage + i); } scope.pageList.push('...'); scope.pageList.push(scope.conf.numberOfPages); } } if(scope.conf.onChange){ //请求数据 if(scope.conf.search) { scope.conf.onChange(); scope.conf.search = false; } } scope.$parent.conf = scope.conf; } // prevPage scope.prevPage = function(){ if(scope.conf.currentPage > 1){ scope.conf.currentPage -= 1; } }; // nextPage scope.nextPage = function(){ if(scope.conf.currentPage < scope.conf.numberOfPages){ scope.conf.currentPage += 1; } }; // 跳转页 scope.jumpToPage = function(){ scope.jumpPageNum = scope.jumpPageNum.replace(/[^0-9]/g,''); if(scope.jumpPageNum !== ''){ scope.conf.currentPage = scope.jumpPageNum; } }; scope.$watch(function() { if(!scope.conf.totalItems) { scope.conf.totalItems = 0; } if(angular.isUndefined(scope.conf.search)) { scope.conf.search = false; } var newValue = [scope.conf.totalItems, scope.conf.currentPage, scope.conf.itemsPerPage, scope.conf.search]; return newValue; }, getPagination, true); } }; }]);以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
P70系列延期,华为新旗舰将在下月发布
3月20日消息,近期博主@数码闲聊站 透露,原定三月份发布的华为新旗舰P70系列延期发布,预计4月份上市。
而博主@定焦数码 爆料,华为的P70系列在定位上已经超过了Mate60,成为了重要的旗舰系列之一。它肩负着重返影像领域顶尖的使命。那么这次P70会带来哪些令人惊艳的创新呢?
根据目前爆料的消息来看,华为P70系列将推出三个版本,其中P70和P70 Pro采用了三角形的摄像头模组设计,而P70 Art则采用了与上一代P60 Art相似的不规则形状设计。这样的外观是否好看见仁见智,但辨识度绝对拉满。