学习 AngularJS (四) directive 指令
学习 AngularJS (四) directive 指令
刘军兴 发表于2年前
学习 AngularJS (四) directive 指令
  • 发表于 2年前
  • 阅读 112
  • 收藏 2
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

(接上篇) 现在已知 AngularJS 的一些基本概念, 让我们看看 kityminder-editor 中使用它的地方, 试图看懂:

<!-- HTML 部分 -->
<body ng-app='kityminderDemo' ng-controller='MainController'>
  <h1 class="editor-title">KityMinder Editor - Powered By FEX</h1>
  <kityminder-editor on-init="initEditor(editor, minder)"></kityminder-editor>
</body>

<!-- 引入很多 js 库, 这里略去, 只保留引入 angular.js -->
<script src="bower_components/angular/angular.js"></script>

<!-- 引入 kityminder-editor 的多个 js, 这里显示第一个... -->
<script src="ui/kityminder.app.js"></script>

<!-- 启动脚本 -->
<script>
  // 定义模块 kityminderDemo, 依赖于 kityminderEditor 模块.
  angular.module('kityminderDemo', ['kityminderEditor'])
      // 注册主控制器, 在 body 标签中有引用.
    .controller('MainController', function ($scope) {
       // 设置作用域中的 initEditor() 方法, 在自定义 directive 
       //   <kityminder-editor ...> 中引用.
       $scope.initEditor = function() { ... }
    });
</script>

现在知道 angular.module() 用于创建/获取模块, HTML <body> 标签被作为整个 ng-app 的渲染区域.
启动模块 ng-app = 'kityminderDemo', 其依赖于模块 'kityminderEditor', 后者在 kityminder.app.js 中
创建, 并在后续引入的 js 中添加多个 directive/filter/service 等.

为了验证, 我们写一个测试页面, 里面摘取最小元素, 并使用 console.log() 的调试办法, 分别输出各阶段执行的一些
信息.

<!-- 启动 app(容器, module) 名为 'demo', 视图控制器(C of MVC)为 xxx -->
<body ng-app='demo' ng-controller='MainController'>
  <h1>Hello World!</h1>
</body>
<!-- 引入各个 js 库的部分略 -->
<script>
  angular.module('demo', [])
    .controller('MainController', function($scope) {
      console.log('MainController 工厂方法被调用.');  // 输出2(顺序在输出1的后面).
    });
  console.log('启动代码执行完成.');  // 输出1.
</script>

有了 console.log() 的输出, 我们就放心了, 知道代码会被执行, 以及执行的顺序如何.

下面由于使用了自定义 directive: <kityminder-editor on-init='xxx'>, 所以我们去学习 directive 的概念.

======

Creating Custom Directives (创建自定义指令)

   英文文档见: https://docs.angularjs.org/guide/directive

这部分文档主要讲怎么创建自定义指令(directive)的. 对理解别人写的 directive 想来也有帮助的.

What are Directives?

指令是一个 DOM 元素标记, 用于告知 AngularJS 的 HTML 编译器  ($compile) 将特定的行为附指定给
所属的 DOM 元素(如通过 event listener), 甚至变换 DOM 元素及其子元素.

(DOM 元素标记包括 attribute, element name, comment or CSS class)

Angular 有内建的指令, 也可以创建自己的 directives. 启动 app 时会遍历 DOM 树查找指令并附加处理.

(实验: 我们自己先简单写一个 <my-directive> 自定义 HTML 标记对在页面中, 然后浏览器调试器中就能看到)

Matching Directives

需要了解一点 Angular's HTML compiler 如何决定何时使用一个给定的 directive.

例子1: <input ng-model='foo'> 匹配 ngModel 指令; ng-model 写作 data-ng-model 也可.
   (写作 data-xxx 似乎是 HTML5 某种规范?)

例子2: <person>{{name}}</person> 匹配 person 指令.

Normalization

大致是指 directive 名字的规范化/一般化. 过程如下:
  1. 去掉(strip) x-, data- 前缀.
  2. 转换 : - _ 名字为 camelCase 格式. 如 ng-model => ngModel

例子: ng-bind, ng:bind, ng_bind, data-ng-bind, x-ng-bind 都等价的转换为 ngBind 指令.

最佳实践: 对使用某些 HTML 校验工具, 用 data- 前缀方式最好.

Directive types

