angularjs入门学习【指令篇】
一、首先我们来了解下指令API
属性 | 含义 |
restrict | 申明标识符在模版中作为元素,属性,类,注释或组合,如何使用 |
priority | 设置模版中相对于其他标识符的执行顺序 |
Template | 指定一个字符串式的内嵌模版,如果你指定了模版是一个URL,那么是不会使用的 |
tempateUrl | 指定URL加载的模版,如果你已经指定了内嵌的模版字符串,那么它不会使用的 |
Replace | 如果为真,替换当前元素,如果是假或未指定,拼接到当前元素 |
Transclude | 移动一个标识符的原始字节带你到一个新模版的位置 |
Scope | 为这个标识符创建一个新的作用域,而不是继承父作用域 |
Controller | 创建一个控制器通过标识符公开通信API |
Require | 当前标识符需要另外一个标识符提供正确的函数功能 |
Link | 通过代码修改目标DOM元素的实例,添加事件监听,建立数据绑定 |
Compile | 通过标识符拷贝编程修改DOM模版 |
在这里,我们简单的了解下每个属性的含义及其简单的作用,在后面我们将会通过一些励志来解释
接下来,我们来了解下指令定义对象
restrict:restrict属性允许为标识符指定声明样式,也就是说它可以作为元素名,属性,类或注释。我们可以使用一个字符串来代表下表中的每个标志,从而指定
一个或者多个声明样式
标志 | 样式 | 示例 |
E | Element | <my-menu title=‘products‘></my-menu> |
A | Attribute | <div my-menu=‘products‘></div> |
C | Class | <div class=‘my-menu:products‘></div> |
M | Comment | <!--directive:my-menu products--> |
如果你希望标识符作为元素后者属性,你可以传递EA作为restrict的字符串
如果省略了restrict属性,默认就是A,详细请看angular文档
Priorities:为应用程序指定顺序,数值越大就越先运行,默认是为零,一般情况下无需设置优先级
Templates:在创建组件时,angular允许你在模版中替换和包装元素中的内容,如果你想创建如下的标签视图
不是使用一串<div><ul><li><a>等元素来实现,而是可以通过自定义创建标识符<tab-set><tab>,分别声明每个页签的结构,可能如果下
<tab-set> <tab title='Home'> <p>Welcome home!</p> </tab> <tab title='Preferences'> <!-- preferences UI goes here --> </tab> </tabset>
同时,你也可通过控制器为title和页签内容进行数据绑定,并用这种方式做出菜单,手风琴,弹窗,对话框或其他应用需求
接下来,让我们来看看temp拉特或者templateUrl属性,指定替换DOM元素。在上表中,我们看到template可以用来设置模版内容的字符串,
templateUrl用于指定将被加载的服务器文件,正如接下来看到的示例,我们可以预缓存这些模版,一遍减少get请求数,提高性能
<html lang='en' ng-app='app'> ... <body> <hello></hello> </body> ...
创建<hello></hello>标签替换<div>hi there</div>,replace设置为true允许拼接内容到元素上,设置replace成为true
var appModule = angular.module('app', []); appModule.directive('hello', function() { return { restrict: 'E', template: '<div>Hi there</div>', replace: true }; });
在加载到浏览器后,我们会看到hi there,通过查看页面源码,我们还是会看到<hello></hello>但是如果你检查生成代码(chrome,右击hi htere,选择检查元素)你会看到
<body> <div>Hi there</div> </body><hello></hello>已经被模版中的<div>替换。
而相对于使用template 输入html到字符串中不是很有意义,一般我们都会使用templateUrl,进行设置适当的头部缓存
var appModule = angular.module('app', []); appModule.directive('hello', function() { return { restrict: 'E', templateUrl: 'helloTemplate.html', replace: true }; });
在helloTemplate.html中,我们需要写入
<div> Hi there</div>
如果你使用chrome浏览器,同源策略会组织可能会导致遇到一个错误“Origin null is not allowed by Access-Controll-Allow-Origin”.这里你有两种可选方式
1.通过服务器加载应用
2.chrome 中设置一个标志,通过命令行‘chrome-allow-file-access-from-files’解决
然后通过templateUrl加载文件,会使用户等待知道加载后看到标识符,如果你希望第一次页面加载时就加载模版,你可以在script标签中让其作为页面的一部分,如下
<script type='text/ng-template' id='helloTemplateInline.html'> <div>Hi there</div> </script>
这里的id属性非常重要,因为是URL键,angular用它来存储模版,你应该在标识符的templateUrl中使用id来指定插入那个模版
还有我们可以通过$http或其他几种机制加载模版,然后直接设置到angular所使用的$templateCache对象中,并可通过run函数调用它,使其在标识符运行之前让这个模版在
缓存中可用
var appModule = angular.module('app', []); appModule.run(function($templateCache) { $templateCache.put('helloTemplateCached.html', '<div>Hi there</div>'); }); appModule.directive('hello', function() { return { restrict: 'E', templateUrl: 'helloTemplateCached.html', replace: true }; });
Transclusion(嵌入包含)
通过transclude属性移动原始的内容到新模版中,当设置成为true时,标识符会删除原来的内容,并通过ng-transclude标识符使它重新插入到模版中
采用transclusion方式修改示例:
appModule.directive('hello', function() { return { template: '<div>Hi there <span ng-transclude></span></div>', transclude: true }; });
应用在:
<div hello>Bob</div>
我们会看到‘Hi there Bob.’
编译和链接函数
虽然插入模版是有用,但是在任何标识符真正有意义的工作发生在编译活着链接功能里
编译和链接的功能就是angular为引用创建实时视图的后两阶段,让我们来看下angular初始化过程的高层次视图,按照次序
【1】脚本加载:加载angular,查找ng-app标识符找到应用绑定
【2】编译阶段:在这一阶段,angular便利DOM标志模版中所有注册的标志,对于每个标识符,基于标识符规则(template,replace,transclude等等)改造DOM,然后
如果编译函数存在就调用它,结果一个编译的template函数,它会调用所有的标识符搜集的link韩素
【3】链接阶段:为了让视图动起来,angular为每个标识符运行link函数,link函数通常在DOM或模型上创建监听器,这些监听器让视图和模型始终保持一致
因此到了编译阶段,它处理转换了模版,链接阶段,它处理了修改视图中的数据,沿着这些思路,标识符中表一功能和链接功能主要区别就是链接功能转换了模版自身,而连接功能在模型和视图上创建了动态链接,就是在第二阶段,作用域scpoes被附加到了编译过程的link功能上,通过数据绑定,标识符变活了
二、作用域
获取作用域scope的三种选择
1.标识符DOM元素中已经存在的作用域
2,创建一个继承封闭的控制器作用域的新作用域,以便读取结构树作用域的所有值。
3.独立作用于,从父类中不继承任何属性,当你需要隔离这个标识符的操作和父类作用域时,创建可从用的组建来使用这个选项
我们可以用如下语法类创建这些作用域配置
已有作用域 | scope:false(如果没有指定,这就是默认值) |
新作用域 | scope:true |
独立作用域 | scope:{属性名次和绑定风格} |
当你创建一个独立作用域时,默认情况下不能访问父类作用域的模型,但我们可以通过指定需要的属性传递到标识符
注意:虽然独立作用域并没有继承模型属性,但他们仍然是父作用域的子节点,并$parent指向父类
我们可以通过标识符属性的键值对父类传递指定的属性给独立作用域,这里有三种可行的方式从父作用域传递数据,我们称这些传递数据方式叫做“绑定策略”,你可以为这个属性名称指定一个本地别名
没有别名的语法如下:
scope: { attributeName1: 'BINDING_STRATEGY', attributeName2: 'BINDING_STRATEGY', … }
用别名的格式如下:
scope: { attributeAlias: 'BINDING_STRATEGY' + 'templateAttributeName', … }
符号 | 意义 |
@ | 传递字符串属性,你可以通过使用改写{{}}属性值从粉笔作用域中进行数据绑定 |
= | 数据绑定属性在标识符父作用域的属性中 |
& | 传递一个来自父作用域的函数,稍后调用 |
在这我们用一个具体例子上的变化来说明它们,比如我们想创建一个expander标识符,展示一个标题栏,当点击时扩展显示额外内容
关闭的时候
打开的状态
代码如下:
<div ng-controller='SomeController'> <expander class='expander' expander-title='title'> {{text}} </expander> </div>
控制器代码:
function SomeController($scope) { $scope.title = 'Click me to expand'; $scope.text = 'Hi there folks, I am the content + 'that was hidden but is now shown.'; }然后我们编写标识符
angular.module('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; } } } });
样式:
.expander { border: 1px solid black; width: 250px; } .expander > .title { background-color: black; color: white; padding: .1em .3em; cursor: pointer; } .expander > .body { padding: .1em .3em; }
元素的功能
功能名 | |
Restrict:EA | 描述调用标识符为元素或属性,也就是<expander>..</expander>或 <div expander>...</div> |
Replace:true | 用提供的模版替换原来的元素 |
Transclude:true | 移动原始元素内容到提供的模版中的另外一个地方 |
Scope:{title:=expanderTitle}} | 创建一个叫title的本地作用域属性,它是用来数据绑定到expander-title 属性中声明的parent-scope属性,这里,为了方便expanderTitle冲命名 为title,由于expanderTitle在模版中,我们飙血作用域 scope:{expanderTitle:‘=‘}来引用它,但在这个场景中,其他标识符也 有一个title属性,为了防止引起歧义,我们将其重命名 |
Template:<div>+ | 为标识符提供即将插入的模版,注意,我们使用ng-click和ng-show 来展示或隐藏自身,ng-transclude来申明原始的内容将何去何从, 同时注意嵌入的内容可以访问父作用域,而不是封闭标识符的 作用域 |
Link: | 建立showMe模型,跟踪expander的打开和关闭状态,然后当用户点击title div时,调用定义的toggle函数 |
三、操作DOM元素
函数 | 描述 |
Controller(name) | 与控制器直接进行通信,这个函数返回绑定在元素上的控制器 如果这个元素不存在,它会遍历DOM,然后查找最近的父控制 器代替,如果参数名字是可选的,用于指定同意元素上其他 标识符名称,如果提供了,则返回标识符上的控制器,这个名字 应该使用驼峰式,也就是说用ngModel代替ng-model |
Injector() | 获取当前元素或者父元素的注入器,允许在这些模块中查找依赖 |
Scope() | 返回当前元素或者最近父元素的作用域 |
inHeritedData() | 和jquery的data()函数一样,inheritedData()以封闭的方式设置以及获取元素上的 数据,除了从当前元素获取数据,它会便利DOM查找 |
下面例子,我们不用ng-show和ng-click重新实现之前expander示例
angular.module('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'); } } } });
在上面我们从模版中移除了ng-click和ng-show标识符,然而,当用户点击expander标题时候,仍然执行预期的操作,我们从tittle元素上创建了一个jqLite元素,然后把toggle函数绑定到click事件上作为它的回调,在toggle函数,我们在expander body元素上调用toggleClass()
来添加或移除closed的类,我们会设置这个元素class设置成的displat:none
.closed { display: none; }