Angularjs学习笔记(五)指令

一、指令基础
当浏览器加载一个包含AngularJS应用的HTML时,我们只需要用内置指令ng-app启动AngularJS应用,并标记出应用的根节点,这个指令需要以属性的形式来使用,因此可以将它写到任何位置,但是写到的开始标签上是最常规的做法:
1.指令的使用方式

<html ng-app="myApp">

html>

任何在这个根元素内部的指令只要能够访问作用域,就可以访问$rootScope。
2.自定义指令
通过AngularJS模块API中的.directive()方法,我们可以通过传入一个字符串和一个函数来注册一个新指令。其中字符串是这个指令的名字,指令名应该是驼峰命名风格的,函数应该返回
一个对象。directive()方法返回的对象中包含了用来定义和配置指令所需的方法和属性。

angular.module('myApp',[])
.directive('myDirective', function() {
return {
restrict: 'E',
template: '
Click me to go to Google' //为了尽快掌握简单的属性定义,我们只用了restrict和template两个设置项来定义指令
};
});

为了让AngularJS能够调用我们的指令,需要修改指令定义中的restrict设置。这个设置告、诉AngularJS在编译HTML时用哪种声明格式来匹配指令定义。我们可以指定一个或多个格式。可以以元素(E) 、属性(A) 、类(C)或注释(M)的格式来调用指令,建议用属性A(在已有的标签上添加功能)和元素(使用模板的时候)的方式来调用。
这里写图片描述

AngularJS在页面加载以及调用指令定义后生成的代码, AngularJS把生成后的代码提供给Chrome进行渲染。默认情况下, AngularJS将模板生成的HTML代码嵌套在自定义标签内部
Angularjs学习笔记(五)指令_第1张图片
向指令定义中添加一些新的设置:我们可以将自定义标签从生成的DOM中完全移除掉,
并只留下由模版生成的链接。将replace设置为true就可以实现这个效果:

angular.module('myApp', [])
.directive('myDirective', function() {
return {
restrict: 'E',
replace: true,
template: 'Click me to go to Google'
};
});

Angularjs学习笔记(五)指令_第2张图片
当AngularJS启动应用时,它会把第一个参数当作一个字符串,并以此字符串为名来注册第二个参数返回的对象。 AngularJS编译器会解析主HTML的DOM中的元素、属性、注释和CSS类名中使用了这个名字的地方,并在这些地方引用对应的指令。当它找到某个已知的指令时,就会在页面中插入指令所对应的DOM元素。

template参数是可选的,必须被设置为以下两种形式之一:
 一段HTML文本;
 一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个代表模板的字符串。
如果模板字符串中含有多个DOM元素,或者只由一个单独的文本节点构成,那它必须被包含在一个父元素内。换句话说,必须存在一个根DOM元素:

template: '\
<div> <-- single root element -->\
<a href="http://google.com">Click mea>\
<h1>When using two elements, wrap them in a parent elementh1>\
div>\

另外,注意每一行末尾的反斜线,这样AngularJS才能正确解析多行字符串。

templateUrl是可选的参数,可以是以下类型:
 一个代表外部HTML文件路径的字符串;
 一个可以接受两个参数的函数,参数为tElement和tAttrs,并返回一个外部HTML文件路径的字符串。
默认情况下,调用指令时会在后台通过Ajax来请求HTML模板文件。有两件事情需要知道。
 在本地开发时,需要在后台运行一个本地服务器,用以从文件系统加载HTML模板,否则会导致Cross Origin Request Script(CORS)错误。
 模板加载是异步的,意味着编译和链接要暂停,等待模板加载完成。

我们自定义指令是为了复用,肯定会用在不同的controller里面。为了在不同的控制器里面使用自定义指令,需要为指令增加配置项attr,指令怎么根据配置项调用不同控制器的方法?见下第5条:link
3.向指令中传递数据
注意,我们在模板中硬编码了URL和链接文本:

template:'"http://google.com"> Click me to go to Google'

