Angular Directive 详解

Angular Directive 学习

学习目的:为了更好的了解 ng directive 的使用方法。

Directive多是AngularJS中比较复杂的一个东西了。通常咱们将其理解成指令。AngularJS自带了很多预设的指令,好比ng-app,ng-controller这些。能够发现个特色,AngularJS自带的指令都是由ng-打头的。javascript

那么,Directive到底是个怎么样的一个东西呢?我我的的理解是这样的:将一段html、js封装在一块儿,造成一个可复用的独立个体,具体特定的功能。下面咱们来详细解读一下Directive的通常性用法。html

AnguarJS directive的经常使用定义格式以及参数说明

看下面的代码:

var myDirective = angular.module('directives', []);

myDirective.directive('directiveName', function($inject) {
    return {
        template: '<div></div>',
        replace: false,
        transclude: true,
        restrict: 'E',
        scope: {},
        controller: function($scope, $element) {

        },
        complie: function(tElement, tAttrs, transclude) {
            return {
                pre: function preLink(scope, iElement, iAttrs, controller) {

                },
                post: function postLink(scope, iElement, iAttrs, controller) {

                }
            };
        },
        link: function(scope, iElement, iAttrs) {

        }
    };
});
复制代码
  • 这里直接return了一个object,对象中包括比较多的属性,这些属性都是对自定义directive的定义。详细的含义,下面会继续说明。
  • return对象参数说明
return {
    name: '',
    priority: 0,
    terminal: true,
    scope: {},
    controller: fn,
    require: fn,
    restrict: '',
    template: '',
    templateUrl: '',
    replace: '',
    transclude: true,
    compile: fn,
    link: fn
}
复制代码

如上所示,return的对象中会有不少的属性,这行属性都是用来定义directive的。java

下面咱们来一个个的说明他们的做用。

  • namejquery

    • 表示当前scope的名称,通常声明时使用默认值,不用手动设置此属性。
  • priorityangularjs

    • 优先级。当有多个directive定义在同一个DOM元素上时,有时须要明确他们的执行顺序。这个属性用于在directive的compile function调用以前进行排序。若是优先级相同,则执行顺序是不肯定的(根据经验,优先级高的先执行,相同优先级时按照先绑定后执行)。
  • teminalexpress

    • 最后一组。若是设置为true,则表示当前的priority将会成为最后一组执行的directive,即比此directive的priority更低的directive将不会执行。同优先级依然会执行,可是顺序不肯定。
  • scopesegmentfault

    • true
      • 将为这个directive建立一个新的scope。若是在同一个元素中有多个directive须要新的scope的话,它仍是只会建立一个scope。新的做用域规则不适用于根模版,由于根模版每每会得到一个新的scope。
    • {}
      • 将建立一个新的、独立的scope,此scope与通常的scope的区别在于它不是经过原型继承于父scope的。这对于建立可复用的组件是颇有帮助的,能够有效的防止读取或者修改父级scope的数据。这个独立的scope会建立一个拥有一组来源于父scope的本地scope属性hash集合。这些本地scope属性对于模版建立值的别名颇有帮助。本地的定义是对其来源的一组本地scope property的hash映射。
  • controllerbash

    • controller构造函数。controller会在pre-linking步骤以前进行初始化,并容许其余directive经过指定名称的require进行共享。这将容许directive之间相互沟通,加强相互之间的行为。controller默认注入了如下本地对象:
      • $scope 与当前元素结合的scope
      • $element 当前的元素
      • $attrs 当前元素的属性对象
      • $transclude 一个预先绑定到当前scope的转置linking function
  • requireapp

    • 请求另外的controller,传入当前directive的linking function中。require须要传入一个directive controller的名称。若是找不到这个名称对应的controller,那么将会抛出一个error。名称能够加入如下前缀:
      • ? 不要抛出异常。这将使得这个依赖变为一个可选项
      • ^ 容许查找父元素的controller
  • restrict异步

    • EACM的子集的字符串,它限制了directive为指定的声明方式。若是省略的话,directive将仅仅容许经过属性声明
      • E 元素名称:
      • A 属性名:
      • C class名:
      • M 注释:
  • template

    • 若是replace为true,则将模版内容替换当前的html元素,并将原来元素的属性、class一并转移;若是replace为false,则将模版元素看成当前元素的子元素处理。
  • templateUrl

    • 与template基本一致,但模版经过指定的url进行加载。由于模版加载是异步的,全部compilation、linking都会暂停,等待加载完毕后再执行。
  • replace

    • 若是设置为true,那么模版将会替换当前元素,而不是做为子元素添加到当前元素中。(为true时,模版必须有一个根节点)
  • transclude

    • 编译元素的内容,使它可以被directive使用。须要在模版中配合ngTransclude使用。transclusion的有点是linking function可以获得一个预先与当前scope绑定的transclusion function。通常地,创建一个widget,建立独立scope,transclusion不是子级的,而是独立scope的兄弟级。这将使得widget拥有私有的状态,transclusion会被绑定到父级scope中。(上面那段话没看懂。但实际实验中,若是经过调用myDirective,而transclude设置为true或者字符串且template中包含的时候,将会将的编译结果插入到sometag的内容中。若是any的内容没有被标签包裹,那么结果sometag中将会多了一个span。若是原本有其余东西包裹的话,将维持原状。但若是transclude设置为’element’的话,any的总体内容会出如今sometag中,且被p包裹)
      • true/false 转换这个directive的内容。(这个感受上,是直接将内容编译后搬入指定地方)
      • ‘element’ 转换整个元素,包括其余优先级较低的directive。(像将总体内容编译后,看成一个总体(外面再包裹p),插入到指定地方)
  • compile

    • 这里是compile function,将在下面实例详细说明
  • link

    • 这里是link function ,将在下面实例详细讲解。这个属性仅仅是在compile属性没有定义的状况下使用。