(Angular 的) $compile 匹配能基于 element name(DOM 元素名字), attributes, class names, comments.
例子:
  1.  <my-dir></my-dir>  --- 基于 DOM 元素名字匹配.
  2.  <span my-dir='hello~'></span>  --- 基于 attribute (名字)匹配.
  3.  <!-- directive: my-dir xxx -->       --- 基于 comment (注释)匹配. (前面使用方式中未见提及...)
  4.  <span class='my-dir: exp;'></span>  --- 孤陋寡闻, 不知道 css-name 也能自定义... 还是 style?

(实验: 写了 <span class='my-dir: exp'> 和 <span style='my-dir: exp'>, 然后浏览器查看, 前者 class 被
   当做两个 class-name, 后者没有数据.  可能这里文档写得不对...? )

Text and attribute bindings

几个 angular 概念: $interpolate(插值,替换?), watch(观察者模式?), digest(?), 暂时略, 以后需要再学.

ngAttr attribute bindings

例子: <svg><circle cx='{{cx}}' /></svg>
某些特定 DOM 元素,属性的值在加载时被检查, 从而写 cx='{{cx}}' 不能通过. 此时可写作 ng-attri-cx='{{cx}}'
形式. 相关 $interpolate 属性 allOrNothing.

Creating Directives

指令的注册类同 controller, 都是注册到 modules. 对应 API: module.directive().
参数为 directive-name, factory-function. 工厂函数应返回一个对象, 告知 $compile 当匹配指令时应如何处理.

工厂函数只被调用一次, 当 $compiler 第一次匹配到该指令. (下面实验: 一个指令使用3次, 查看工厂函数是
如何被调用的).

下面先看几个简单例子, 然后深入不同的选项和编译过程 (我们略去复杂的吧).

最佳实践: 给你自己的 directive 加一个区分前缀, 避免和别人冲突.

Template-expanding directive

场景: 多个地方显示 customer 信息(某个固定 template), 一旦要修改就要改多个地方, 这时候用 directive 合适.
下面示例一个简单 directive: 替换内容为静态模板:

angular.module('demo').controller(...)  // module,controller 细节略.
  .directive('my-customer', function() { 
    return {
      template: 'Hello {{customer.name}}'  // 一个静态模板
    }
  });

这里, 用 module.directive() 注册一个新的 directive, 提供一个工厂函数, 该函数返回一个含有模板属性的对象.
(具体可以有哪些属性及含义, 肯定有文档介绍, 需要再查) 这里重点是结构.

现学现实验一下:

angular.module('demo', [])
  .controller('MainController', function($scope) {
    console.log('controller 工厂函数被调用.'); // 后显示.
    $scope.customer = {name: '顾客'};
  })
  .directive('myDirective', function() { // 名字写 my-directive 不对
    console.log('directive 工厂函数被调用'); // 先显示.
    return {
      template: "Hello {{customer.name}}, I'm directive!"
    }
  });

