本网站只能被运行在支持JavaScript脚本的环境中

第四节 AngularJS中的指令

本节介绍AngularJs中的指令。通过指令的使用,我们可以自定义元素或元素的属性。

<!DOCTYPE html>
<html>
<head>
<title>AngularJS使用示例</title>
<script src="angular.min.js"></script>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script>
var directiveApp = angular.module('directiveApp', []);
directiveApp.directive('header', [function () {
    return {
        restrict: 'E',
        templateUrl: "directiveSampleHeader.html"
    };
}]);
directiveApp.directive('mymessage', [function () {
    return {
        restrict: 'E',
        template: '<div><mymessage>元素体。请选择年。</div>'
    };
}]);
directiveApp.controller('directiveCtrl', ['$scope', function ($scope) {
    $scope.years = [2010, 2011, 2012, 2013, 2014, 2015];
    $scope.selecter = 2014;
}]);

directiveApp.directive('footer', [function () {
    return {
        restrict: 'A',
        templateUrl: "directiveSampleFooter.html"
    };
}]);
</script>
</head>
<body ng-app="directiveApp">
    <div class="container">
        <div class="jumbotron">
            <h2>指令示例!</h2>
        </div>
        <div ng-controller="directiveCtrl">
            <header></header>
            <mymessage></mymessage>
            <select ng-model="selecter" ng-options="year for year in years"></select>
            <div footer></div>
        </div>
    </div>
</body>
</html>

directiveSampleHeader.html文件中的内容

<label>Header部分</label>

directiveSampleFooter.html文件中的内容

<label>Footer部分</label>

在脚本代码中,directiveApp.directive()语句表示为directiveApp模块定义指令。

在指令定义中,restrict关键字用于指定指令的作用:

可以使用“EAC”、“AE”等复合指定方法。不指定时默认使用A。

template关键字用于指定使用了指令后的元素中显示的字符串。

templateUrl关键字用于指定在使用了指令后的元素中显示的内容来源于什么文件(指定该文件的URL路径)。

在directiveCtrl控制器中,我们为页面中的select元素指定了下拉框中的所有显示内容,通过$scope.selecter指定该下拉框的初始值(该下拉框的ng-model属性值也需被指定为selecter)。

在HTML代码中,通过<header>元素使用通过header指令指定的自定义header元素。

通过mymessage元素使用通过mymessage指令指定的自定义mymessage元素。

在select元素的ng-options属性值中,通过“year for year in years”的形式对在控制器中指定的years数组进行遍历,取出其中每一个元素进行显示。

通过对div元素使用自定义footer属性来使用通过footer指令指定的自定义属性。

关于指令的名称

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        template:'<u>message={{message}}</u>'
    };
});
myApp.controller('SampleController',function($scope) {
    $scope.message = 'hoge';
});
</script>
</head>
<body ng-controller="SampleController">
<h1 my-hoge>some string</h1>
<h1 my:hoge>some string</h1>
<h1 my_hoge>some string</h1>
<h1 data-my-hoge>some string</h1>
<h1 data-my:hoge>some string</h1>
<h1 data-my_hoge>some string</h1>
<h1 x-my-hoge>some string</h1>
<h1 x-my:hoge>some string</h1>
<h1 x-my_hoge>some string</h1>
</body>
</html>

画面显示

在定义指令时,指令的名称必须使用camelCase命名法。

在使用指令时,使用短划线-进行连接,全部采用小写字母。

为了符合HTML 5标准,可以添加“data-”、“x-”等前缀。

使用模板替换元素

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        compile: function($element, $attr) {
        restrict: 'E',
        replace: true,
        template: '<h1>hoge</h1>'
    };
});
</script>
</head>
<body>
<my-hoge />
</body>
</html>

画面显示

DOM结构

<body>
<h1>hoge</h1>
</body>

将replace属性值指定为true时,模板将替换DOM中的元素。

定义创建指令前执行的复杂处理

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        compile: function($element, $attr) {
            console.log('compile');
            console.log('$attr.id = ' + $attr.id);
            $element.append('<li>four</li>');
        }
    };
});
</script>
</head>
<body>
    <ul my-hoge id="list">
        <li>one</li>
        <li>two</li>
        <li>three</li>
    </ul>
