学习要点:
- 定义复杂的指令
- 指令模板
- 使用函数作为模板
- 使用外部模板
- 动态引入外部模板
- 替换元素
- 管理指令的作用域
- 创建多个控制器
- 给每个指令实例创建自己的作用域
- 创建隔离的作用域
引子:
前面我们简单地介绍了NG 指令的简单用法,但是这有时并不能满足我们的要求,其实指令用起来还是很复杂的,接下来让我们看看这风谲云诡的指令如何兴风作浪!
一、定义复杂的指令
定义一个复杂的指令,我们需要在工厂函数中返回一个对象,这个对象需要我们使用一些指令,下面基本涵盖了,我们在使用的时候介绍他们的作用和用法:
接下来,我们利用link和restrict属性来讲解一个返回链式函数的指令
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="defaultCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div class="panel-body">
<!-- 指令当作元素 -->
<!-- <unorderlist list-source="products" list-property="price | currency"/> -->
<!-- 指令当作属性 -->
<div unorderlist="products" list-source="products" list-property="price | currency"></div>
</div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript"> angular.module("exampleApp",[]) .directive("unorderlist",function () { return { // link : 为指令指定连接函数 link: function (scope,element,attrs) { var data = scope[attrs["unorderlist"] || attrs["listSource"]]; var propertyExpression = attrs["listProperty"] || "price | currency"; if (angular.isArray(data)) { var listElem = angular.element("<ul>"); if (element[0].nodeName == "#comment") { element.parent().append(listElem); } else { element.append(listElem); } for (var i = 0; i < data.length; i++) { listElem.append(angular.element("<li>").text(scope.$eval(propertyExpression,data[i]))); } } },// restrict : 指定指令如何使用 ECMA 元素、类、注释和属性,类和注释一般不用 restrict : "ECMA" } }) .controller("defaultCtrl",function ($scope) { $scope.products = [ { name: "Apples",category: "Fruit",price: 1.20,expiry: 10 },{ name: "Bananas",price: 2.42,expiry: 7 },{ name: "Pears",price: 2.02,expiry: 6 } ]; }) </script>
</body>
</html>
定义的unorderlist指令返回一个水果列表,里面的逻辑我们在上一个已经详细讲解了【 AngularJS 优雅的自定义指令】 ,这里不再累赘
二、使用指令模板
1.使用字符串作为模板
我们需要修改定义指令部分,即Module.directive部分,其他不变。
angular.module("exampleApp",[])
.directive("unorderlist",function () {
return {
// link : 为指令指定连接函数
link: function (scope,attrs) {
// attrs['unorderlist'] 获取unorderlist属性值,这里为products
// 获取数据模型值,这里为scope.products,并且将其赋值给scope.data
scope.data = scope[attrs["unorderlist"] || attrs["listSource"]];
},// template : 指定一个将插入HTML的模板的指令
template : "<ul><li ng-repeat='item in data'>{{item.price | currency}}</li></ul>",类和注释一般不用
restrict : "EA"
}
})
2.使用函数作为模板
<!DOCTYPE> <!-- use module --> <html ng-app="exampleApp"> <head> <title>Angluar test</title> <Meta charset="utf-8"/> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> </head> <body> <dlv class="panel panel-default" ng-controller="defaultCtrl"> <div class="panel-heading"> <h3>Products</h3> </div> <div class="panel-body"> <!-- 指令当作元素 --> <!-- <unorderlist list-source="products" list-property="price | currency"/> --> <!-- 指令当作属性 --> <div unorderlist="products" list-source="products" list-property="price | currency"></div> </div> </dlv> <script type="text/javascript" src="js/angular.min.js"></script> <!-- 定义模板 --> <script type="text/template" id="tplList"> <ul> <li ng-repeat="item in data"> {{item.price | currency}} </li> </ul> </script> <script type="text/javascript"> angular.module("exampleApp",attrs) { // attrs['unorderlist'] 获取unorderlist属性值,这里为products // 获取数据模型值,这里为scope.products,并且将其赋值给scope.data scope.data = scope[attrs["unorderlist"] || attrs["listSource"]]; },// template : 指定一个将插入HTML的模板的指令 // 使用函数 template : function () { return angular.element(document.querySelector("#tplList")).html(); },类和注释一般不用 restrict : "EA" } }) .controller("defaultCtrl",expiry: 6 } ]; }) </script> </body> </html>
上述代码使用到了jqLite,我们应该尽量避免使用DOMAPI
3.使用外部模板
第一步:
我们先定义一个外部模板文件,这里我们叫做tableTpl.html
<table> <thead><th>Name</th><th>Price</th></thead> <tbody> <tr ng-repeat="item in data"> <td>{{item.name}}</td> <td>{{item.price | currency}}</td> </tr> </tbody> </table>
接下来,我们再定义一个外部模板文件,这里我们叫做itemTpl.html
<p>This is the list from the template file</p> <ul> <li ng-repeat="item in data">{{item.price | currency}}</li> </ul>
第二步:我们来动态引用
4.替换元素
默认情况下,我们在模板中添加样式类,但是显示效果并不是我们所想象的那样好,我们需要使用替换技术。所谓替换,就是用模板文件代替使用指令的元素,并且将使用指令的元素的属性都赋值给模板元素。
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="defaultCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div class="panel-body">
<!-- 指令当作元素 -->
<!-- <unorderlist list-source="products" list-property="price | currency"/> -->
<!-- 指令当作属性 -->
<div unorderlist="products" template="table" class="table table-striped"></div>
</div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript"> angular.module("exampleApp",attrs) { return attrs["template"] == "table" ? "tableTpl.html" : "itemTpl.html"; },类和注释一般不用 restrict : "EA",// 指定模板元素是否替换指令所应用到的元素 replace : true,} }) .controller("defaultCtrl",expiry: 6 } ]; }) </script>
</body>
</html>
解析:
第一步:在应用指令的元素中加上修饰类
第二步:在JS中加上replace属性
三、管理指令的作用域
先看看一个例子:
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="scopeCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div scope-dome></div>
<div scope-dome></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript"> angular.module("exampleApp",[]) .directive("scopeDome",function () { return { // templateUrl : 指定一个将插入HTML的外部模板的指令 template : "<div class='panel-body'>Name: <input ng-model=name /></div>",} }) .controller("scopeCtrl",function ($scope) { }) </script>
</body>
</html>
例子中同时使用了两个一样的模板,由于两个元素的作用域一种,使用的元素一致,双向绑定的数据name一致,导致当你输入字符时,两个输入框元素同步
下面是原理图(一个指令的不同实例在同一个控制器作用域的操作):
1.建立多个控制器
对于上述的问题,我们想要消除同步的问题,需要建立多个控制器,这里彼此的数据绑定name就独立了,因为每个元素都是不同的控制器,即作用域不同,所以不会同步了
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div ng-controller="firstCtrl" scope-dome></div>
<div ng-controller="secondCtrl" scope-dome></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript"> angular.module("exampleApp",} }) .controller("firstCtrl",function ($scope) { }) .controller("secondCtrl",function ($scope) { }) </script>
</body>
</html>
解析:
第一步:同时创建了两个控制器,即firstCtrl和secondCtrl
第二步:在调用指令时,应用于不同的控制器
下面是原理图(为指令的每个实例创建一个控制器):
2.给每个指令实例创建自己的作用域
其实,我们可以避免创建那么多控制器,可以使用scope属性来为每个指令实例创建自己的作用域
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div ng-controller="firstCtrl" scope-dome></div>
<div ng-controller="secondCtrl" scope-dome></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/javascript"> angular.module("exampleApp",// scope : 为指令创建一个新的作用域或者是隔离的作用域 scope : true,} }) </script>
</body>
</html>
3.创建隔离作用域
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="scopeCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div scope-dome></div>
<div scope-dome></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <p>Name: <input ng-model="data.name" /></p> <p>City: <input ng-model="city" /></p> <p>Country: <input ng-model="country" /></p> </div> </script>
<script type="text/javascript"> angular.module("exampleApp",function () { return { // templateUrl : 指定一个将插入HTML的外部模板的指令 template : function () { return angular.element(document.querySelector("#scopeTemplate")).html(); },function ($scope) { // 共享 $scope.data = { name : "Tom"}; // 独立 $scope.city = "BeiJing"; }) </script>
</body>
</html>
这个例子中data.name共享,city独立
使用scope的优点是可以省去许多控制器,缺点是它还是要受到控制的影响的,例如本例子中的scopeCtrl控制器,那么我们怎么避免呢?
就是用隔离控制器,直接隔离了指令模板与控制器的关系
下面是原理图(隔离作用域的效果):
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="scopeCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<div scope-dome></div>
<div scope-dome></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <p>Name: <input ng-model="data.name" /></p> <p>City: <input ng-model="city" /></p> <p>Country: <input ng-model="country" /></p> </div> </script>
<script type="text/javascript"> angular.module("exampleApp",// scope : 为指令创建一个新的作用域或者是隔离的作用域 scope : {},function ($scope) { $scope.data = { name : "Tom"}; $scope.city = "BeiJing"; }) </script>
</body>
</html>
这样,隔离控制器的指令元素不再受影响了
1.单向数据绑定
<!DOCTYPE> <!-- use module --> <html ng-app="exampleApp"> <head> <title>Angluar test</title> <Meta charset="utf-8"/> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> </head> <body> <dlv class="panel panel-default" ng-controller="scopeCtrl"> <div class="panel-heading"> <h3>Products</h3> </div> <!-- 本地作用域数据与scopeCtrl作用域数据绑定 --> <div scope-dome nameprop="{{data.name}}"></div> <div scope-dome nameprop="{{data.name}}"></div> </dlv> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <!-- 使用本地作用域数据 --> <p>Name: <input value="{{local}}" /></p> </div> </script> <script type="text/javascript"> angular.module("exampleApp",// scope : 为指令创建一个新的作用域或者是隔离的作用域 scope : { // 隔离的本地作用域 local : "@nameprop" },function ($scope) { $scope.data = { name : "Tom"}; $scope.city = "BeiJing"; }) </script> </body> </html>
在一开始两个输入框都获取了scopeCtrl作用域的data.name的值,但是两个输入框都是隔离作用域的,所有他们修改值时,不会影响其他的值
2.双向数据绑定
<!DOCTYPE>
<!-- use module -->
<html ng-app="exampleApp">
<head>
<title>Angluar test</title>
<Meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
</head>
<body>
<dlv class="panel panel-default" ng-controller="scopeCtrl">
<div class="panel-heading">
<h3>Products</h3>
</div>
<!-- 本地作用域数据与scopeCtrl作用域数据绑定 -->
<div scope-dome nameprop="data.name"></div>
<div scope-dome nameprop="data.name"></div>
</dlv>
<script type="text/javascript" src="js/angular.min.js"></script>
<script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <!-- 使用本地作用域数据 --> <p>Name: <input ng-model="local" /></p> </div> </script>
<script type="text/javascript"> angular.module("exampleApp",// scope : 为指令创建一个新的作用域或者是隔离的作用域 scope : { // 隔离的本地作用域 local : "=nameprop" },function ($scope) { $scope.data = { name : "Tom"}; $scope.city = "BeiJing"; }) </script>
</body>
</html>
一共做出了三处修改
第一:将指令中local 的@nameprop 改为 =nameprop
第二:将模板中的{{local}} 改为 ng-model=”local”
第三:将视图中的 nameprop = {{data.name}} 改为 nameprop = “data.name”
这样数据又同步了,牵一发而动全身
3.使用函数表达式
<!DOCTYPE> <!-- use module --> <html ng-app="exampleApp"> <head> <title>Angluar test</title> <Meta charset="utf-8"/> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> </head> <body> <dlv class="panel panel-default" ng-controller="scopeCtrl"> <div class="panel-heading"> <h3>Products</h3> </div> <!-- 本地作用域数据与scopeCtrl作用域数据绑定 --> <div scope-dome nameprop="data.name" city="getCity(data.name)"></div> </dlv> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <!-- 使用本地作用域数据 --> <P>Name: {{local}},City : {{cityFn()}}</P> </div> </script> <script type="text/javascript"> angular.module("exampleApp",// scope : 为指令创建一个新的作用域或者是隔离的作用域 scope : { // 隔离的本地作用域 local : "=nameprop",cityFn : "&city" },function ($scope) { $scope.data = { name : "Tom"}; $scope.city = "BeiJing"; $scope.getCity = function (name) { console.log(name); return name == "Tom" ? $scope.city : "Unknow"; } }) </script> </body> </html>
4.直接用隔离作用域的数据来计算一个表达式
<!DOCTYPE> <!-- use module --> <html ng-app="exampleApp"> <head> <title>Angluar test</title> <Meta charset="utf-8"/> <link rel="stylesheet" type="text/css" href="css/bootstrap.min.css"> <link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css"> </head> <body> <dlv class="panel panel-default" ng-controller="scopeCtrl"> <div class="panel-heading"> <h3>Products</h3> </div> <!-- 本地作用域数据与scopeCtrl作用域数据绑定 --> <div scope-dome nameprop="data.name" city="getCity(nameValue)"></div> </dlv> <script type="text/javascript" src="js/angular.min.js"></script> <script type="text/ng-template" id="scopeTemplate"> <div class="panel-body"> <!-- 使用隔离作用域的数据 --> <P>Name: {{local}},City : {{cityFn({nameValue : local})}}</P> </div> </script> <script type="text/javascript"> angular.module("exampleApp",function ($scope) { $scope.data = { name : "Tom"}; $scope.city = "BeiJing"; $scope.getCity = function (name) { console.log(name); return name == "Tom" ? $scope.city : "Unknow"; } }) </script> </body> </html>