Angular.JS中的指令与参数详解

指令,很重要!

AngularJS与jQuery最大的区别在哪里?我认为,表现在数据双向绑定,实质就是DOM的操作形式不一样。

  • JQuery通过选择器找到DOM元素,再赋予元素的行为;
  • 而AngularJS则是,将指令与DOM绑定在一起,再扩展指令的行为。

所以AngularJS开发最理想的结果就是,在页面HTML与CSS的设计时,设计工程师只需要关注指令的使用;而在背后的逻辑开发上,架构工程师则是不需要知道如何操作DOM,只需要关注指令背后的行为要如何实现就行;测试工程师也可以开发针对指令的单元测试。

指令就是DOM与逻辑行为的媒介,本质就是DOM绑定的独立逻辑行为函数。

指令难点在于参数

来看看都有哪些

angular.module('app', [])
.directive('myDirective', function() {
 return {
 restrict: String,
 priority: Number,
 terminal: Boolean,
 template: String or Template Function:
 function(tElement, tAttrs) {...},
 templateUrl: String,
 replace: Boolean or String,
 scope: Boolean or Object,
 transclude: Boolean,
 controller: String or
 function(scope, element, attrs, transclude, otherInjectables) { ... },
 controllerAs: String,
 require: String,
 link: function(scope, iElement, iAttrs) { ... },
 compile: // 返回一个对象或连接函数,如下所示:
 function(tElement, tAttrs, transclude) {
 return {
 pre: function(scope, iElement, iAttrs, controller) { ... },
 post: function(scope, iElement, iAttrs, controller) { ... }
 }
 return function postLink(...) { ... }
 }
 };
 });

刚开始接触指令的时候,我简直就是蒙了,这堆参数究竟怎么用怎么理解啊。告诉大家我的一个理解方法。

把它们分成三类:

  1. 描述指令或DOM本身特性的内部参数
  2. 连接指令外界、与其他指令或控制器沟通的对外参数
  3. 描述指令本身行为的行为参数

内部参数

  • restrict:String,E(元素)<my-directive></my-directive> A(属性,默认值)<div my-directive="expression"></div> C(类名)<div class="my-directive:expression;"></div> M(注释)<--directive:my-directive expression-->
  • priority: Number,指令执行优先级
  • template: String,指令链接DOM模板,例如“<h1>{{head}}</h1>”
  • templateUrl:String,DOM模板路径
  • replace: Boolean,指令链接模板是否替换原有元素,

对外参数——scope

scope参数非常重要,本应该是放到最后说明的,但是scope却是理解其他参数的关键,所以务必先跟大家说清楚。

scope参数的作用是,隔离指令与所在控制器间的作用域、隔离指令与指令间的作用域。

scope参数是可选的,默认值为false,可选true、对象{};

  • false:共享父域
  • true:继承父域,且新建独立作用域
  • 对象{}:不继承父域,且新建独立作用域

false、true、{}三者对比

来看个例子

<body>
 <div ng-controller='parentCtrl'>
 <h3>指令scope参数——false、true、{}对比测试</h3>
 parent:
 <div>
 <span> {{parentName}}</span>
 <input type="text" ng-model="parentName" />
 </div>
 <br />
 <child-a></child-a>
 <br />
 <child-b></child-b>
 <br />
 <child-c parent-name="parentName"></child-c>
 </div>
 <!--t1指令模板-->
 <script type="text/html" id="t1">
 <div>
 <span>{{parentName}}</span>
 <input type="text" ng-model="parentName" />
 </div>
 </script>
 <script>
 var app = angular.module("app", []);

 app.controller('parentCtrl', function ($scope) {
 $scope.parentName = "parent";
 })

 //false:共享作用域
 app.directive('childA', function () {
 return {
 restrict: 'E',
 scope: false,
 template: function (elem, attr) {
  return "false:" + document.getElementById('t1').innerHTML;
 }
 };
 });

 //true:继承父域,并建立独立作用域
 app.directive('childB', function () {
 return {
 restrict: 'E',
 scope: true,
 template: function (elem, attr) {
  return "true:" + document.getElementById('t1').innerHTML;
 },
 controller: function ($scope) {
  $scope.parentName = "parent";

  //已声明的情况下,$scope.$watch监听的是自己的parentName
  $scope.$watch('parentName', function (n, o) {
  console.log("child watch" + n);
  });

  //$scope.$parent.$watch监听的是父域的parentName
  $scope.$parent.$watch('parentName', function (n, o) {
  console.log("parent watch" + n);
  });
 }
 };
 });

 //{}:不继承父域,建立独立作用域
 app.directive('childC', function () {
 return {
 restrict: 'E',
 scope: {},
 template: function (elem, attr) {
  return "{}:" + document.getElementById('t1').innerHTML;
 },
 controller: function ($scope) {
  console.log($scope);
 }
 };
 });

 </script>
</body>

false参数

本质:子域与父域共享作用域。

特点:父域修改parentName的同时,指令绑定的parentName的元素会被刷新。

反之,指令内部parentName被修改时,父域的parentName同样会被刷新。

true参数

本质:子域继承父域,并建立独立作用域。

特点:

1、在指令已声明parentName的情况下,父域parentName变更,指令中parentName不会发生变化。
指令在true参数下,建立了的scope,独立并隔离与父控制器的scope。

controller: function ($scope) {
 $scope.parentName = "parent";
}

反之,指令中parentName变更,父域也不会发生变化。

2、在指令未声明parentName的情况下,父域的parentName变更,指令中parentName也会刷新
这种情况很多时候会被忽略,指令的scope没有声明对象时,其元素绑定的仍然是父域的对象。但,一旦指令中Input变更了,对应的独立scope也会自动声明该绑定对象,这就回到了第1种情况。

controller: function ($scope) {
 //$scope.parentName = "parent";
}

然而,指令中parentName变更,父域是不会变化的;

3、在指令已声明parentName的情况下 ,在指令中监听父域parentName 的变化无效。但监听子域parentName的变化有效
独立子域scope,只能监听自己的,不能监听父域的。但通过 $scope.$parent可以监听父域。

controller: function ($scope) {
 $scope.parentName = "parent" ;

 //已声明的情况下,$scope.$watch监听的是自己的parentName
 $scope.$watch( 'parentName' , function (n, o) {
 console.log("child watch" + n);
 });

 //$scope.$parent.$watch监听的是父域的parentName
 $scope.$parent.$watch( 'parentName' , function (n, o) {
 console.log("parent watch" + n);
 });
}

4、在指令未声明parentName的情况下 ,在指令中监听父域parentName的变化有效。

这里就不解释了,参考第2点,大家可以动手试一下。

controller: function ($scope) {
 //$scope.parentName = "parent";

 //未声明的情况下,$scope.$watch监听的是父域的parentName
 $scope.$watch('parentName' , function (n, o) {
 console.log("child watch" + n);
 });
}

对象{}参数

本质:子域不继承父域,并建立独立作用域。

特点:

1、当scope对象为空对象时,无论是父域parentName,还是指令子域parentName发生变更,都不会影响到对方。
原理很清楚,就是指令建立的独立作用域,与父域是完全隔离的。

scope: {}

2、当scope对象为非空对象时,指令会将该对象处理成子域scope的扩展属性。而父域与子域之间传递数据的任务,就是可以通过这块扩展属性完成。

<div ng-controller='parentCtrl'>
 parent:
 <p><span>{{name}}</span><input type="text" ng-model="name" /></p>
 <p><span>{{sexy}}</span><input type="text" ng-model="sexy" /></p>
 <p><span>{{age}}</span><input type="text" ng-model="age" /></p>
 <br />

 <!--特别注意:@与=对应的attr,@是单向绑定父域的机制,记得加上{{}};&对应的attrName必须以on-开头-->
 <child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>
</div>

<!--t1指令模板-->
<script type="text/html" id="t1">
 <div>
 <span>{{myName}}</span>
 <input type="text" ng-model="myName" />
 </div>
 <div>
 <span>{{mySexy}}</span>
 <input type="text" ng-model="mySexy" />
 </div>
 <div>
 <span>{{myAge}}</span>
 <input type="text" ng-model="myAge" />
 </div>
</script>

<script>
 var app = angular.module("app", []);

 app.controller('parentCtrl', function ($scope) {
 $scope.name = "mark";
 $scope.sexy = "male";
 $scope.age = "30";
 $scope.say = function (sth) {
  alert(sth);
 };
 })

 app.directive('childC', function () {
 return {
  restrict: 'E',
  scope: {
  myName: '=',
  mySexy: '=mySexyAttr',
  myAge: '@',
  onSay: '&'
  },
  template: function (elem, attr) {
  return "{}:" + document.getElementById('t1').innerHTML;
  },
  controller: function ($scope) {
  console.log($scope.myName);
  console.log($scope.mySexy);
  console.log($scope.myAge);
  $scope.onSay();
  }
 };
 });

</script>

@(or @Attr)绑定策略——本地作用域属性,使用@符号将本地作用域同DOM属性的值进行绑定。指令内部作用域可以使用外部作用域的变量。(单向引用父域对象)

<child-c my-age="{{age}}"></child-c>

ps:@ 是单向绑定本地作用域,记得加上{{}}

scope: {
 myAge: '@',
}

= (or =Attr)绑定策略——双向绑定:通过=可以将本地作用域上的属性同父级作用域上的属性进行双向的数据绑定。就像普通的数据绑定一样,本地属性会反映出父数据模型中所发生的改变。(双向引用父域对象)

<child-c onSay="name"></child-c>

ps:=策略不需要加上{{}}进行绑定

scope: {
 myName: '=',
}

& (or &Attr)绑定策略——通过&符号可以对父级作用域进行绑定,以便在其中运行函数。(调用父域函数)

<child-c on-say="say('i m ' + name)"></child-c>

ps:&对应的attrName必须以on-开头

scope: {
 onSay: '&',
}

父域绑定调用函数及传参

app.controller('parentCtrl', function ($scope) {
 $scope.say = function (sth) {
  alert(sth);
 };
})

ps特别注意:@与=对应的attr,;

<child-c my-name="name" my-sexy-attr="sexy" my-age="{{age}}" on-say="say('i m ' + name)"></child-c>

总结下来,scope扩展对象,既能够解耦父域与子域共域的问题,也能够实现指令与外界通讯的问题,是Angular开发指令化模块化的重要基础。在往后的章节,我会向大家介绍指令化开发的更多实例。

对外参数——require

scope是指令与外界作用域通讯的桥梁,而require是指令与指令之间通讯的桥梁。这个参数最大的作用在于,当要开发单指令无法完成,需要一些组合型指令的控件或功能,例如日期控件,通过require参数,指令可以获得外部其他指令的控制器,从而达到交换数据、事件分发的目的。

使用方法:require: String or Array——String值为引入指令名称,并且有两个寻找指令策略符号‘?'与‘^';Array数组则为多个外部指令名称。

在link函数第4个参数ctrl中获取注入外部指令的控制器,如果require为String,ctrl为对象,如果require是数组,ctrl为数组。

require: '^teacher1',
link: function ($scope, $element, $attrs, ctrl) {
 //ctrl指向teacher1指令的控制器
}

?策略——寻找指令名称,如果没有找到,link函数第4个参数为null;如果没有?,则报错。

^ 策略——在自身指令寻找指令名称的同时,向上父元素寻找;如果没有^,则仅在自身寻找。
如下例子,指令studentA向上可以找到指令teacher及自身,但是不能找到相邻兄弟的student-b。

<div teacher>
 <student-a></student-a>
 <student-b></student-b>
</div>

完整例子

<body>
 <div teacher>
 {{name}}
 <student-a></student-a>
 <student-b></student-b>
 </div>
 <script>
 var app = angular.module("app", []);

 //studentA——require指向父级指令teacher
 app.directive('studentA', function () {
  return {
  require: '?^teacher',
  scope: {},
  template: '<div>A`s teacher name: <span>{{teacherName}}</span></div>',
  link: function ($scope, $element, $attrs, ctrl) {
   //获取teacher指令控制器,并调用其方法sayName()
   $scope.teacherName = ctrl.sayName();
  }
  };
 });

 //studentB——require指向父级指令teacher,及指令studentA
 //但是,由于不能获得兄弟,也没有采取?策略,导致报错
 app.directive('studentB', function () {
  return {
  require: ['?^teacher', 'studentA'],
  scope: {},
  template: '<div>B`s teacher name: <span>{{teacherName}}</span></div>',
  link: function ($scope, $element, $attrs, ctrl) {
   $scope.teacherName = ctrl.sayName();
  }
  };
 });

 app.directive('teacher', function () {
  return {
  restrict: 'A',
  controller: function ($scope) {
   $scope.name = "Miss wang";

   //扩展控制器的方法sayName,目的是让外部内获取控制器内部数据
   this.sayName = function () {
   return $scope.name;
   };
  }
  };
 });
 </script>
</body>

既然require可以获取外部指令,那Angular原生指令应该也是能够获取。其中最广泛应用的就是require: 'ngModel',关于ngModel在自定义指令上的应用,留待下回实例中再跟大家深入讨论。

行为参数——link与controller

为什么要把link与controller两个参数放到一起?