页面显示 `Hello 顾客'. console.log() 输出顺序是 directive 先打印出来.

顺便实验页面放多个 <my-directive>, directive console.log() 仍只出现一次(且只一次).
  如果页面没有该 directive, 则工厂函数不会调用.

实验: 用 <!-- --> 模式写引用 directive, 没实验通, 工厂函数被调用, 但未显示结果...

注意: 在注册的时候要使用名字 myDirective(驼峰大小写模式), 写 my-directive 找不到.

实验: 用 <div my-directive></div> 模式引用指令, 实验通过.
文档说用这种方式可以组合(compose)多个指令. 后面给出例子.

模板可以放外面(独立的) HTML 文件中, 使用 templateUrl 选项引用, 像 ng-include 指令.

实验: 使用 templateUrl 放一个模板到独立文件中, console 中显示该文件被加载并正确显示.
   这意味着模板文件也可以是 php 动态生成的, 强大吧...

templateUrl 还可以是一个函数, 函数可以处理引用 directive 中带有参数的情况, 实验就先略了.

创建 directive 时, 可指定 restrict 选项: 'A' 表示匹配 attribute, 'E' 匹配 element name,
   'C' 匹配 class name. 可以组合为 'AEC' 表示三者都匹配.
更多请参见 API docs: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object

Isolating the Scope of a Directive (隔离作用域)

(个人理解) 重用此 directive 时, 需要指定引用的 $scope 数据, 如果用新建 controller 的办法就有点笨,
此时可以使用 scope option, 指明使用哪个信息.

如指定 scope: { customerInfo: '=info' } 指明 customerInfo 数据取自 $scope[info], 其中 info 由 attr 指定.

细节略. 需要时再看.

Creating a Directive that Manipulates the DOM (操作 DOM 的指令)

先看例子, 区分是 directive 返回的对象中给出一个 link 选项, 其是一个函数(后面详细看) :

angular.module(略).controller(略)
  .directive('myCurrentTime', [... function() {
    return {
      link: link // 下面说明此 link 函数.
    };
  });

选项 link 用于注册 DOM 侦听器, 也可更新 DOM. 该函数在模板被复制到 directive 位置, 及 logic 将被设置时.

link 函数形式: function link(scope, element, attrs, controller, transcludeFn) { ... }, 参数说明请看文档.

// 上面 link 函数例子. element 给出 directive 所在元素, attrs 给出元素属性.
function link(scope, element, attrs) {
  function update_time() {
    element.text('格式化时间部分略');
  }

  scope.$watch(element.myCurrentTime, function() {  // 估计是添加 listener 当...时.
    update_time();
  });

  element.on('$destroy', function() {
    // 元素关闭时取消定时任务, 不引入内存泄露.
    $interval -> kill_timer(timer_id);
  });

  // 设置定时器, 每秒更新一下时间显示.
  var timer_id = $interval -> set_timer(update_time, 1000);
}

在 link() 函数中, 注意点1 是使用 element.text() 设置新的文本(如新的时间字符串).
注意点2 是 $watch() 侦听当用户改变了 myCurrentTime 时间格式(或某些数据改变), 更新显示. 
注意点3 是 angularjs 自己会发布 '$destroy' 事件 当一个被编译的 DOM 节点被 destroy 时.

例如 scope 也有 '$destroy' 事件, 侦听方法: scope.$on('$destroy', ...)

Creating a Directive that Wraps Other Elements

介绍 transclude 选项. 暂时略, 以后需要看.

Creating a Directive that Adds Event Listeners

例子:

function link(...) {
  element.css(...); // 可为元素设置 style
  element.on('mousedown', function() { ... }); // 为元素添加事件.
  ...
}

 

Creating Directives that Communicate

我们能组合 directives, 通过在模板中使用别的 directives. 例子如属性页(tab)指令:

angular.module(...)
  .directive('myTabs', function() {
    return {
      ... 其它略...
      controller: ['$scope', function($scope) {
        // 这里定义了一个新的 scope?
        var panes = $scope.panes = [];  // 面板数组.
        $scope.select = function 选择指定面板...的函数;
        this.addPane = function 添加面板到数组中的函数;
      }
    };
  })
  .directive('myPane', function() {  // 被嵌套在 <myTabs> 指令里面.
    return {
      ... 其它略 ...
      require: '^myTabs',   // 注意点: 注入依赖?
      link: function(...) { tabs.addPane(scope); },  // 把自己添加到 tabs 中
    };
  });

这里注意到 myPane 指令使用了 require 选项, 用于引用到 myTabs 指令... (细节见文档)

<my-tabs>
  <my-pane title='Hello'>
    ...
  </my-pane>
  <my-pane title='World'>
    ...foobar...
  </my-pane>
</my-tabs>

这样, directives 里面可以嵌别的 directives, 并能(互相?)引用到.

Summary

In-depth explanation 请参见 compiler guide...

 

======

对 directive 有了大致了解之后, 让我们去看看 <kityminder-editor> 是一个怎样的 directive 吧.

找到 ui/directive/kityminderEditor/kityminderEditor.directive.js 打开查看:


angular.module(...)
  .directive('kityminderEditor', ['config',  // 依赖部分暂时不细究.
        'minder.service', 'revokeDialog', function() {
    return {
      restrict: 'EA', // 限定在 element,attribute 使用. 也许 E 就够了.
      templateUrl: 'kityminderEditor.html', // 模板文件, 一会细看.
      replace: true,  // 某选项, 需去查文档, 也可先忽略...
      scope: {
        onInit: '&'   // 也许需要查文档.
      },
      link: function() {
        // ...不少处理, 稍后仔细研究...
      }
    };
  });

可以看到是一个 directive 的定义, 只是有一些还不知道的 option. 再简单看看那个 template html:

<div class='minder-editor-container'>
  <div class='...' top-tab='minder' editor='editor' ng-if='minder'></div>
  ...
  可能又引用了多个其它子 directive
</div>

似乎又使用了别的子 directive, 我们对比查看最终产生的 html 页面的元素, 可以看到上面那个 container div,
继续对照, 里面共 6 个子 div 也都存在, 分别对应界面的几个部件: 工具栏区, 编辑区, search框, 备注输入区,
备注显示区, 左下导航器. 估计是子 directive 实现的, 让我们下一步再细化进入看吧.

 

共有 人打赏支持
粉丝 55
博文 141
码字总数 222144
×
刘军兴
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: