AngularJS入门教程(五)指令的作用域和内嵌机制

上一篇我们学习了指令的简介,指令的封装,指令的类型,指令封装UI等。介于指令太过强大以及篇幅的原因,本章我将继续学习指令的作用域,指令的交互,指令操作DOM等知识。

指令的作用域

上面的示例中,我们通过配置可以实现动态加载UI模板文件,但是我们无法动态指定UI模板文件中显示的内容。这一节我们来了解一下如何通过指令的隔离域达到在同一个指令中动态指定UI模板文件中要显示的内容,先看看代码示例:

var mainModule = angular.module("mainModule", []);
mainModule.controller("MyController", function() {
  this.jason = { name: "Jason", job: "Developer" };
  this.green = { name: "Green", job: "Doctor" };
});
mainModule.directive("myDirective", function() {
  return {
    restrict: "E",
    scope: {
      personInfo: "=person"
    },
    templateUrl: "myTemplate.html"
  };
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Directive</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="MyController as mc">
      <my-directive person="mc.jason"></my-directive>
      <hr>
      <my-directive person="mc.green"></my-directive>
    </div>
  </body>
</html>
<!-- myTemplate.html -->
Name: {{personInfo.name}}, Job: {{personInfo.job}}

从示例中可以看到,我们给指令的返回对象又添加了一个属性scope,这就是指令的作用域属性,scope属性有三种可设置的值: 

  • false:默认值,这表示指令共享它父节点的Controller的作用域,也就是可以使用{{}}语法直接访问父节点Controller作用域中的属性。 
  • true:创建指令自己的作用域,但是该作用域继承父节点Controller的作用域。 
  • {}:第三种是设置一个对象,表示创建了指令自己独立的作用域,与父节点Controller的作用是完全隔离的。

如果我们希望指令的隔离作用域和父节点Controller的作用域之间进行交互,那么就需要将两者进行绑定,这里有三种绑定方式: 

  • 使用@实现单向数据绑定,但是只限于绑定Controller作用域中值为字符串的属性,因为是单向绑定,所以父节点Controller修改绑定的属性可影响到指令作用域中对应的属性,反之则不可以。在HTML中使用{{}}语法取值,比如person="{{nameStr}}"。 
  • 使用=实现双向数据绑定,在父节点Controller中修改属性和在指令中修改属性可相互影响。在HTML中直接使用属性名称,比如person="jasonObj"。 
  • 使用&实现函数绑定,用于绑定Controller中值为函数的属性,在HTML中直接调用函数,比如action="click()"。

上面的示例中我们给myDirective指令设置了隔离域并添加了名为personInfo的属性,并与父节点的MyController进行数据双向绑定,在HTML代码中,就可以通过<my-directive>指令标签的person属性与MyController的数据绑定了。另外,在进行绑定时还有一种简写的方式:

...
scope: {
  personInfo: "="
  // personInfo: "@"
  // personInfo: "&"
},
...

等同于:

...
scope: {
  personInfo: "=personInfo"
  // personInfo: "@personInfo"
  // personInfo: "&personInfo"
},
...

指令的Controller

在指令中也可以创建Controller,和在Module中创建Controller很类似,既定义函数,在参数中注入需要的AngularJS服务既可:

var mainModule = angular.module("mainModule", []);
mainModule.controller("MyController", function($scope) {
  $scope.green = { name: "Green", job: "Doctor" };
});
mainModule.directive("myDirective", function() {
  return {
    restrict: "E",
    scope: {
      person: "="
    },
    controller: function($scope) {
      $scope.jason = { name: "Jason", job: "Developer" };
    },
    templateUrl: "myTemplate.html"
  };
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Directive</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div ng-controller="MyController">
      <my-directive person="green"></my-directive>
    </div>
  </body>
</html>
<!-- myTemplate.html -->
Name: {{jason.name}}, Job: {{jason.job}}
Name: {{person.name}}, Job: {{person.job}}

在上面的示例中,我们给myDirective指令添加了Controller,有一点不同的是在添加Controller时不能设置名称,指令的Controller名称默认与指令名称一样,如果需要另外指定名称,需要配置controllerAs指定Controller的名称:

...
controller: function($scope) {
  $scope.jason = { name: "Jason", job: "Developer" };
},
controllerAs: "directiveController",
...

在上面示例的UI模板文件中可以看出,既可以使用指令隔离域中与父节点Controller绑定的属性,也可以使用在指令自己的Controller中定义在隔离域的属性。

指令之间的交互

  • require: "controllerName":只查找指令自己的Controller。 
  • require: "^controllerName":查找指令自己的Controller以及父指令的Controller。 
  • require: "^^controllerName":只查找父指令的Controller。 
  • require: [“^controllerName1”, “^controllerName2”]:引用多个Controller。

如果指令查找到引用的Controller后该如何使用呢,这就要使用指令的另一个重要的属性link函数了。link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM,该函数共有五个参数: 

  • scope:指令的作用域,默认是父节点Controller的作用域,如果指令有创建自己的作用域,那么则指指令自己的作用域。 
  • element:指令的jQLite(jQuery的子集)包装的DOM元素,可以通过该参数操作指令所在的DOM元素。 
  • attrs:指令所在DOM元素的属性对象,通过.语法可以获取到给DOM元素添加的属性。 
  • controller:指令通过require属性引用的Controller实例。 
  • transcludeFn:嵌入函数

link函数的其他几个参数后面文章中都会讲到,当指令找到通过require属性引用的Controller后,我们就可以通过link函数的第四个参数访问引用的Controller了。

通过指令操作DOM元素

我们了解了link函数后就可以使用该函数实现各种有用的指令了,比如通过指令操作DOM元素:

var mainModule = angular.module("mainModule", []);
mainModule.directive("myDirective", function($interval) {
  return {
    restrict: "A",
    link: function(scope, element, attrs) {
      $interval(function() {
        element.text(new Date());
      }, 1000);
    }
  };
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Directive</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <div>
      Current Date is: <span my-directive></span>
    </div>
  </body>
</html>

上面的示例中,首先我们限定了myDirective指令只能以标签属性的形式使用,然后注入了AngularJS的内置服务$interval,通过link函数的第二个参数获取到指令所在的DOM元素,然后周期性更新DOM元素显示的内容。
Angular指令操作DOM

指令的内嵌机制

大家都知道HTML中的DOM元素是具有层级关系的,一般情况下我们使用指令封装的UI模板颗粒度都会比较小,所以就会出现指令嵌套的现象,这几需要用到指令的内嵌机制了,指令的transclude属性默认为false,如果将其设置为true,那么该指令就开启了内嵌机制,也就是说指令标签之间的内容可以被指定嵌入UI模板中被ng-transclude内置指令标记过的DOM元素中,结合之前说过的父子指令交互的内容来实现一个例子:

// modules.js
var mainModule = angular.module("mainModule", []);
mainModule.directive("myTabs", function() {
  return {
    restrict: "E",
    transclude: true,
    controller: function($scope) {
      $scope.panes = [];
      var panes = $scope.panes;
      this.addPane = function(pane) {
        if(panes.length == 0) {
          $scope.select(pane);
        };
        panes.push(pane);
      };
      $scope.select = function(pane) {
        angular.forEach(panes, function(pane) {
          pane.selected = false;
        });
        pane.selected = true;
      };
    },
    templateUrl: "myTabs.html"
  };
});
mainModule.directive("myPane", function() {
  return {
    restrict: "E",
    require: "^^myTabs",
    scope: {
      name: "@",
      job: "@"
    },
    link: function(scope, element, attrs, controller) {
      controller.addPane(scope);
    },
    templateUrl: "myPane.html"
  };
})

在上面的示例中,我们创建了两个指令,myTabs和myPane,在myTabs指令中,我们限定它只能以标签形式使用,开启了内嵌机制,并定义了它自己的Controller,在Controller中定义了panes变量和addPane(pane),select(pane)两个方法,方法的具体实现内容这里就不解释了,都很简单,最后指定了UI模板文件myTabs.html。

在myPane指令中同样限定只能以标签形式使用,指定了要引用的父节点的Controller名称,后创建了自己的隔离域,定义了name,job两个属性,并进行了字符串的单向绑定,然后定义了link函数,通过第四个参数访问到了父节点的myTabsController,并调用addPane(pane)函数,将自己的隔离域作为参数传入,最后指定了UI模板文件myPane.html。

再来看看index.html和myTabs.html,myPane.html这两个模板文件:

<!--index.html-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo for Directive</title>
    <script src="../angular-1.5.8.js"></script>
    <script src="modules.js"></script>
  </head>
  <body ng-app="mainModule">
    <my-tabs>
      <my-pane name="Jason" job="Developer"></my-pane>
      <my-pane name="Green" job="Doctor"></my-pane>
    </my-tabs>
  </body>
</html>

<!--myTabs.html-->
<div>
  <ul>
    <li ng-repeat="pane in panes">
      <a href="" ng-click="select(pane)">{{pane.name}}</a>
    </li>
  </ul>
  <div id="paneContainer" ng-transclude></div>
</div>

<!--myPane.html-->
<div ng-show="selected">
  I am {{name}}, my job is {{job}}!
</div>

在index.html中,myTabs指令包含两个myPane指令,这两个myPane指令所显示的内容将嵌入在myTabs.html中id为paneContainer的DIV中,也就是myPane.html中的内容会被嵌入在这个DIV里。

上面这三个文件中有几个点需要注意:

  • 因为在myPane指令的隔离域中定义了name和job属性,并进行了字符串绑定,所以在index.html文件中,可以对myPane标签里的name,job属性直接进行字符串赋值。 
  • 因为在myPane指令中引用了myTabs指令的Controller,并在link函数中将隔离域作为参数传给了myTabs,既myTabs指令的Controller中的select(pane)和addPane(pane)函数的参数均为myPane指令的隔离域,所以在myTabs.html文件中可以直接使用pane访问myPane指令隔离域中定义的属性,比如{{pane.name}},并且也可以在myTabs指令在myPane的隔离域中定义属性,比如pane.selected = true,给隔离域定义了selected的属性,然后可以在myPane指令中使用。 
  • ng-show是AngularJS内置的指令,用于显示或隐藏指定的DOM元素。

上面demo的运行效果如下:
AngularJS内嵌指令

link函数的第五个参数transcludeFn是一个函数,该函数常用的有两个参数scope和function(clone){},既transcludeFn(scope, function(clone){})。前者是嵌入内容的作用域,与指令的隔离作用域是平行的,后者函数的参数clone是嵌入的内容的jquery封装,可以通过它对嵌入的内容进行DOM操作。

指令的其他属性

priority用于指定指令的优先级,该属性的值从1开始。当有多个指令定义在同一个DOM元素中时就需要通过该属性明确它们的执行顺序。
replace用于判定是否将UI模板的内容替换掉指令标签本身,该属性默认值为false,既保留指令标签本身,若设置为true则替换指令标签。

版权声明:本文为博主原创文章,未经博主允许不得转载。

AngularJS入门教程(五)指令的作用域和内嵌机制

: » AngularJS入门教程(五)指令的作用域和内嵌机制

原创文章,作者:506227337,如若转载,请注明出处:https://blog.ytso.com/251166.html

(0)
上一篇 2022年5月2日
下一篇 2022年5月2日

相关推荐

发表回复

登录后才能评论