关于scope

这里关于directive的scope为一个object时,有更多的内容很是有必要说明一下。看下面的代码:

scope: {
    name: '=',
    age: '=',
    sex: '@',
    say: '&'
}
复制代码

这个scope中关于各类属性的配置出现了一些奇怪的前缀符号,有=,@,&,那么这些符号具体的含义是什么呢?再看下面的代码:

  • html
<div my-directive name="myName" age="myAge" sex="male" say="say()"></div>
复制代码
  • javascript
function Controller($scope) {
    $scope.name = 'Pajjket';
    $scope.age = 99;
    $scope.sex = '我是男的';
    $scope.say = function() {
        alert('Hello,我是弹出消息');
    };
}
复制代码
能够看出,几种修饰前缀符的大概含义:
  • =: 指令中的属性取值为Controller中对应$scope上属性的取值
  • @: 指令中的取值为html中的字面量/直接量
  • &: 指令中的取值为Controller中对应$scope上的属性,可是这个属性必须为一个函数回调 下面是更加官方的解释:
  • =或者=expression/attr

在本地scope属性与parent scope属性之间设置双向的绑定。若是没有指定attr名称,那么本地名称将与属性名称一致。

  • 例如: 中,widget定义的scope为:{localModel: '=myAttr'},那么widget scope property中的localName将会映射父scope的parentModel。若是parentModel发生任何改变,localModel也会发生改变,反之亦然。即双向绑定。

  • @或者@attr 创建一个local scope property到DOM属性的绑定。由于属性值老是String类型,因此这个值总返回一个字符串。若是没有经过@attr指定属性名称,那么本地名称将与DOM属性的名称一致。 例如: ,widget的scope定义为:{localName: '@myAttr'}。那么,widget scope property的localName会映射出"hello "转换后的真实值。当name属性值发生改变后,widget scope的localName属性也会相应的改变(仅仅是单向,与上面的=不一样)。那么属性是在父scope读取的(不是从组件的scope读取的)

  • &或者&attr 提供一个在父scope上下文中执行一个表达式的途径。若是没有指定attr的名称,那么local name将与属性名一致。

    • 例如:

<widget my-attr="count = count + value">,widget的scope定义为:{localFn:’increment()’},那么isolate scope property localFn会指向一个包裹着increment()表达式的function。 通常来讲,咱们但愿经过一个表达式,将数据从isolate scope传到parent scope中。这能够经过传送一个本地变量键值的映射到表达式的wrapper函数中来完成。例如,若是表达式是increment(amount),那么咱们能够经过localFn({amount:22})的方式调用localFn以指定amount的值。

directive 实例讲解

下面的示例都围绕着上面所做的参数说明而展开的。

  • directive声明实例
// 自定义directive
var myDirective = angular.modeule('directives', []);

myDirective.directive('myTest', function() {
    return {
        restrict: 'EMAC',
        require: '^ngModel',
        scope: {
            ngModel: '='
        },
        template: '<div><h4>Weather for {{ngModel}}</h4</div>'
    };
});

// 定义controller
var myControllers = angular.module('controllers', []);
myControllers.controller('testController', [
    '$scope',
    function($scope) {
        $scope.name = 'this is directive1';
    }
]);


var app = angular.module('testApp', [
    'directives',
    'controllers'
]);

<body ng-app="testApp">
    <div ng-controller="testController">
        <input type="text" ng-model="city" placeholder="Enter a city" />
        <my-test ng-model="city" ></my-test>
        <span my-test="exp" ng-model="city"></span>
        <span ng-model="city"></span>
    </div>
</body>
复制代码

template与templateUrl的区别和联系

