`

《用AngularJS开发下一代Web应用》指令学习笔记

阅读更多

一.指令的作用:实现语义化标签

我们常用的HTML标签是这样的:

<div>
    <span>目录</span>
</div>

而使用AngularJS的directive(指令)机制,我们可以实现这样的东西

<tabset>
    <tab title='Home'>
        <p>Welcome home!</p>
    </tab>
    <tab title='Preferences'>
         <p>Content</p>
    </tab>
<tabset>

和JSP或者Struts等等框架里面的taglib功能一样,只不过这里是使用JavaScript来实现的。

 

二.简单实例

DirectiveStudy01.html

<html ng-app='app'>
    <body>
        <hello></hello>
    </body>
    <script src="lib/angular/angular.js"></script>
    <script src="directive/HelloDirect.js"></script>
</html>

HelloDirect.js

var appModule = angular.module('app', []);
appModule.directive('hello', function() {
    return {
        restrict: 'E',
        template: '<div>Hi there</div>',
        replace: true
    };
});

运行结果:


        对于DirectiveStudy01.html里面的<hello>标签,浏览器显然是不认识的,它唯一能做的事情就是无视这个标签。那么,为了让浏览器能够认识这个标签,我们需要使用Angular来定义一个hello指令(本质上说就是自己来把<hello>替换成浏览器能识别的那些标准HTML标签)。

        从运行结果可以看到,<hello>已经被<div>Hi there</div>这个标签替换掉了,这也是以上JS代码里面replace:true这行配置的作用,代码里面的template配置项当然就是我们要的div标签啦,至于restrict:'E'这个配置项的含义,请看下表:


        当然,如果需要替换的HTML标签很长,显然不能用拼接字符串的方式来写,这时候我们可以用templateUrl来替代template,从而可以把模板写到一个独立的HTML文件中。

 

三.transclude(变换)

        可以通过transclude属性将内容插入新的模板。当transcluden属性设置为true时,指令会删除原来的内容,使你的模板可以用ng-transclude指令进行重新插入。

DirectiveStudy02.html

<html ng-app='app'>
    <body>
        <div hello>Bob</div>
    </body>
    <script src="lib/angular/angular.js"></script>
    <script src="directive/HelloDirect02.js"></script>
</html>
HelloDirect02.js
var appModule = angular.module('app', []);
appModule.directive('hello', function() {
    return {
        template: '<div>Hi there <span ng-transclude></span></div>',
        transclude: true
    };
});
运行结果:

        和第一个例子对比,这个例子的JS和HTML代码都略有不同,JS代码里面多了一个transclude: true,HTML代码里面在<hello>内部出现了子标签。

        按照我们在第一个例子中的说法,指令的作用是把我们自定义的语义化标签替换成浏览器能够认识的HTML标签。那好,如果我们自定义的标签内部出现了子标签,应该如何去处理呢?很显然,transclude就是用来处理这种情况的。

        对于当前这个例子,transclude的作用可以简化地理解成:把<hello>标签替换成我们所编写的HTML模板,但是<hello>标签内部的内容保持不变

         很显然,由于我们没有加replace:true选项,所以<hello>标签还在,没有被替换掉。

 

四.compile和link函数

        虽然插入模板的方式很有用,但是对于指令来说,真正有趣的工作发生在compile和link函数中。

        compile和link这两个函数是根据Angular创建动态视图的两个处理阶段来命名的。从高层来看Angular的初始化过程,它们依次如下:

1.加载脚本

        加载Angular库,并查找ng-app指令,从而找到应用的边界。

2.编译阶段

        在这个阶段,Angular将会遍历DOM结构,标识出模板中注册的所有指令。对于每一条指令,它会根据指令定义的规则(template、replace、transclude等)来转换DOM结构,如果存在compile函数,则调用它。调用compile函数将得到一个编译好的template函数,它将会调用从所有指令中搜集而来的link函数。

3.连接阶段

        为了让视图成为动态的,Angular会对每一条指令运行一个link函数。link函数的一般操作是在DOM或者模型上创建监听器,监听器会使视图和模型的内容随时保持同步。

        总结来说,后两个阶段就负责转换模板的编译阶段,以及修改视图中数据的连接阶段。在以上内容中,指令中的compile和link函数的主要不同点在于,compile函数用来对模板自身进行转换,而link函数负责在模型和视图之间进行动态关联。作用域在连接阶段才会被绑定到编译之后的link函数上,然后再通过数据绑定技术,指令就变成了动态的。

        出于性能方面的考虑,这两个阶段是分开处理的。compile函数仅仅在编译阶段运行一次,而link函数会执行很多次——对于指令的每个实例,link函数都会执行一次。例如,在指令的上一层标签里面使用了ng-repeat,这里你并不想调用compile函数,因为那样会导致对ng-repeat中的每一次遍历都进行一次DOM-walk(DOM遍历)。希望只编译一次,然后再连接。

        当然,你需要知道compile和link函数之间的不同点以及它们各自的功能。对于你将编写的大多数指令来说,并不需要对模板进行转换,所以,大部分情况下只要编写link函数就可以了。

        为了进行比较,再分别看一下compile和link函数的语法。对于compile函数,语法是这样的:

compile: function compile(tElement, tAttrs, transclude) {
    return {
       pre: function preLink(scope, iElement, iAttrs, controller) {...},
       post: function postLink(scope, iElement, iAttrs, controller) {...}
    }
}

        对于link函数,是这样:

link: function postLink(scope, iElement, iAttrs) {...}

        注意这里有一个不同点,即link函数会访问scope(作用域)对象,而compile不会。这是因为,scope对象在编译阶段还不存在。当然你也可以在compile函数中返回link函数,这些link函数仍然可以访问scope对象。

        同时请注意,compile和link都会接收到对应DOM元素的引用以及元素的属性列表。这里的不同点是,compile函数会接收模板元素及其属性列表,所以函数形参带有一个t前缀;而link函数会接收到视图实例对象,视图实例是使用模板创建的,所以函数形参带有一个i前缀。

        只有当一个指令位于其他指令内部,而外部指令会生成模板的多份拷贝时,这种差异性才会显得比较重要。ng-repeat指令就是一个很好的例子。

DirectiveCompileAndLink.html

<html ng-app='app'>
    <body ng-controller='MyController'>
        <div ng-repeat='thing in things'>
            {{thing}}.<hello></hello>
        </div>
    </body>
    <script src="lib/angular/angular.js"></script>
    <script src="directive/CompileAndLink.js"></script>
</html>

CompileAndLink.js

var appModule = angular.module('app', []);
appModule.directive('hello', function() {
    return {
        restrict: 'E',
        template: '<span>Hi there</span>',
        replace: true
    };
});
appModule.controller('MyController',function($scope) {
    $scope.things = [1,2,3,4,5,6];
});

运行结果:


        这里,compile函数只会被调用一次,而link函数的调用次数等于things中的元素个数——对于{{thing}}.<hello></hello>的每一份拷贝,它都会被调用一次。所以,如果对{{thing}}.<hello></hello>的每一份拷贝(实例)都有一些共同的东西需要修改,那么,出于效率方面的考虑,最好在compile函数里面来做这件事情。

    你还会注意到,compile函数会接受一个transclude函数作为属性。如果你需要对内容进行变换,而简单的基于模板的变换并没有提供这种功能,那么,你可以在这里编写一个函数,使用程序的方式对内容进行变换。

    最后,compile可以返回preLink和postLink函数,而link函数只会返回postLink函数。正如它的名称所表示的,preLink会在编译阶段之后、指令连接到子元素之前运行。类似的,postLink会在所有子元素指令都连接之后才运行。这就意味着,如果你需要修改DOM结构,你应该在postLink中来做这件事情,而如果在preLink中做这件事情会破环绑定过程,并导致错误。

 

五.扩展条实例Expander

        我们经常需要在指令中访问scope对象,以便观察数据模型的值,当这些值发生变化时刷新UI。当你使用jQuery、Closure或其他类库来封装一些非Angular组件时,或者实现简单的DOM事件时,或者把一个Angular表达式传递给指令的属性然后执行时,都需要访问scope对象。

        如果出于以上某种原因需要一个scope对象,那么scope对象的类型有以下三种选择:

        1.指令对应的DOM元素上存在的scope对象。

        2.可以创建一个新的scope对象,它继承了外层控制器的scope。在继承树中,位于当前scope对象上方的所有scope对象的值都可以被读取。对于DOM元素里面的任何其他指令,如果需要这种类型的scope,也可以共享这个scope,并且可以用它和树中其他scope进行通信。

        3.使用独立的scope对象,它不会从父对象上继承模型的任何属性。当创建可复用的组件,并且需要把当前指令的操作和父scope隔离开时,你就需要使用这个选项。

        可以使用以下配置语法来创建这些指令:


        当创建独立scope时,默认情况是不可以访问父scope模型中的任何东西。但是,你可以指定把某些属性传递到你的指令中,你可以把这些属性名称看成函数的形参。

        注意,虽然独立的scope不会继承模型的属性,但是它们仍然是父scope的孩子。与所有其他scope一样,它们带有一个指向父scope对象的$parent属性。

        你可以通过传递属性名映射的方式把父scope中指定的属性传递给这个独立的scope。有三种方法可以在scope和父scope之间传递数据,我们把这三种方式叫做“绑定策略”,你还可以选择选择给属性名指定一个局部的别名。

        不使用别名的语法形式为:

scope: {attributeName1: 'BINDING_STRATEGY',
        attributeName2: 'BINDING_STRATEGY',...
        }

        使用别名的形式为:

scope: {attributeAlias: 'BINDING_STRATEGY' + 'templateAttributeName',
       ...
       }

        绑定策略符号定义如下表所示:


        下面通过一个具体例子来示范一下各用用法。例如,我们想要创建一个expander指令,它会显示一个很小的扩展条,点击的时候扩展条就会展开,显示额外的内容。

DirectiveExpander.html

<html ng-app='expanderModule'>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <script src="lib/angular/angular.js"></script>
        <link rel="stylesheet" type="text/css" href="css/ExpanderSimple.css"/>
    </head>
    <body>
        <div ng-controller='SomeController'>
            <expander class='expander' expander-title='title'>
                {{text}}
            </expander>
        </div>
    </body>
    <script src="directive/ExpanderSimple.js"></script>
</html>

ExpanderSimple.js

var expanderModule=angular.module('expanderModule', [])
expanderModule.directive('expander', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        scope : {
            title : '=expanderTitle'
        },
        template : '<div>'
                 + '<div class="title" ng-click="toggle()">{{title}}</div>'
                 + '<div class="body" ng-show="showMe" ng-transclude></div>'
                 + '</div>',
        link : function(scope, element, attrs) {
            scope.showMe = false;
            scope.toggle = function toggle() {
                scope.showMe = !scope.showMe;
            }
        }
    }
});
expanderModule.controller('SomeController',function($scope) {
    $scope.title = '点击展开';
    $scope.text = '这里是内部的内容。';
});

ExpanderSimple.css

.expander {
    border: 1px solid black;
    width: 250px;
}

.expander>.title {
    background-color: black;
    color: white;
    padding: .1em .3em;
    cursor: pointer;
}

.expander>.body {
    padding: .1em .3em;
}

运行结果:


点击展开,效果如下:


        我们先来看一看指令中的每一个选项分别会为我们做一些什么事情,如下所示:


        如果觉得应该把把扩展条的标题定义在模板中,而不应该定义在数据模型中,那么你可以在scope的定义中使用字符串风格的属性,用@符号作为标志进行传递,示例如下:

scope: {title:'@expanderTitle'},

        在模板中,我们可以使用下面这种方法达到同样的效果:

<expander class='expander' expander-title='Click me to expand'>
    {{text}}
</expander>

        注意,在使用@策略时,我们仍然可以通过双花括号插值语法把title绑定到控制器scope上:

<expander class='expander' expander-title='{{title}}'>
   {{text}}
</expander>

 

六.扩展条实例Expander——操作DOM元素

        可以向指令的link和compile函数传递iElement或tElement参数,这两个参数都是包装后的引用,它们都指向原始的DOM元素。如果你加载了jQuery库,那么它们就会指向经过jQuery包装之后的元素。

        如果你不使用jQuery,那么这些DOM元素都位于Angular-native包装器jqLite中。jqLite是jQuery API的子集,在Angular中我们需要用它来创建所有东西。对于很多应用来说,只要使用这个轻量级API就可以实现所有你想做的事情了。

        如果你需要直接访问原始的DOM元素,你可以使用element[0]来访问对象中的第一个元素。

        在Angular文档中angular.element()部分,你可以看到目前能够支持的完整API列表,你可以使用angular.element()来创建包装在jqLite中的DOM元素。anuglar对象还带有addClass()、bind()、find()、toggleClass()等函数。当然,这些都是jQuery中常用的核心函数,只是Angular的实现代码更精致而已。

        除了jQuery的API之外,元素还带有一些基于Angular的函数,如下所示。这些函数是否存在与你是否使用完整的jQuery库有关。


        作为例子,重新实现一下扩展条的实例,新的实现方式不再借助于ng-show和ng-click指令,代码如下:

var expanderModule=angular.module('expanderModule', [])
expanderModule.directive('expander', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        scope : {
            title : '=expanderTitle'
        },
        template : '<div>'
                 + '<div class="title">{{title}}</div>'
                 + '<div class="body closed" ng-transclude></div>'
                 + '</div>',
        link : function(scope, element, attrs) {
            var titleElement = angular.element(element.children().eq(0));
            var bodyElement = angular.element(element.children().eq(1));
            
            titleElement.bind('click', toggle);
            
            function toggle() {
            	bodyElement.toggleClass('closed');
            }
        }
    }
});
expanderModule.controller('SomeController',function($scope) {
    $scope.title = '点击展开';
    $scope.text = '这里是内部的内容。';
});

        从模板中删掉了ng-click和ng-show指令,作为替代,我们给title元素创建了一个jqLite元素,然后把toggle()函数绑定到它的click事件上作为回调,这样就能在用户点击扩展条title的时候执行必要的动作。在toggle()函数中,我们通过调用扩展条body元素上的toggleClass()方法来添加或者删除closed样式类。在toggleClass()函数中我们使用一个CSS样式类把元素设置为display:none,示例如下:

.closed {
    display: none;
}

        PS:jQuery 属性操作-toggleClass() 方法

        toggleClass() 对设置或移除被选元素的一个或多个类进行切换。该方法检查每个元素中指定的类。如果不存在则添加类,如果已设置则删除之。这就是所谓的切换效果。

 

七.控制器及综合实例

        要实现需要彼此通信的嵌套指令,可以使用控制器。<menu>需要知道内部<menu-item>元素的信息,这样它才能够正确地显示和隐藏它们。同样地,<tab-set>需要知道内部<tab>元素的信息;<grid-view>需要知道内部<grid-element>元素的信息。

        如前所示,为了创建能够在指令之间进行通信的接口,你可以使用controller属性语法把控制器声明成指令的一部分:

controller: function controllerConstructor($scope, $element, $attrs, $transclude)

        controller函数是通过依赖注入的,所以这里所列出的参数列表都是可选的,可以按照其他顺序将其列出,当然这些参数都具有某种潜在的用途。它们还是可用的服务子集。

        通过require属性语法,其他指令可以把这个控制器传递给自已。require的完整形式如下:

require: '^?directiveName'

        require字符串的解释如下表所示:


        作为例子,我们来重写expander指令,让它可以用在一个"accordion"集合中。它会保证当你打开一个扩展条时,集合中的所有其他扩展条都会自动关闭掉。

Accordion.js

var expModule=angular.module('expanderModule',[])
expModule.directive('accordion', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        template : '<div ng-transclude></div>',
        controller : function() {
            var expanders = [];
            this.gotOpened = function(selectedExpander) {
                angular.forEach(expanders, function(expander) {
                    if (selectedExpander != expander) {
                        expander.showMe = false;
                    }
                });
            }
            this.addExpander = function(expander) {
                expanders.push(expander);
            }
        }
    }
});

expModule.directive('expander', function() {
    return {
        restrict : 'EA',
        replace : true,
        transclude : true,
        require : '^?accordion',
        scope : {
            title : '=expanderTitle'
        },
        template : '<div>'
                   + '<div class="title" ng-click="toggle()">{{title}}</div>'
                   + '<div class="body" ng-show="showMe" ng-transclude></div>'
                   + '</div>',
        link : function(scope, element, attrs, xccordionController) {
            scope.showMe = false;
            xccordionController.addExpander(scope);
            scope.toggle = function toggle() {
                scope.showMe = !scope.showMe;
                xccordionController.gotOpened(scope);
            }
        }
    }
});

expModule.controller("SomeController",function($scope) {
    $scope.expanders = [{
        title : 'Click me to expand',
        text : 'Hi there folks, I am the content that was hidden but is now shown.'
    }, {
        title : 'Click this',
        text : 'I am even better text than you have seen previously'
    }, {
        title : 'No,click me!',
        text : 'I am text that should be seen before seeing other texts'
    }];
});

        accordion指令,它会做一些元素定位工作。我们把控制器的构造函数以及进行元素定位操作的方法添加到accordion指令中。accordion指令中定义了一个addExpander()函数,扩展条可以调用它来注册自身;还创建了一个可被扩展条调用的gotOpened()函数,通过它,accordion的控制器就知道需要把其他所有处于打开状态的扩展条都关闭。

        在expander指令自身中,我们扩展它的时候要求accordion的控制器来自它的父元素,然后 在合适的时候调用addExpander()和gotOpened()函数。

        注意,accordion指令中的控制器创建了一个API接口,有了它,所有扩展条控件之间就可以进行通信了。

        编写模板来使用以上指令如下:

DirectiveAccordionExpander.html

<html ng-app="expanderModule">
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <script src="lib/angular/angular.js"></script>
        <link rel="stylesheet" type="text/css" href="css/Accordion.css"/>
    </head>
    <body ng-controller='SomeController' >
        <accordion>
            <expander class='expander' ng-repeat='expander in expanders' expander-title='expander.title'>
                {{expander.text}}
            </expander>
        </accordion>
    </body>
    <script src="directive/Accordion.js"></script>
</html>

Accordion.css

.expander {
    border: 1px solid black;
    width: 250px;
}

.expander>.title {
    background-color: black;
    color: white;
    padding: .1em .3em;
    cursor: pointer;
}

.expander>.body {
    padding: .1em .3em;
}

.closed {
	display: none;
}

运行效果:

  • 大小: 28.2 KB
  • 大小: 43.2 KB
  • 大小: 40.4 KB
  • 大小: 70.1 KB
  • 大小: 47.4 KB
  • 大小: 14.5 KB
  • 大小: 12.7 KB
  • 大小: 18.9 KB
  • 大小: 91.6 KB
  • 大小: 67.2 KB
  • 大小: 60.9 KB
  • 大小: 20.7 KB
分享到:
评论

相关推荐

    angularjs-book:《用angularjs开发下一代Web应用》源码笔记

    《用angularjs开发下一代Web应用》源码笔记 使用$rootScope.$broadcast来传递事件 reportBuilderCtrl里面广播事件: $scope.addChart = function() { console.log('reportBuilderCtrl:addChart'); $rootScope.$...

    JS AngularJS 学习笔记

    **AngularJS 框架详解** AngularJS 是一个强大的JavaScript框架,由Google维护,用于构建动态Web应用。它通过MVC(模型-视图-控制器...通过深入学习和实践,你可以有效地利用AngularJS构建高效、可维护的Web应用程序。

    AngularJS学习笔记

    **学习路径**:文档建议初学者不必过于纠结概念和原理,在初步掌握如何使用AngularJS的基础上逐步深入理解其背后的设计理念和技术细节。特别是到了文档后期的“自定义指令”章节,读者将更加全面地了解AngularJS的...

    AngularJS 学习笔记.pdf

    尽管AngularJS被称之为工具而非完整的框架,但其设计核心却提供了前端应用开发的一种全新组织与开发方式。数据双向绑定是AngularJS最突出的特性之一,它允许视图层直接和数据模型层关联,模型数据的任何改变都能实时...

    angularjs开发文档

    在提供的压缩包文件中,包含了多份AngularJS的学习资料,如"AngularJS学习笔记 - 进出自由,我的分享.mht"、"AngularJS开发指南"、"AngularJs学习笔记"和"AngularJS入门教程"。这些资源将帮助你深入理解AngularJS的...

    angularjs学习笔记

    **AngularJS学习笔记** AngularJS,作为一款强大的前端JavaScript框架,由Google维护,主要用于构建单页应用程序(SPA)。它的核心特性包括数据绑定、依赖注入、模块化和指令系统,极大地简化了网页应用的开发流程...

    Node.js MongoDB AngularJSWeb开发.part3

    3. AngularJS应用开发:可能涉及到模块化、服务、过滤器、指令的创建与使用,以及如何实现路由和状态管理。 4. 集成与部署:这部分可能讲述了如何将Node.js、MongoDB和AngularJS整合在一起,形成完整的Web应用,并...

    AngularJs学习笔记.docx

    ### AngularJS 学习笔记 #### 一、AngularJS 概述 AngularJS 是一个用于构建动态Web应用的开源框架,由 Google 维护。它通过扩展 HTML 的功能来简化 Web 开发,并允许开发者以声明式的方式编写代码,极大地提高了...

    angularjs学习笔记本

    AngularJS是一款由Google维护的JavaScript框架,用于构建前端Web应用。这个"angularjs学习笔记本"很可能是包含了关于AngularJS的学习资料、示例代码和教程。AngularJS的核心特性是它使用了MVC(Model-View-...

    angularJS_学习资料

    它将详细介绍数据绑定和指令的基本用法,以及如何使用AngularJS进行简单的路由配置。此外,教程还会讲解如何利用$http服务进行异步数据获取,以及如何进行单元测试。 ### 四、AngularJs学习笔记 学习笔记通常包含...

    移动互联网应用开发课堂笔记,web应用开发必备

    移动互联网应用开发不仅限于Web应用,还包括原生应用的开发,如使用Android Studio和iOS的Swift或Objective-C。笔记可能讨论了跨平台框架,如React Native或Flutter,它们允许开发者用一种语言编写代码,同时在多个...

    Flask之旅FlaskWeb开发基于Python的Web应用开发实战学习笔记.doc

    Flask之旅FlaskWeb开发基于Python的Web应用开发实战学习笔记

    angularJs个人学习笔记及源码

    这个压缩包包含了作者个人的学习笔记和AngularJS的源码分析,这对于深入理解AngularJS的工作原理及其应用场景非常有帮助。让我们逐一探讨这些知识点。 1. **AngularJS基础**: AngularJS的核心特性包括数据绑定、...

    AngularJS笔记

    AngularJS是一个开源的JavaScript框架,由Google维护,主要用于构建动态的Web应用程序。它的设计理念是将MVC(Model-View-Controller)模式应用于单页应用程序(SPA),使得前端开发更加高效、结构化。 **初始化...

    java-web开发学习笔记

    本人在学习web开发中的个人学习笔记,其中包括Ext ssh javascript css 特效收藏 学习笔记

    Java Web ppt 开发 J2SE 学习笔记

    Java Web ppt 开发和J2SE学习笔记涵盖了Java编程的基础以及如何将其应用于Web应用程序的开发。J2SE,即Java 2 Platform, Standard Edition,是Java平台的核心部分,提供了用于开发和运行桌面应用、服务器端应用和...

    AngularJs学习笔记1

    【AngularJS学习笔记1】 AngularJS 是一个强大的前端JavaScript框架,用于构建动态Web应用程序。它通过数据绑定和依赖注入简化了HTML页面与JavaScript代码之间的交互。这篇笔记将深入探讨AngularJS的一些基本概念,...

Global site tag (gtag.js) - Google Analytics