Angular中实现树形结构视图实例代码


Posted in Javascript onMay 05, 2017

近两年当中使用Angular开发过很多项目,其中也涉及到一些树形结构视图的显示,最近的在项目中封装了大量的组件,一些组件也有树形结构的展示,所以写出来总结一下。

相信大家都知道,树结构最典型的例子就是目录结构了吧,一个目录可以包含很多子目录,子目录又可以包含若干个子孙目录,那咱们今天就以目录结构为例来说明一下Angular中树结构的实现。

首先,我们希望封装一个组件,用于显示整个目录的树形机构,代码如下:

<!DOCTYPE html> 
<html ng-app="treeDemo"> 
 <body> 
  <div ng-controller="TreeController"> 
    <folder-tree current-folder="folder"></folder-tree> 
  </div> 
  <script src="node_modules/angular/angular.min.js"></script> 
  <script src="js/app.js"></script> 
 </body> 
</html>

就像上面的代码一样,我们直接声明folder-tree标签,将controller中的folder数据作为参数传递进去,预期会显示出一个完整的树状目录结构。接下来我们就来定义模块,控制器,以及相应的指令部分:

angular.module('treeDemo', []) 
 .controller("TreeController", function($scope) { 
  $scope.folder = { 
    name: 'techs', 
    children: [ 
      { 
        name: 'server-side', 
        children: [ 
          { 
            name: 'Java' 
          }, 
          { 
            name: 'Python' 
          }, 
          { 
            name: 'Node' 
          } 
        ] 
      }, 
      { 
        name: 'front-end', 
        children: [ 
          { 
            name: 'jQuery' 
          }, 
          { 
            name: 'Angular' 
          }, 
          { 
            name: 'React' 
          } 
        ] 
      } 
    ] 
  } 
 }) 
 .directive("folderTree", function() { 
  return { 
    restrict: "E", 
    scope: { 
      currentFolder: '=' 
    }, 
    templateUrl: 'template/tree.html' 
  }; 
 });

如上所述,在控制器中我们在$scope中绑定了一个folder的数据对象,包含整个的目录结构数据,接着定义了folderTree指令,它会使用tree.html作为模板进行视图渲染,我们这就来look look模板中的内容:

<p>{{currentFolder.name}}</p> 
<ul> 
  <li ng-repeat="subfolder in currentFolder.children"> 
    <folder-tree current-folder="subfolder"></folder-tree> 
  </li> 
</ul>

可以看到,在模板中有个很重要的环节,那就是使用ng-repeat指令遍历当前目录的子集,然后再次使用folder-tree组件来渲染相应的子目录,这种方式简直是完美,现在我们来看看渲染的结果:

Angular中实现树形结构视图实例代码

这种在模板中嵌套使用指令的方法很完美,只可惜低版本的Angular中是无法实现的,会出现无限递归循环,造成页面假死,这是Angular本身的解析机制造成的。经测试,Angular 1.5.0及以上版本是没有问题的,但在Angular 1.4.9及以下版本中就会运行失败。假如你在项目中真的使用了低版本的Angular并且造成运行失败,我们还可以稍微改动一下模板,采用ng-include来实现同样的功能:

<p>{{currentFolder.name}}</p> 
<ul> 
  <li ng-repeat="subfolder in currentFolder.children"  
    ng-include="'template/tree.html'"  
    ng-init="currentFolder = subfolder"> 
  </li> 
</ul>

我们在模板中去掉了folder-tree指令,使用了ng-include指令,再次将tree.html模板引入进来,需要注意的是,因为ng-include会创建一个独立的作用域,为了让其正确的引用到currentFolder变量,我们需要在每个ng-include中初始化currentFolder,将其赋值为ng-repeat中的当前subfolder,另外,别忘了ng-include指令中模板路径前后加上单引号。

这样确实可以,但是你可能觉得有些遗憾,没能使用最好的解决方案来实现这个树结构。

其实这个问题早就有人吐槽了,令人惊喜的是,有个叫Mark的伙计写了一个service专门解决这个问题:

/* 
 * An Angular service which helps with creating recursive directives. 
 * @author Mark Lagendijk 
 * @license MIT 
 */ 
angular.module('RecursionHelper', []).factory('RecursionHelper', ['$compile', function($compile){ 
  return { 
    /** 
     * Manually compiles the element, fixing the recursion loop. 
     * @param element 
     * @param [link] A post-link function, or an object with function(s) registered via pre and post properties. 
     * @returns An object containing the linking functions. 
     */ 
    compile: function(element, link){ 
      // Normalize the link parameter 
      // 如果link参数是对象类型link:{pre: function(...){}, post: function(...){}}则不做处理 
      // 如果link参数是函数类型则将其作为post-link函数在$compile之后调用 
      if(angular.isFunction(link)){ 
        link = { post: link }; 
      } 
 
      // Break the recursion loop by removing the contents 
      // 获取并清空当前元素的内容,后面进行编译 
      var contents = element.contents().remove(); 
      var compiledContents; 
 
      return { 
        pre: (link && link.pre) ? link.pre : null, 
        /** 
         * Compiles and re-adds the contents 
         * 编译和重新添加内容到当前元素 
         */ 
        post: function(scope, element){ 
          // Compile the contents 
          if(!compiledContents){ 
            compiledContents = $compile(contents); 
          } 
          // Re-add the compiled contents to the element 
          compiledContents(scope, function(clone){ 
            element.append(clone); 
          }); 
 
          // Call the post-linking function, if any 
          if(link && link.post){ 
            link.post.apply(null, arguments); 
          } 
        } 
      }; 
    } 
  }; 
}]);

现在我们只需引入这个模块,然后在directive中使用RecursionHelper这个service,调用其compile手动编译指令对应的元素节点:

angular.module('treeDemo', ['RecursionHelper']) 
 .controller("TreeController", function($scope) { 
  $scope.folder = { 
    name: 'techs', 
    children: [ 
      { 
        name: 'server-side', 
        children: [ 
          { 
            name: 'Java' 
          }, 
          { 
            name: 'Python' 
          }, 
          { 
            name: 'Node' 
          } 
        ] 
      }, 
      { 
        name: 'front-end', 
        children: [ 
          { 
            name: 'jQuery' 
          }, 
          { 
            name: 'Angular' 
          }, 
          { 
            name: 'React' 
          } 
        ] 
      } 
    ] 
  } 
 }) 
 .directive("folderTree", function(RecursionHelper) { 
  return { 
    restrict: "E", 
    scope: { 
      currentFolder: '=' 
    }, 
    templateUrl: 'template/tree.html', 
    compile: function(element) { 
      // 我们这里使用RecursionHelper的compile方法编译指令当前元素,这里第二个参数指定一个函数,相当于常用的link函数 
      // 当然我们也可以指定一个对象,里面包含pre和post函数,分别对应pre-link和post-link 
      return RecursionHelper.compile(element, function(scope, iElement, iAttrs, controller, transcludeFn){ 
        // Define your normal link function here. 
        // Alternative: instead of passing a function, 
        // you can also pass an object with  
        // a 'pre'- and 'post'-link function. 
 
        // 这里可以往scope中绑定一些变量 
        scope.variable = 'hello world'; 
      }); 
    } 
  }; 
 });

在上面代码中,我们在创建treeDemo模块时引入RecursionHelper模块,然后在创建folderTree指令时使用RecursionHelper服务,并且在compile方法中调用RecursionHelper的compile方法,即可修复上面的问题。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Javascript中暂停功能的实现代码
Mar 04 Javascript
javascript 避免闭包引发的问题
Mar 17 Javascript
JavaScript写的一个DIV 弹出网页对话框
Aug 14 Javascript
Javascript图像处理思路及实现代码
Dec 25 Javascript
JavaScript创建闭包的两种方式的优劣与区别分析
Jun 22 Javascript
Node.js的npm包管理器基础使用教程
May 26 Javascript
JS正则匹配URL网址的方法(可匹配www,http开头的一切网址)
Jan 06 Javascript
vue.js 使用v-if v-else发现没有执行解决办法
May 15 Javascript
JS实现求数组起始项到终止项之和的方法【基于数组扩展函数】
Jun 13 Javascript
vue 添加vux的代码讲解
Nov 30 Javascript
webpack配置proxyTable时pathRewrite无效的解决方法
Dec 13 Javascript
JavaScript中filter的用法实例分析
Feb 27 Javascript
node.JS md5加密中文与php结果不一致的解决方法
May 05 #Javascript
jquery中封装函数传递当前元素的方法示例
May 05 #jQuery
使用JS在浏览器中判断当前网络连接状态的几种方法
May 05 #Javascript
js实现倒计时关键代码
May 05 #Javascript
javascript 中的继承实例详解
May 05 #Javascript
JavaScript函数表达式详解及实例
May 05 #Javascript
Node.js中的http请求客户端示例(request client)
May 04 #Javascript
You might like
在PHP中利用XML技术构造远程服务(上)
2006/10/09 PHP
PHP文件读写操作之文件写入代码
2011/01/13 PHP
php中serialize序列化与json性能测试的示例分析
2013/04/27 PHP
浅析虚拟主机服务器php fsockopen函数被禁用的解决办法
2013/08/07 PHP
PHP中加密解密函数与DES加密解密实例
2014/10/17 PHP
php中使用GD库做验证码
2016/03/31 PHP
Yii列表定义与使用分页方法小结(3种方法)
2016/07/15 PHP
js之事件冒泡和事件捕获详细介绍
2013/10/28 Javascript
js实现幻灯片播放图片示例代码
2013/11/07 Javascript
JavaScript数组随机排列实现随机洗牌功能
2015/03/19 Javascript
AngularJS基础学习笔记之简单介绍
2015/05/10 Javascript
JS实现弹性菜单效果代码
2015/09/07 Javascript
js淡入淡出焦点图幻灯片效果代码分享
2015/09/08 Javascript
基于JavaScript实现智能右键菜单
2016/03/02 Javascript
JavaScript简单实现弹出拖拽窗口(一)
2016/06/17 Javascript
jQuery UI结合Ajax创建可定制的Web界面
2016/06/22 Javascript
使用Vue自定义数字键盘组件(体验度极好)
2017/12/19 Javascript
vue父组件点击触发子组件事件的实例讲解
2018/02/08 Javascript
通过JS深度判断两个对象字段相同
2019/06/14 Javascript
关于ckeditor在bootstrap中modal中弹框无法输入的解决方法
2019/09/11 Javascript
jQuery中getJSON跨域原理的深入讲解
2020/09/02 jQuery
javascript使用canvas实现饼状图效果
2020/09/08 Javascript
vue keep-alive的简单总结
2021/01/25 Vue.js
python3.6连接MySQL和表的创建与删除实例代码
2017/12/28 Python
PyQt5通信机制 信号与槽详解
2019/08/07 Python
vscode 配置 python3开发环境的方法
2019/09/19 Python
Python调用接口合并Excel表代码实例
2020/03/31 Python
使用纯 CSS 创作一个脉动 loader效果的源码
2018/09/28 HTML / CSS
Sephora丝芙兰印尼官方网站:购买化妆品和护肤品
2018/07/02 全球购物
房地产营销策划方案
2014/02/08 职场文书
新人入职感言
2015/07/31 职场文书
幼儿园教师培训心得体会
2016/01/21 职场文书
开学季:喜迎新生,迎新标语少不了
2019/11/07 职场文书
mysql分组后合并显示一个字段的多条数据方式
2022/01/22 MySQL
Apache Hudi数据布局黑科技降低一半查询时间
2022/03/31 Servers
Win11跳过联网界面创建本地管理账户的3种方法
2022/04/20 数码科技