因为很多童鞋会把它们错误地混淆使用,包括我自己。

link与controller都是描述指令行为的参数,但它们是要描述的行为是完全不同的类型。

controller语法 controller:String or Function

controller本身的意义就是赋予指令控制器,而控制器就是定义其内部作用域的行为的。

所以controller要描述的是:指令的作用域的行为。

//指向匿名控制器
controller: function ($scope) {
},
//指向控制器mainCtrl
controller: "mainCtrl"

link语法 link:String Or Function

link名称是链接函数,啥意思,好像挺难理解。所以在解释链接函数之前,先要说一下Angular的初始化对于指令究竟做了什么。

Angular在刚从HTTP Response接收静态素材之初,会首先去分析母页HTML中有哪些原生指令或自定义指令,然后再去加载指令的template模板HTML,而template模板中又去加载自己的指令模板,如此类推,直到Angular找到了所有的指令及模板,形成模板树,并返回模板函数,提供给下一阶段进行数据绑定。

<body>
 <stu1 ></ stu1>
 <script >
  var app = angular.module("app" , []);

  app.directive( 'stu1' , function () {
   return {
    restrict: 'E' ,
    template: "<p>1</p><stu2></stu2>" ,
    link: function (scope) {
     console.log( 'stu1 running' );
    }
   };
  });

  app.directive( 'stu2' , function () {
   return {
    restrict: 'E' ,
    template: "<p>2</p><stu3></stu3>" ,
    link: function (scope) {
     console.log( 'stu2 running' );
    }
   };
  });

  app.directive( 'stu3' , function () {
   return {
    restrict: 'E' ,
    template: "<p>3</p>" ,
    link: function (scope) {
     console.log( 'stu3 running' );
    }
   };
  });

 </script >
</ body>

console output

stu3 running
stu2 running
stu1 running

注意以上例子,在第一个断点stu3 running的时候,1 2 3 三个模板都渲染完成了。然后从最根部的stu3的link函数开始,依次执行stu 3 stu2 stu1的link函数。

简单来说就是:

  1. 加载模板,形成DOM模板树
  2. @@@@
  3. 数据绑定

@@@@是啥?没错,就是link链接函数,它会在形成模板树之后,在数据绑定之前,从最底部指令开始,逐个指令执行它们的link函数。

在这个时间节点的link函数,操作DOM的性能开销是最低,非常适合在这个时机执行DOM的操作,例如鼠标操作或触控事件分发绑定、样式Class设置、增删改元素等等。

所以link就是描述指令元素操作行为。

link: function (scope, element, attr, ctrl) {

 element.bind("click", function () {
  console.log("绑定点击事件");
 });

 element.append("<p>增加段落块</p>");

 //设置样式
 element.css("background-color", "yellow");

 //不推荐,在link中赋予scope行为
 scope.hello = function () {
  console.log("hello");
 };
}

同理,在link中定义$scope行为是不推荐的。

这样想想,对于controller与link,就明白了。但还有一个问题,它们俩的执行顺序是?答案是先controller,后link。

放到全局顺序就是:

  1. 执行controller,设置各个作用域scope
  2. 加载模板,形成DOM模板树
  3. 执行link,设置DOM各个行为
  4. 数据绑定,最后scope绑上DOM

例子

 <div student>
  {{name }}
 </div>
 <script>
  var app = angular.module("app", []);

  app.directive('student', function () {
   return {
    restrict: 'A',
    controller: function ($scope) {
     $scope.name = "tgor";

     console.log('controller running');
    },
    link: function (scope, el) {
     el.append("<p>hello</p>");

     console.log('link running');
    }
   };
  });

 </script>

总结

以上就是这篇文章的全部内容了,,还有compile、controllerAs……没有讲。写得累死,希望能给大家一些帮助,同时也可以将自己的错误暴露出来,望大家指正。之后会跟大家分享一下指令在实战中的一些实例。请大家继续关注我们。

(0)

