创建自定义指令(二)

一、使用嵌入包含

嵌入包含的意思是将一个文档的一部分通过引用插入到另一个文档中。在指令的上下文信息中,当你要创建一个可以包含任意内容的包装器指令时,这将十分有用。

<script type="text/ng-template" id="template">
        <div class="panel panel-default">
            <div class="panel-heading">
                <h4>This is the panel</h4>
            </div>
            <div class="panel-body" ng-transclude>
            </div>
        </div>
</script>
<script type="text/javascript">
        angular.module("exampleApp", [])
            .directive("panel", function () {
                return {
                    link: function (scope, element, attrs) {
                        scope.dataSource = "directive";
                    },
                    restrict: "E",
                    scope: true,
                    template: function () {
                        return angular.element(
                            document.querySelector("#template")).html();
                    },
                    transclude: true
                }
            })
            .controller("defaultCtrl", function ($scope) {
                $scope.dataSource = "controller";
            });
</script>

<body ng-controller="defaultCtrl">
    <panel>
        The data value comes from the: {{dataSource}}
    </panel>
</body>

我想要达到的效果:

 <div class="panel panel-default">
            <div class="panel-heading">
                <h4>This is the panel</h4>
            </div>
            <div class="panel-body" ng-transclude>
                 The data value comes from the:controller
            </div>
 </div>

之所以使用嵌入包含这样一个术语,因为内容时放在要插入到模板中的panel元素里面的。在使用嵌入包含时有两个特定步骤是必需的。第一步是在创建指令时将transclude定义属性设置为true,如下:transclude:true

第二步是将ng-transclude指令使用到模板中,就放在想插入被包装元素的地方。

设置transclude为true后,会对指令所应用到的元素内容进行包装,但并不是元素本身。如果你想包含进元素,就需要将transclude属性设置为element。

另外需要注意的是,被嵌入包含的内容中的表达式是在控制器作用域中被计算的,而不是指令的作用域。我再控制器的工厂函数中和指令的链接函数中都对dataSource属性定义了值,但是AngularJS的明智之处在于从控制器中取得了该值。我说明智是因为这种方法意味着将被嵌入包含的内容不需要知道它的数据是定义在哪个作用域中,你只需要不把嵌入包含当做一个需要考虑的问题,尽管写表达式就好了,让AngularJS自己去进行计算。

尽管如此,如果在计算嵌入包含表达式时你确实想将指令作用域考虑在内,只需确保将scope属性设置为false,如下