</body>

页面显示

控制台输出

compile
$attr.id = list

compile关键字用于定义页面显示时为指令的调用执行一次的回调函数。

compile函数的参数值分别为使用指令的元素($element)及存放了该元素所有属性的对象($attr)。

$element对象为jqLite对象,所以可以像jQuery那样直接使用该对象在jQuery中定义的方法。

jqLite对象具有AngularJS中追加的一些方法(controller() 、 scope()等等)。

简单来说,compile关键字用于定义页面显示时为每个使用指令的元素执行一次的回调函数,可以在此时初始化jQuery插件。

compile函数只会为指令的调用执行一次

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        compile: function($element, $attr) {
            console.log('compile');
        },
        restrict: 'E',
        template:'<p>message</p>'
    };
});
</script>
</head>
<body>
<ul>
    <li ng-repeat="i in [1, 2, 3]">
        <my-hoge></my-hoge>
    </li>
</ul>
<my-hoge></my-hoge>
</body>
</html>

页面显示

控制台输出结果

ng-repeat为AngularJS中的一个内置指令,用于对数组进行遍历。本例中数组拥有三个元素,所以循环显示三个li元素。

AngularJS只会为使用ng-repeat内置指令创建的循环中使用的指令统一调用一次compile函数,所以控制台中分别为ul列表中的my-hoge自定义元素及ul列表之后的my-hoge自定义元素调用两次compile函数,浏览器控制台中输出两次“compile”文字。

如果需要为ng-repeat内置指令创建的循环中的每一个指令调用一次函数,需要使用link函数。

使用link定义指令创建时的处理

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        link: function($scope, $element, $attr) {
            console.log('link');
        },
        restrict: 'E',
        template:'<p>message</p>'
    };
});
</script>
</head>
<body>
<ul>
    <li ng-repeat="i in [1, 2, 3]">
        <my-hoge></my-hoge>
    </li>
</ul>
</body>
</html>

控制台输出结果

在每一个指令创建时都会调用link函数。

在函数中需要添加使用$scope指令。

同时使用compile函数与link函数

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        compile: function($element, $attr) {
            console.log('compile');
            return function($scope, $element, $attr) {
                console.log('link');
            };
        },
        restrict: 'E',
        template:'<p>message</p>'
    };
});
</script>
</head>
<body>
<ul>
    <li ng-repeat="i in [1, 2, 3]">
        <my-hoge></my-hoge>
    </li>
</ul>
</body>
</html>

控制台输出结果

当同时定义compile函数与link函数时,只会执行compile函数。

如果需要同时定义compile函数与link函数,需要在compile函数的返回值中定义link函数。

使用指令内部的作用域

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        restrict: 'E',
        template:'<p>{{message}}</p>',
        link: function($scope) {
            console.log('$scope.message = ' + $scope.message);
        }
    };
});
myApp.controller('SampleController', function($scope) {
    $scope.message = 'sample controller';
});
</script>
</head>
<body>
<div ng-controller="SampleController">
    <my-hoge></my-hoge>
</div>
</body>
</html>

页面显示

控制台输出结果

在默认情况下,指令继承其父作用域。

不推荐直接继承使用其父作用域(即使用全局作用域)。

推荐为控制器或指令创建自己的内部作用域,

使用$scope指令引用控制器或指令自己的作用域。

使用继承父作用域的子作用域

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var parentScope;
var myApp = angular.module('myApp', []);
myApp.directive('myHoge',function(){
    return {
        restrict: 'E',
        scope:true,
        template:'<p>{{message}}</p>',
        link: function($scope) {
            console.log('($scope === parentScope) = ' + ($scope === parentScope));
            console.log('$scope.message = ' + $scope.message);
        }
    };
});
myApp.controller('SampleController', function($scope) {
    $scope.message = 'sample controller';
    parentScope=$scope;
});
</script>
</head>
<body>
<div ng-controller="SampleController">
    <my-hoge></my-hoge>
</div>
</body>
</html>

控制台输出结果

如果将scope属性值设置为true,可以创建继承父作用域的子作用域。

由于是子作用域,对子作用域创建的属性或方法不会影响父作用域。