我们的目标是关注指令的公共接口,就像其他任何编程语言一样。实际上,应该将上面的模板转换成可以接受两个变量的形式:一个变量是URL,另一个是链接文本:
template: '{{ myLinkText }}'
在主HTML文档中,可以给指令添加myUrl和myLinkText两个属性,这两个参数会成为指令内部作用域的属性:

<div my-directive my-url="http://google.com"
my-link-text="Click me to go to Google">
div>

有好几种途径可以设置指令内部作用域中属性的值。最简单的方法就是使用由所属控制器提供的已经存在的作用域。如果控制器被移除,或者在控制器的作用域中也定义了一个叫myUrl的属性,AngularJS 允许通过创建新的子作用域或者隔离作用域来解决这个常见问题。

<div my-directive my-url="http://google.com"
my-link-text="Click me to go to Google">div>
angular.module('myApp', [])
.directive('myDirective', function() {
    return {
        restrict: 'A',
        replace: true,

        scope: {
            myUrl: '@', //绑定策略
            myLinkText: '@' //绑定策略
        },

        template: '' +'{{myLinkText}}'
    };
});

4.缓存
在模板第一次被引用时,它会被载入到模板缓存中以便快速的检索。angular直接通过 $templateCache 服务
Angularjs学习笔记(五)指令_第3张图片
5.link 方法:
回顾:
directive中的几个属性:
restrict
E: 表示该directive仅能以element方式使用,即:
A: 表示该directive仅能以attribute方式使用,即:


EA: 表示该directive既能以element方式使用,也能以attribute方式使用
transclude
directive可能接受页面上的其他html内容时才会用到,建议先去掉该参数。有些高阶了。
scope
写上该属性时,就表示这个directive不会从它的controller里继承$scope对象,而是会重新创建一个。
templateUrl
你的directive里的html内容
link
可以简单理解为,当directive被angular 编译后,执行该方法,是用来处理指令内部事务的,包括:给元素绑定事件/数据之类的。

compile在编译前执行,负责把template(包括transclude所引用的)变成一个完整的DOM结构。 link在编译后执行,
负责根据controller和scope里的东东,给compile得到的DOM注册事件、关联数据,或者repeat之。

link里面有三个参数:
第一个参数scope基本上就是上面写的那个scope。
element简单说就是$(‘my-dialog’)
attrs是个map,内容是你这个directive上的所有属性,
require 之后,link就会多一个参数(第四个参数,scope,element,attrs,superCon
Angularjs学习笔记(五)指令_第4张图片
当有两种加载方式时,可以给自定义指令增加属性:
Angularjs学习笔记(五)指令_第5张图片
指令内部怎么获取到哪种加载方式呢?attr!直接写属性小写名,不要加括号
Angularjs学习笔记(五)指令_第6张图片

那什么时候把逻辑写在controller里,什么时候写在link里呢?
如果想让方法暴露出结果,在外部被调用,则写在controller里
link是处理指令内部的事物的,比如给元素绑定事件、数据
6.指令间的交互
假设现在我们要编写两个指令,两个指令中的link链接函数中(link函数后面会讲)存在有很多重合的方法,
这时候我们就可以将这些重复的方法写在第三个指令的controller中(上面也讲到controller经常用来提供指令间的复用行为)
然后在这两个指令中,require这个拥有controller字段的的指令(第三个指令通过require以及link第四个参数——这里是supermanCtrl):

var myModule = angular.module("MyModule", []);
myModule.directive("superman", function() {
    return {
        scope: {},  //创建独立作用域
        restrict: 'AE',
        controller: function($scope) {
            $scope.abilities = [];
            this.addStrength = function() {
                $scope.abilities.push("strength");
            };
            this.addSpeed = function() {
                $scope.abilities.push("speed");
            };
            this.addLight = function() {
                $scope.abilities.push("light");
            };
        },
        link: function(scope, element, attrs) {
            element.addClass('btn btn-primary');
            element.bind("mouseenter", function() {
                console.log(scope.abilities);
            });
        }
    }
});
myModule.directive("strength", function() {
    return {
        require: '^superman',//把supermanCtrl自动注入到指令中,就可以调用superman控制器中的方法了
        link: function(scope, element, attrs, supermanCtrl) {   
            supermanCtrl.addStrength();
        }
    }
});
myModule.directive("speed", function() {
    return {
        require: '^superman',
        link: function(scope, element, attrs, supermanCtrl) {
            supermanCtrl.addSpeed();
        }
    }
});
myModule.directive("light", function() {
    return {
        require: '^superman',
        link: function(scope, element, attrs, supermanCtrl) {
            supermanCtrl.addLight();
        }
    }
});

页面代码:

<div class="row">
        <div class="col-md-3">
            动感超人---力量
        div>
    div>
    <div class="row">
        <div class="col-md-3">
            动感超人2---力量+敏捷
        div>
    div>
    <div class="row">
        <div class="col-md-3">
            动感超人3---力量+敏捷+发光
        div>
    div>

7、创建独立scope
为了使指令相互独立使用,我们应该在return里面创建独立scope:

myModule.directive("superman", function() {
    return {
        scope: {},  //创建独立作用域
        restrict: 'AE',
        controller: function($scope) {
        }
    }
});

angularjs scope的绑定策略:见另一篇博文

二、内置指令
Angularjs学习笔记(五)指令_第7张图片
这里只列出几个常用的:
(1)ng-app 定义应用程序的根元素。
声明了ng-app的元素会成为$rootScope的起点,而$rootScope是作用域链的根,通常声明在
(2)ng-model:将表单控件和当前作用域的属性进行绑定,需要注意绑定的scope的范围(父scope与子scope)

<div ng-app="myApp" ng-controller="myCtrl">
    <input ng-model="name"> //input加上ng-model指令后,框架会负责input和name变量的自动同步。
div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.name = "John Doe";
});
script>