...
restrict:"E",
scope:false,
template:function(){
...

这确保了指令在指令作用域上操作,而且任何定义在链接函数中的值将影响嵌入包含的表达式。修改后显示如下:

 <div class="panel panel-default">
            <div class="panel-heading">
                <h4>This is the panel</h4>
            </div>
            <div class="panel-body" ng-transclude>
                 The data value comes from the:directive
            </div>
 </div>

二、使用编译函数

当指令特别复杂或者需要处理大量数据时,使用编译函数操作DOM并让链接函数执行其他任务,是比较有利的。除了性能以外使用编译函数还有一个好处,就是可以使用嵌入包含来重复生成内容的能力,就像ng-repeat所做的那样。

<body ng-controller="defaultCtrl" class="panel panel-body" >
    <table class="table table-striped">
        <thead><tr><th>Name</th><th>Price</th></tr></thead>
        <tbody>
            <tr simple-repeater source="products" item-name="item">
                <td>{{item.name}}</td><td>{{item.price | currency}}</td>
            </tr>
        </tbody>
    </table>
    <button class="btn btn-default text" ng-click="changeData()">Change</button>
</body>
angular.module("exampleApp", [])
            .controller("defaultCtrl", function ($scope) {
                $scope.products = [{ name: "Apples", price: 1.20 },
                    { name: "Bananas", price: 2.42 }, { name: "Pears", price: 2.02 }];

                $scope.changeData = function () {
                    $scope.products.push({ name: "Cherries", price: 4.02 });
                    for (var i = 0; i < $scope.products.length; i++) {
                        $scope.products[i].price++;
                    }
                }
            })
            .directive("simpleRepeater", function () {
                return {
                    scope: {
                        data: "=source",
                        propName: "@itemName"
                    },
                    transclude: ‘element‘,
                    compile: function (element, attrs, transcludeFn) {
                        return function ($scope, $element, $attr) {
                            $scope.$watch("data.length", function () {
                                var parent = $element.parent();
                                parent.children().remove();
                                for (var i = 0; i < $scope.data.length; i++) {
                                    var childScope = $scope.$new();
                                    childScope[$scope.propName] = $scope.data[i];
                                    transcludeFn(childScope, function (clone) {
                                        parent.append(clone);
                                    });
                                }
                            });
                        }
                    }
                }
            });

在HTML中使用source属性指定了数据对象的来源,并使用item-name属性指定了在嵌入包含的模板中可被用于应用当前对象的名称。我的目标是对每个product对象重复生成tr元素,所以我设置了transclude定义对象的element,也就是说元素本身将被包含于嵌入包含中,而不是其内容。我也可以将我的指令应用在tbody元素上并设置transclude属性为true。

这个指令的核心部分是编译函数,是由compile属性指定的。编译函数被传入三个参数:指令所应用到的元素,该元素的属性,以及一个可用于创建嵌入包含元素的拷贝的函数。编译函数会返回一个链接函数(当compile属性被使用时link属性会被忽略)。这可能看起来有点奇怪,但是请记住编译函数的目的是为了修改DOM,所以从编译函数返回一个链接函数是很有帮助的,因为它提供了一个简易的将数据从指令的一部分传递到下一部分的方法。编译函数应当仅仅是操作DOM的,所以并没有为他提供作用域,但是编译函数返回的链接函数可以声明对scope,element,attrs参数的依赖,对应于普通链接函数中的各个参数。

理解编译函数:通过调用$scope.$new方法创建了一个新的作用域。对于嵌入包含内容的每个实例,这允许我将一个不同的对象赋给item属性,使用的是如下方法进行克隆的:

...
transcludeFn(childScope,function(clone){
    parent.append(clone);
})
...

对于每个数据对象,调用了传给编译函数的嵌入包含函数。第一个参数是包含item属性的子作用域,item属性设置为当前数据项。第二个参数是一个传入了嵌入包含内容的一组拷贝函数,这份拷贝被使用jqLite添加到父元素下。结果是对于每个数据对象生成了指令所应用到的tr元素的一份拷贝,并且创建了一个新的作用域,在这个作用域中允许嵌入包含内容使用item来引用当前数据对象。

三、在指令中使用控制器

指令能够创建出被其他指令所用的控制器。这允许指令被组合起来创建出更复杂的组件。

<body ng-controller="defaultCtrl">
    <div class="panel panel-default">
        <div class="panel-body">
            <table class="table table-striped" product-table="totalValue" product-data="products" ng-transclude>
                <tr>
                    <th>Name</th>
                    <th>Quantity</th>
                </tr>
                <tr ng-repeat="item in products" product-item></tr>
                <tr>
                    <th>Total:</th>
                    <td>{{totalValue}}</td>
                </tr>
            </table>
        </div>
    </div>
</body>
<script type="text/ng-template" id="productTemplate">
        <td>{{item.name}}</td>
        <td>
            <input ng-model=‘item.quantity‘ />
        </td>
</script>
angular.module("exampleApp", [])
        .controller("defaultCtrl", function($scope) {
            $scope.products = [{
                name: "Apples",
                price: 1.20,
                quantity: 2
            }, {
                name: "Bananas",
                price: 2.42,
                quantity: 3
            }, {
                name: "Pears",
                price: 2.02,
                quantity: 1
            }];
        })
        .directive("productItem", function() {
            return {
                template: document.querySelector("#productTemplate").outerText,
                require: "^productTable",
                link: function(scope, element, attrs, ctrl) {
                    scope.$watch("item.quantity", function() {
                        ctrl.updateTotal();
                    });
                }
            }
        })
        .directive("productTable", function() {
            return {
                transclude: true,
                scope: {
                    value: "=productTable",
                    data: "=productData"
                },
                controller: function($scope, $element, $attrs) {
                    this.updateTotal = function() {
                        var total = 0;
                        for (var i = 0; i < $scope.data.length; i++) {
                            total += Number($scope.data[i].quantity);
                        }
                        $scope.value = total;
                    }
                }
            }
        });

controller用于为指令创建一个控制器,这个函数可以声明对作用域的依赖,对指令所应用到的元素的依赖,和对该元素属性的依赖。require定义对象属性用于声明对控制器的依赖,属性值是指令名和一个可选的前缀。

可用的require属性值的前缀:

None  假定两个指令都应用与同一个元素

^ 在指令所应用到的元素的父元素上查找另一个指令

? 如果找不到指令并不报错——小心使用

我指定了名称为productTable,以及前缀^,需要这样指定是因为productTable指令被应用在productItem指令所应用到的元素的父元素上。为了使用控制器中定义的功能,我在链接函数上指定了一个附加参数,如下:

link:function(scope,element,attrs,ctrl){}

控制器参数不能被依赖注入,所以你可以调用任何你想调的东西,我的个人习惯是使用名称ctrl。做了这些修改后,我就可以调用控制器中的函数了,就像它们已经定义在本地指令中一样。

我在调用一个控制器方法,作为一个执行计算的信号,并不需要任何参数,但是你可以从一个控制器传递数据到另一个,只要你记着传给控制器函数的scope参数是定义控制器的那个指令中的作用域就可以了,而不是引用该控制器的那个指令的作用域。

添加另一个指令

定义控制器函数的价值来自于对功能进行分离和重用的能力,从而无需构建和测试单个庞大的组件。在上面的例子中,productTable控制器并不知道productItem控制器的设计或实现,也就是说我可以独立地测试他们并任意修改,只要productTable控制器仍然继续提供updateTotal函数即可。

这种方法也允许你能够混合搭配各种指令的功能,从而在一个程序里创建出各种功能的不同组合。

<script type="text/ng-template" id="resetTemplate">
        <td colspan="2">
            <button ng-click="reset()">Reset</button>
        </td>
</script>
<body ng-controller="defaultCtrl">
    <div class="panel panel-default">
        <div class="panel-body">
            <table class="table table-striped" product-table="totalValue" product-data="products" ng-transclude>
                <tr>
                    <th>Name</th>
                    <th>Quantity</th>
                </tr>
                <tr ng-repeat="item in products" product-item></tr>
                <tr>
                    <th>Total:</th>
                    <td>{{totalValue}}</td>
                </tr>
                <tr reset-totals product-data="products" property-name="quantity"></tr>
            </table>
        </div>
    </div>
</body>
.directive("resetTotals", function() {
            return {
                scope: {
                    data: "=productData",
                    propname: "@propertyName"
                },
                template: document.querySelector("#resetTemplate").outerText,
                require: "^productTable",
                link: function(scope, element, attrs, ctrl) {
                    scope.reset = function() {
                        for (var i = 0; i < scope.data.length; i++) {
                            scope.data[i][scope.propname] = 0;
                        }
                        ctrl.updateTotal();
                    }
                }

            }
        });

新的指令名为resetTotals,它向表格中添加了一个reset按钮,可以将所有的数量清零,在一个隔离的作用域上提供了数据数组和要清零的属性名称,该指令就可以通过数据绑定查找到要清零的位置。在值被重置后,resetTotals指令调用了productTable指令所提供的updateTotal方法。

时间: 2024-11-17 16:26:37

创建自定义指令(二)的相关文章

带你走近AngularJS - 创建自定义指令

为什么使用AngularJS 指令? 使用过 AngularJS 的朋友应该最感兴趣的是它的指令.现今市场上的前端框架也只有AngularJS 拥有自定义指令的功能,并且AngularJS 是目前唯一提供Web应用可复用能力的框架. 目前有很多JavaScript 产品提供插件给Web开发人员.例如, Bootstrap 就是当前比较流行的提供样式和JavaScript插件的前端开发工具包.但是开发人员在使用Booostrap中的插件时, 必须切换到JavaScript 模式来写 jQuery

angular创建自定义指令的四种方式

angular除了内置的部分指令,还可以通过.directive来自定义指令.要调用自定义指令,HTML 元素上需要添加自定义指令名.使用驼峰法来命名一个指令:nsHeader,在调用时使用需要-来分割:ns-header.自定义指令调用的的方式有四种,如下: 元素名 属性 类名 注释 1.使用元素名调用: 1 <!DOCTYPE html> 2 <html ng-app="myApp"> 3 <head> 4 <meta charset=&q

AngularJS -- 指令(创建自定义指令)

什么是指令 注:本指南是针对已经熟悉AngularJS基础知识的开发人员.如果你才刚刚开始,我建议查看系列教程. 指令是一个Dom元素上的标签(和元素上的属性, CSS 类样式一样,属于这个Dom元素),它告诉AngualrJS的HTML 编译器($compile),去附加一个行为到这个Dom元素上去,这个行为可以改变这个Dom元素,或者这个Dom元素的子元素. AngularJS 有一套自己内置的指令,如:ngBind,ngModel,ngClass等等...你可以自定义的指令.当Angula

angular.js创建自定义指令-demo3

html: <!doctype html><html ng-app="myModule"> <head> <meta charset="utf-8"> </head> <body> <hello></hello> </body> <script src="js/angular-1.3.0.js"></script>

angularjs 表单验证ngMessages和创建自定义指令结合

index.html 1 <!doctype html> 2 <html > 3 <head> 4 <meta charset="utf-8"> 5 </head> 6 <style> 7 /*input.ng-invalid { 8 border: 1px solid red; 9 } 10 input.ng-valid { 11 border: 1px solid green; 12 }*/ 13 </s

AngularJS之基础-5 路由(定义路由、使用路由)、自定义指令(Directive)

一.定义路由 ng-view - AngularJS 支持通过在单页面上的多个视图的单页应用 - ng-view 标记只是简单地创建一个占位符 - 使用 ng-template - 创建使用script标签的HTML视图 - 使用 - 定义类型作为主模块中 ng-template 的脚本块 $routeProvider - 映射相应的HTML页面或ng-template - 附加一个控制器使用相同键的服务   - 注意: - 需要angular-route.js脚本文件的引用 二.使用路由 锚点

AngularJS自定义指令详解(有分页插件代码)

前言 除了 AngularJS 内置的指令外,我们还可以创建自定义指令. 通过 .directive() 函数来添加自定义的指令. 调用自定义指令时,需要在HTMl 元素上添加自定义指令名. 自定义指令命名规则:使用驼峰命名法来命名,即除第一个单词外的首字母需大写.如: myDirective. 在html页面调用该指令时需要以 - 分割,如: my-directive.示例代码: <body ng-app="myApp"> <my-directive><

AngularJS 自定义指令详解

版权声明:本文为博主原创文章,未经博主允许不得转载. //blog.csdn.net/qq_27626333/article/details/52261409 除了 AngularJS 内置的63个指令外,我们还可以创建自定义指令.你可以使用 .directive 函数来添加自定义的指令.要调用自定义指令,HTML 元素上需要添加自定义指令名.使用驼峰法来命名一个指令, runoobDirective, 但在使用它时需要以 - 分割, runoob-directive,自定义指令的参数如下: [

angular的自定义指令---详解

1.angualr指令 在angualr自己里面有许多丰富的指令,但都是平时所常见的,但对于自己所需要的可能有所欠缺,所以自己可能会摒弃原声指令,自己封装更为健壮灵活的指令: 其实angular里面的指令也是基于下面所用到的步骤来创建的,只不过他都添加到了全局中,所以可以直接使用: 2.创建自定义指令 首先创建模块app,再使用app的服务directive方法, 创建指令内容第一个参数自定义的名字,第二个参数是自定义参数属性对象,该对象包括的属性基本在代码注释解释清楚: // a.创建模块 v