创建指令内的隔离作用域

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var parentScope;
var myApp = angular.module('myApp', []);
myApp.controller('SampleController', function($scope) {
    $scope.message = 'hoge fuga piyo';
    $scope.func = function() {
        return 'HOGE FUGA PIYO';
    };
});
myApp.directive('myHoge',function(){
    return {
        scope: {
            msg: '=myMessage',
            str: '@myString',
            func: '&myFunc'
        },
        link: function($scope) {
            console.log('$scope.msg = ' + $scope.msg);
            console.log('$scope.str = ' + $scope.str);
            console.log('$scope.func() = ' + $scope.func());
        }
    };
});

</script>
</head>
<body>
<div ng-controller="SampleController">
    <div my-hoge my-message="message" my-string="message, {{message}}" my-func="func()">
    </div>
</div>
</body>
</html>

控制台输出结果

如果在指令内定义scope对象,可以创建完全独立于父作用域的隔离作用域。

使用“属性名:对于父作用域的绑定表达式”的形式定义scope对象的各属性及属性值。

可以使用如下所示的三种绑定表达式:

  • =<属性名>:与父作用域中属性名与“<属性名>”相同的属性进行绑定。
  • @<属性名>:属性值为字符串,其值等于父作用域中属性名与“<属性名>”相同的属性值,父作用域中的字符串属性值中可以包含{{}}形式的Angular表达式,将计算结果赋值给隔离作用域中的属性值。
  • &<属性名>:引用父作用域中的同名函数。

定义scope对象时使用的<属性名>与模板或DOM中使用的属性名的匹配原则与指令名称的匹配原则相同(myMessage与my-message相匹配)。

如果使用=<属性名>绑定表达式,修改隔离作用域中的属性值时父作用域中的对应属性值也被同步修改。

省略属性名

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('SampleController', function($scope) {
    $scope.message = 'hoge fuga piyo';
    $scope.func = function() {
        return 'HOGE FUGA PIYO';
    };
});
myApp.directive('myHoge',function(){
    return {
        scope: {
            myMessage: '=',
            myString: '@',
            myFunc: '&'
        },
        link: function($scope) {
            console.log('$scope.msg = ' + $scope.myMessage);
            console.log('$scope.str = ' + $scope.myString);
            console.log('$scope.func() = ' + $scope.myFunc());
        }
    };
});
</script>
</head>
<body>
<div ng-controller="SampleController">
    <div my-hoge my-message="message" my-string="message, {{message}}" my-func="func()">
    </div>
</div>
</body>
</html>

控制台输出结果

当为scope对象定义的属性名与隔离作用域内使用的属性名相同时,可在属性值定义中省略属性名。

为同一元素传递不同指令内的作用域

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myParent',function(){
    return {
        controller: function() {
            this.method = function() {
                console.log('Parent Controller');
            };
        }
    };
});
myApp.directive('myChild',function(){
    return {
        require:'myParent',
        link:function($scope, $element, $attr, cntroller) {
            cntroller.method();
        }
    };
});
</script>
</head>
<body>
<div my-parent my-child></div>
</body>
</html>

控制台输出结果

Parent Controller

当在指令中引用另一个指令中的作用域时,需定义controller函数。

为被调用指令定义controller函数,在调用指令中使用require属性引用被调用指令,属性值为被调用指令名。

为调用指令定义link函数,使用函数第四个参数值引用被调用指令中的controller函数(参数名可任意)。

为避免寻找不到被调用指令,可以在require属性值字符串中添加使用@前缀( require: '@myPArent' )。

为不同元素传递不同指令内的作用域

<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<script src="angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('myParent',function(){
    return {
        controller: function() {
            this.method = function() {
                console.log('Parent Controller');
            };
        }
    };
});
myApp.directive('myChild',function(){
    return {
        require:'^myParent',
        link:function($scope, $element, $attr, cntroller) {
            cntroller.method();
        }
    };
});
</script>
</head>
<body>
<my-parent>
    <my-child />
</my-parent>
</body>
</html>

控制台输出结果

Parent Controller

如果在调用方(Child)的require属性值中添加使用“^”前缀,将从父元素中寻找指令(如果不使用则只在当前元素中寻找指令)。