templateUrl其实根template功能是同样的,只不过templateUrl加载一个html文件,上例中,咱们也能发现问题,template后面根的是html的标签,若是标签不少呢,那就比较不爽了。能够将上例中的,template改一下。

myDirective.directive('myTest', function() {
    return {
        restrict: 'EMAC',
        require: '^ngModel',
        scope: {
            ngModel: '='
        },
        templateUrl:'../partials/tem1.html'   //tem1.html中的内容就是上例中template的内容。
    }
});
复制代码

scope重定义

//directives.js中定义myAttr
myDirectives.directive('myAttr', function() {
    return {
        restrict: 'E',
        scope: {
            customerInfo: '=info'
        },
        template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
                  'Name: {{vojta.name}} Address: {{vojta.address}}'
    };
});

//controller.js中定义attrtest
myControllers.controller('attrtest',['$scope',
    function($scope) {
        $scope.naomi = {
            name: 'Naomi',
            address: '1600 Amphitheatre'
        };
        $scope.vojta = {
            name: 'Vojta',
            address: '3456 Somewhere Else'
        };
    }
]);

// html中
<body ng-app="testApp">
    <div ng-controller="attrtest"> <my-attr info="naomi"></my-attr> </div> </body>
复制代码

其运行结果以下:

Name: Naomi Address: 1600 Amphitheatre      //有值,由于customerInfo定义过的
Name: Address:                              //没值 ,由于scope重定义后,vojta是没有定义的
复制代码

咱们将上面的directive简单的改一下,

myDirectives.directive('myAttr', function() {
    return {
        restrict: 'E',
        template: 'Name: {{customerInfo.name}} Address: {{customerInfo.address}}<br>' +
                  'Name: {{vojta.name}} Address: {{vojta.address}}'
    };
});
复制代码
  • 运行结果以下:
Name: Address:
Name: Vojta Address: 3456 Somewhere Else
复制代码

由于此时的directive没有定义独立的scope,customerInfo是undefined,因此结果正好与上面相反。

transclude的使用

  • transclude的用法,有点像jquery里面的$().html()功能
myDirective.directive('myEvent', function() {
    return {
        restrict: 'E',
        transclude: true,
        scope: {
            'close': '$onClick'      //根html中的on-click="hideDialog()"有关联关系
        },
        templateUrl: '../partials/event_part.html'
    };
});

myController.controller('eventTest', [
    '$scope',
    '$timeout',
    function($scope, $timeout) {
        $scope.name = 'Tobias';
        $scope.hideDialog = function() {
            $scope.dialogIsHidden = true;
            $timeout(function() {
                $scope.dialogIsHidden = false;
            }, 2000);
        };
    }
]);
复制代码
<body ng-app="phonecatApp">
    <div ng-controller="eventtest">
        <my-event ng-hide="dialogIsHidden" on-click="hideDialog()">
            Check out the contents, {{name}}!
        </my-event>
    </div>
</body>

<!--event_part.html -->
<div>
    <a href ng-click="close()">×</a>
    <div ng-transclude></div>
</div>
复制代码
  • 说明:这段html最终的结构应该以下所示:
<body ng-app="phonecatApp">
    <div ng-controller="eventtest">
        <div ng-hide="dialogIsHidden" on-click="hideDialog()">
            <span>Check out the contents, {{name}}!</span>
        </div>
    </div>
</body>
复制代码
  • 将原来的html元素中的元素Check out the contents, !插入到模版的
    中,还会另外附加一个标签。 controllerlinkcompile之间的关系
myDirective.directive('exampleDirective', function() {
    return {
        restrict: 'E',
        template: '<p>Hello {{number}}!</p>',
        controller: function($scope, $element){
            $scope.number = $scope.number + "22222 ";
        },
        link: function(scope, el, attr) {
            scope.number = scope.number + "33333 ";
        },
        compile: function(element, attributes) {
            return {
                pre: function preLink(scope, element, attributes) {
                    scope.number = scope.number + "44444 ";
                },
                post: function postLink(scope, element, attributes) {
                    scope.number = scope.number + "55555 ";
                }
            };
        }
    }
});

//controller.js添加
myController.controller('directive2',[
    '$scope',
    function($scope) {
        $scope.number = '1111 ';
    }
]);

//html
<body ng-app="testApp">
    <div ng-controller="directive2"> <example-directive></example-directive> </div> </body>
复制代码
  • 上面小例子的运行结果以下:
Hello 1111 22222 44444 5555 !
复制代码

由结果能够看出来,controller先运行,compile后运行,link不运行。 咱们如今将compile属性注释掉后,获得的运行结果以下: Hello 1111 22222 33333 !

由结果能够看出来,controller先运行,link后运行,link和compile不兼容。通常地,compile比link的优先级要高。

参考

相关文章
相关标签/搜索