相关推荐

  • Angularjs 创建可复用组件实例代码

    AngularJS框架可以用Service和Directive降低开发复杂性.这个特性非常适合用于分离代码,创建可测试组件,然后将它们变成可重用组件. Directive是一组独立的JavaScript.HTML和CSS,它们封装了一个特定的行为,它将成为将来创建的Web组件的组成部分,我们可以在各种应用中重用这些组件.在创建之后,我们可以直接通过一个HTML标签.自定义属性或CSS类.甚至可以是HTML注释,来执行一个Directive. 这一篇教程将介绍如何创建一个'自定义步长选择' Dir

  • AngularJS中$http使用的简单介绍

    在AngularJS中主要使用$http服务与远程http服务器交互,其作用类似于jquery中的$.ajax服务: $http是AngularJS的一个核心服务,利用浏览器的xmlhttprequest或者via JSONP对象与远程HTTP服务器进行交互: 与$.ajax相同,支持多种method请求:get.post.put.delete等: controller中可通过与$scope同样的方式获取$http对象,形如:function controller($ http, $ scope

  • Angular使用$http.jsonp发送跨站请求的方法

    本文实例讲述了Angular使用$http.jsonp发送跨站请求的方法.分享给大家供大家参考,具体如下: Angular中使用$http.jsonp发送跨站请求的实践中,遇到了下面的一些问题: 1. 不是所有返回json格式的url都支持jsonp,服务器端需要支持从url中读取返回函数并用它封装json数据. 2. AngularJS v1.6.1中,url中不能包含callback这个参数,而是用jsonpCallbackParam来指定 复制代码 代码如下: $http.jsonp('s

  • angularjs使用directive实现分页组件的示例

    闲来没事,分享下项目中自己写的分页组件.来不及了,直接上车. 效果: 输入框可任意输入,并会自动提交到该页 依赖项: fontawesome,bootstrap html: <ul class="page clearfix"> <li ng-hide="currentPage <= 1"> <a href="" ng-click=" rel="external nofollow"

  • angularJS之$http:与服务器交互示例

    在angularJS中与远程HTTP服务器交互时会用一个非常关键的服务-$http. $http是angular中的一个核心服务,利用浏览器的xmlhttprequest或者via JSONP对象与远程HTTP服务器进行交互. $http的使用方式和jquery提供的$.ajax操作比较相同,均支持多种method的请求,get.post.put.delete等. $http的各种方式的请求更趋近于rest风格. 在controller中可通过与$scope同样的方式获取$http对象,e.g.

  • angular2倒计时组件使用详解

    项目中遇到倒计时需求,考虑到以后在其他模块也会用到,就自己封装了一个组件.便于以后复用. 组件需求如下: - 接收父级组件传递截止日期 - 接收父级组件传递标题 组件效果 变量 组件countdown.html代码 <div class="count-down"> <div class="title"> <h4> {{title}} </h4> </div> <div class="body

  • Angular组件化管理实现方法分析

    本文实例分析了Angular组件化管理实现方法.分享给大家供大家参考,具体如下: 在做sass产品页面的时候,往往每个页面的header和footer都是一样的,还有最近我做的页面,类似datetimepicker这种组件,其实都是可以复用的代码,所以如果能把这些公用的UI组件提取出来,对于维护就会方便很多啦!! angular框架就支持这种组件化管理,不过也有优缺点,我先来说实现方法哈! index.html:没有用到路由,所以js都是src生引进来的 <head> <title>

  • 基于AngularJS前端云组件最佳实践

    AngularJS是google设计和开发的一套前端开发框架,他能帮助开发人员更便捷地进行前端开发.AngularJS是为了克服HTML在构建应用上的不足而设计的,它非常全面且简单易学习,因此AngularJS快速的成为了javascript的主流框架. 一.Amazing的Angular AnguarJS的特性 方便的REST: RESTful逐渐成为了一种标准的服务器和客户端沟通的方式.你只需使用一行javascript代码,就可以快速的从服务器端得到数据.AugularJS将这些变成了JS

  • 详解angular2封装material2对话框组件

    1. 说明 angular-material2自身文档不详,控件不齐,使用上造成了很大的障碍.这里提供一个方案用于封装我们最常用的alert和confirm组件. 2. 官方使用方法之alert ①编写alert内容组件 @Component({ template : `<p>你好</p>` }) export class AlertComponent { constructor(){ } } ②在所属模块上声明 //必须声明两处 declarations: [ AlertComp

  • angular 动态组件类型详解(四种组件类型)

    组件类型1:纯函数功能,而没有视图部分,即Factory(类似于$http) promise.component.html 常见的有内置的$http,$q之类的.一般使用promise与作用域进行交互 组件类型2:不是常驻于视图,而是动态插入的.有UI的一类组件,有输入交互.不常被调用(类似于Model对话框) factory.component.html 并发性.这里收到es6的启发.在factory内使用了构造函数,来区分不同的实例.当然,factory接口返回的类型要根据需求来定:仅仅是一

随机推荐