我们应该始终用ngModel来绑定$scope上一个数据模型内的属性,而不是$scope上的属性,这可以避免在作用域或后代作用域中发生属性覆盖。例如:

type="text"
ng-model="modelName.someProperty" />

(3)ng-init:该指令被调用时会初始化内部作用域。
这个指令一般会出现在比较小的应用中,比如demo什么的

<div ng-init="job='fighter'">
    I'm a/an {{job}}
div>

(4)ng-controller :用来定义一个控制器对象
内置指令ng-controller的作用是为嵌套在其中的指令创建一个子作用域,避免将所有操作和模型都定义在$rootScope上。用这个指令可以在一个DOM元素上放置控制器。

<div ng-app="myApp" ng-controller="myCtrl">
Full Name: {{firstName + " " + lastName}}
div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});
script>

(5)ng-form :用来定义一个from,通常是用来验证参数
场景稍微复杂一点,比如一个父表单中有多个子表单,子表单中有3个验证通过时父表单便可以提交。但是,

是不可以嵌套的。考虑到这种场景,我们便使用ng-form指令来解决这一问题。
详细说明ng-form:见后

<form name="mainForm" novalidate>
    <div ng-form="form1">
        姓名:<input type="text" ng-required="true" ng-model="name"/><br>
        证件号码:<input type="number" ng-minLength="15" ng-maxLength="18" ng-required="true" ng-model="idnum"/>
    </div>
    <br>
    <div ng-form="form2">
        监护人姓名:<input type="text" ng-required="true" ng-model="gname"/><br>
        监护人证件号码:<input type="number" ng-minLength="15" ng-maxLength="18" ng-required="true" ng-model="gidnum"/>
    </div>
    <button ng-disabled="form1.$invalid && form2.$invalid">submit all</button>
</form>

ng-valid (有效的).
ng-invalid (无效的).

type="submit" ng-disabled="mainForm.$invalid" /> //也就是表单的状态为$invalid时禁用提交按钮。

ng-pristine (原始,简介).
ng-dirty (脏的).
ng-submitted (提交的)
下面四个是用到HTML的布尔属性:
(6)ng-disabled :禁用表单输入字段。——布尔
使用ng-disabled可以把disabled属性绑定到以下表单输入字段上:
(text、 checkbox、 radio、 number、 url, email、 submit);