阅读本博客的同学先看一下本博客的主要内容点,如果咩有涉及到同学们需要的知识点,请绕道,有错误请及时指出,以免祸害其他同学:
主要内容点:
1、如果获取ng-transclude的内容;
2、获取了ng-transclude内容之后如果动态添加内容,并且编译然后加载到dom中;
3、如果在子作用域(指令)中操作父作用域(调用指令的地方所在的controller上下文)的变量(新增,修改等);
4、如果在父作用域中修改子作用域的变量;
讲一下该指令的意义所在,我们平时写一些列表指令的时候,往往都会有显示加载状态,或者点击加载更多的功能,或者根据某个条件筛选、排序、或者分页查询、根据给出的地址请求数据等等……这些代码有的同学可能已经写了千千万万遍,而现在就是摆脱这种问题的时机。写一个自定义指令,根据给出的url以及参数自动请求数据、监听参数变化重新请求数据(筛选,排序),自动处理显示加载状态……所有的重复劳动都是自动完成,但是有一点不一样的是,列表的内容ui布局是不一样的,然后请求的数据结果也是不一样的,如何才能使得实现的自定义列表适应这两个问题呢?
先看一下调用代码:
<my-list list="dataList" item="it" var="devLnkList"> <div ng-click="removeItem(it,$index)"> <span>123456</span> ------<span ng-bind="it.value"></span>------- <span>3333333</span> </div> </my-list>
对,这个自定义的列表调用的方式跟ng-repeat非常像,接下来看一下实现原理;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="../../lib/angular/angular.min.js"></script> </head> <body ng-app="MyApp"> <div ng-controller="MyCtrl"> <my-list list="dataList" item="it" var="devLnkList"> <div ng-click="removeItem(it,$index)"> <span>123456</span> ------<span ng-bind="it.value"></span>------- <span>3333333</span> </div> </my-list> {{dataList}} <button ng-click="addItem()">add</button> <button ng-click="loadMore()">loadMore</button> </div> <script> var myApp = angular.module('MyApp', []); myApp.controller('MyCtrl', function ($scope) { $scope.addItem = function () { console.log($scope); $scope.dataList.push({ name:'add', value:(Math.random()*100).toFixed(0) }); }; $scope.dataList = [ {name:1,value:2}, {name:3,value:"a"}, {name:4,value:"v"}, {name:5,value:"f"}, ]; $scope.removeItem = function (param,index) { console.log(param,index); $scope.dataList.splice(index,1); }; $scope.loadMore = function () { $scope.devLnkList.loadNextPage(); } }); myApp.directive('myList', function ($compile, $timeout) { return { restrict: 'E', replace: true, template: '<ng-transclude></ng-transclude>', scope: false, transclude: true, controller: [ '$scope', '$element', '$attrs', '$transclude', function ($scope, $element, $attrs, $transclude) { if (!$attrs.var) { console.error("my-list need necessary attribute : var"); return; } if (!$attrs.list) { console.error("my-list need necessary attribute : list"); return; } /*模拟数据请求过程,通过给予的url,自动请求数据,放到父作用域中*/ if (!$scope[$attrs.list]) $scope[$attrs.list] = [ {name: 111, value: 'aaa'}, {name: 2223, value: "bbb"}, {name: 33334, value: "ccc"}, {name: 4445, value: "ddd"}, ]; /*暴露一个代理对象给父作用域,父作用域可以通过调用该对象中的方法对lnkList进行操作,比如手动刷新数据等等*/ $scope[$attrs.var] = { isLoading: false, loadNextPage: function () { $scope[$attrs.var].isLoading = true; $timeout(function () { console.log($scope); if ($scope[$attrs.list]) { $scope[$attrs.list].push({ name: 'add in directive', value: (Math.random() * 100).toFixed(0), }); } }, 1000 * 1).then(function () { $scope[$attrs.var].isLoading = false; }); } }; } ], link: function (scope, element, attrs, ctrl, transclude) { transclude(scope, function (clone) { /*获取调用指令的地方的html内容,由于拿到的是一个dom对象数组,所以需要遍历数组,把dom对象的outerHtml拼接,由于换行等字符会被解析为名为text的dom对象(与div、span等dom对象平级),而text的dom对象没有outerHtml属性,只有wholeText属性,所以拼接的时候使用的是wholeText的值*/ var html = "<div ng-repeat='" + attrs.item + " in " + attrs.list + " track by $index'>"; for (var i = 0; i < clone.length; i++) { if (clone[i].outerHTML) html += clone[i].outerHTML; else if (clone[i].wholeText) html += clone[i].wholeText; } html += "</div>"; html += '<div ng-show="!' + attrs.var + '.isLoading" ripple ng-click="' + attrs.var + '.loadNextPage()"><div style="color: #469ce7;">点击加载更多</div></div>'; html += '<div ng-show="' + attrs.var + '.isLoading" ripple><div style="color: #469ce7">正在加载中...<ons-icon icon="ion-load-a" style="animation: rotating 1.2s linear infinite;color: #469ce7"></ons-icon></div></div>'; /*拼接完成,开始编辑html内容,并且加载到当前指令中*/ element.html(html); $compile(element.contents())(scope); }); }, } } ); </script> </body> </html>
在指令中,link函数中的代码是本次的主要代码,它获取了未编译的ng-transclude的html内容,就是在调用的时候,<my-list>节点下的自定义的内容,然后使用ng-repeat去显示,后面还自动加上了显示加载中的文字,本来是使用了onsenui,为了能够使得代码能够直接使用,就去掉了这个框架,导致界面看起来有点难看,同学们就将就一下。
指令controller的主要工作是请求数据,不过这个我没有写好,因为这个请求数据的过程因使用场景而异,我这里只是模拟了一个请求数据的过程,加载数据也是,最后还暴露了一个对象保存在父作用域的scope中,父作用域可以调用这个对象的方法调用指令中的方法。比如代码中的loadNextPage函数,同学们需要其他的指令动作可以自己扩展这个对象的函数;
最后一点比较重要的,父作用域的获取,我这个指令的scope的值是false,表示直接使用父作用域,所以是$scope[$attrs.var],如果同学们发现$scope中的变量不是父作用域中的变量,可以试试$scope.$parent,再不行可以试试$scope.$parent. $parent。