AngularJS自定义指令

时间:2020-01-09 10:34:01  来源:igfitidea点击:

自定义指令简介

AngularJS指令控制AngularJS应用程序中HTML的呈现。指令示例包括插值指令({{}}),ng-repeat指令和ng-if指令。

也可以实现自己的指令。这就是AngularJS所说的"教HTML新技巧"。本文将向我们展示如何做到这一点。

指令类型

我们可以实现以下类型的指令:

  • 元素指令
  • 属性指令
  • CSS类指令
  • 注释指令

其中,AngularJS建议我们尝试使用元素和属性指令,并保留CSS类和注释指令(除非绝对必要)。

指令的类型决定了如何激活指令。当AngularJS在HTML模板中找到匹配的HTML元素时,将激活元素指令。当AngularJS找到匹配的HTML元素属性时,将激活属性指令。当AngularJS找到匹配的CSS类时,将激活CSS类指令。并且,当AngularJS找到匹配的HTML注释时,注释指令将被激活。

基本指令

我们向模块注册指令。这是一个看起来的例子:

myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */

    directive.template = "My first directive: {{textToInsert}}";

    return directive;
});

注意模块上对directive()函数的调用。调用此函数时,可以注册一个新指令。 " directive()"函数调用的第一个参数是要注册的指令的名称。此名称是我们要激活指令时在HTML模板中使用的名称。在此示例中,我使用了名称" div",这意味着该指令每次在HTML模板中找到名为" div"的HTML元素时都被激活。

传递给"指令"功能的第二个参数是工厂功能。该函数在调用时应返回指令定义。 AngularJS将调用此函数以获得一个包含指令定义的JavaScript对象。如果我们在上面的示例中查看该函数,我们会发现它确实返回了一个JavaScript对象。

从工厂函数返回的JavaScript对象具有两个属性:"限制"字段和"模板"字段。

"限制"字段用于设置指令是否应由匹配的HTML元素或者元素属性激活。通过将" restrict"设置为" E",我们可以指定只有名为" div"的HTML元素才能激活指令。通过将" restrict"设置为" A",我们可以指定只有名为" div"的HTML属性才能激活该指令。我们还可以使用值" AE",该值将与HTML元素名称和属性名称匹配。

template字段是一个HTML模板,它将替换匹配的div元素的内容。它将好像没有匹配的div元素的内容那样工作,而是将该HTML模板放置在同一位置。

想象一下,HTML页面具有以下HTML:

<div ng-controller="MyController" >
    <div>This div will be replaced</div>
</div>

然后,当AngularJS找到内部的" div"元素时,将激活添加的指令。代替此div元素,将插入以下HTML:

My first directive: {{textToInsert}}

如我们所见,此HTML包含一个插值指令({{textToInsert}})。 AngularJS将再次解释此HTML,以便插值指令实际起作用。此时,将在HTML中插入$ scope.textToInsert属性的值。

template和templateUrl属性

如上例所示,创建自己的指令的最简单方法。指令旨在生成HTML,并将HTML放入指令定义对象的template属性中。这是从前开始重复的指令定义,其中template字符串用粗体标记:

myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.template = "My first directive: {{textToInsert}}";

    return directive;
});

如果HTML模板变大,则很难在JavaScript字符串中编写和维护HTML。然后,我们可以将HTML放入其自己的文件中,并让AngularJS从该文件中加载它。我们可以通过将HTML模板文件的URL放入指令定义对象的templateUrl属性中来实现。这是一个例子:

myapp = angular.module("myapp", []);

myapp.directive('div', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.templateUrl = "/myapp/html-templates/div-template.html";

    return directive;
});

AngularJS现在将从在templateUrl属性中设置的URL加载HTML模板。

当我们创建更专门的指令(例如显示用户信息的指令)时,使用单独的HTML模板文件和templateUrl属性特别有用。这是一个例子:

myapp = angular.module("myapp", []);

myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */
    directive.templateUrl = "/myapp/html-templates/userinfo-template.html";

    return directive;
});

这个例子创建了一个指令,每当AngularJS找到一个<userinfo>元素时,该指令就被激活。 AngularJS加载在/ myapp / html-templates / userinfo-template.html中找到的HTML模板,并将其解释为好像从一开始就位于父HTML文件中。

将$ scope与指令隔离

在上面的示例中,userinfo指令很难绑定到$ scope变量,因为HTML模板直接引用了textToInsert属性。直接引用$ scope变量使在同一控制器内多次重复使用该指令变得很困难,因为$ scope变量通常在同一控制器内各处都具有相同的值。例如,如果我们想在页面中包含此HTML:

<userinfo></userinfo>
<userinfo></userinfo>

然后,将两个<userinfo>元素替换为相同的HTML模板,该模板绑定到相同的$ scope变量。结果是两个<userinfo>元素将被完全相同的HTML代码替换。

为了能够将两个<userinfo>元素绑定到$ scope对象中的不同值,我们需要将HTML模板绑定到一个独立的作用域。

孤立范围是与指令绑定的单独范围对象。这是在指令定义对象中定义它的方式:

myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E';

    directive.template = "User : {{user.firstName}} {{user.lastName}}";

    directive.scope = {
        user : "=user"
    }

    return directive;
})

请注意,HTML模板如何将两个插值指令绑定到" {{user.firstName}}"和" {{user.lastName}}"。注意"用户"部分。并注意" directive.scope"属性。 " directive.scope"属性是一个JavaScript对象,其中包含名为" user"的属性。 " directive.scope"属性是隔离范围对象,并且HTML模板现在已通过" {{user.firstName}}"和" {{user.lastName}"绑定到" directive.scope.user"对象。 }`插补指令)。

" directive.scope.user"属性设置为"" = user"`。这意味着,directive.scope.user属性被绑定到scope属性中的属性(而不是在隔离范围中),并且名称被传递给<userinfo>元素的user属性。这听起来令人困惑,因此请看以下HTML示例:

<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

这两个&lt;userinfo>元素包含一个user属性。这些属性的值包含$ scope对象中的属性名称,隔离范围对象的userinfo属性将引用这些属性的名称。

这是一个完整的示例:

<userinfo user="jakob"></userinfo>
<userinfo user="john"></userinfo>

<script>
myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E';

    directive.template = "User : <b>{{user.firstName}}</b> <b>{{user.lastName}}</b>";

    directive.scope = {
        user : "=user"
    }

    return directive;
});

myapp.controller("MyController", function($scope, $http) {
    $scope.jakob = {};
    $scope.jakob.firstName = "Jakob";
    $scope.jakob.lastName  = "Jenkov";

    $scope.john = {};
    $scope.john.firstName = "John";
    $scope.john.lastName  = "Doe";
});

</script>

compile()和link()函数

如果我们需要在指令中执行更高级的操作,而HTML模板则无法执行某些操作,则可以改用compile()link()函数。

compile()和link()函数定义指令如何修改与指令匹配的HTML。

HTML页面中每次出现该指令时,都会一次调用一次compile()函数。然后,compile()函数可以对包含指令的元素进行任何一次性配置。

通过返回link()函数来完成compile()函数。每次将元素绑定到$ scope对象中的数据时,都会调用link()函数。

如前所述,我们将compile()函数添加到指令定义对象,并且compile()函数必须在执行时返回link()函数。看起来是这样的:

<script>
myapp = angular.module("myapp", []);
myapp.directive('userinfo', function() {
    var directive = {};

    directive.restrict = 'E'; /* restrict this directive to elements */

    directive.compile = function(element, attributes) {
        // do one-time configuration of element.

        var linkFunction = function($scope, element, atttributes) {
            // bind element to data in $scope
        }

        return linkFunction;
    }

    return directive;
});    
</script>

函数compile()具有两个参数:element和attributes参数。

element参数是jqLite包装的DOM元素。 AngularJS包含一个精简版的jQuery,可进行DOM操作,因此element的DOM操作方法与我们从jQuery所知道的相同。

" attributes"参数是一个JavaScript对象,其中包含DOM元素的所有属性的属性。因此,要访问名为" type"的属性,我们可以编写" attributes.type"。

link()函数具有三个参数:$ scope参数,element参数和attributes参数。 element和attributes参数与传递给compile()函数的参数相同。 $ scope参数是普通作用域对象,或者是隔离作用域(如果我们在指令定义对象中指定了一个作用域)。

实际上,compile()link()函数名称令人困惑。它们受到编译器术语的启发。我可以看到相似之处,但是编译器只解析一次输入,然后创建输出。伪指令配置一个HTML元素,然后在$ scope对象改变时随后更新该HTML。

函数compile()更好的名字应该是诸如create(),init()或者configure()之类的东西。信号表明此函数仅被调用一次。

更好的名字是link()函数,如bind()或者render(),这表示只要指令需要将数据绑定到该函数或者重新渲染该函数,就会调用该函数。它。

这是一个完整的示例,显示了同时使用compile()和link()函数的指令:

<div ng-controller="MyController" >
    <userinfo >This will be replaced</userinfo>
</div>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('userinfo', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */

        directive.compile = function(element, attributes) {
            element.css("border", "1px solid #cccccc");

            var linkFunction = function($scope, element, attributes) {
                element.html("This is the new content: " + $scope.firstName);
                element.css("background-color", "#ffff00");
            }

            return linkFunction;
        }

        return directive;
    })
    myapp.controller("MyController", function($scope, $http) {
        $scope.cssClass = "notificationDiv";

        $scope.firstName = "Jakob";

        $scope.doClick = function() {
            console.log("doClick() called");
        }
    });
</script>

函数compile()在HTML元素上设置边框。这仅执行一次,因为compile()函数仅执行一次。

link()函数替换HTML元素的内容,并将背景色设置为黄色。

没有特别的原因为什么要在compile()函数中设置边框,而在link()函数中设置背景颜色。两者都可以在compile()函数中设置,也可以在link()函数中设置。如果在compile()函数中设置,它们只会被设置一次(通常是我们想要的)。如果在link()函数中设置,则每次将HTML元素绑定到$ scope对象中的数据时,都将对其进行设置。如果我们需要根据$ scope对象中的数据不同地设置边框和背景颜色,这可能会很有用。

只设置一个link()函数

有时,指令不需要compile()步骤。我们只需要link()函数。在这种情况下,我们可以直接在指令定义对象上设置link()函数。这是以前的示例,仅带有链接功能:

<div ng-controller="MyController" >
    <userinfo >This will be replaced</userinfo>
</div>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('userinfo', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */

        directive.link = function($scope, element, attributes) {
                element.html("This is the new content: " + $scope.firstName);
                element.css("background-color", "#ffff00");
        }

        return directive;
    })
    myapp.controller("MyController", function($scope, $http) {
        $scope.cssClass = "notificationDiv";

        $scope.firstName = "Jakob";

        $scope.doClick = function() {
            console.log("doClick() called");
        }
    });
</script>

请注意,link()函数的功能与上一示例中返回的link()函数相同。

通过包含换行符的指令

到目前为止,我们已经看到的示例都通过JavaScript代码或者HTML模板设置了与指令本身匹配的元素的内容。但是,如果我们想要一个指令来包装开发人员插入到指令主体中的元素,该怎么办?例如:

<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>

该指令由&lt;mytransclude>元素标记。但是其中的内容是由开发人员设置的。因此,不应将HTML的这一部分替换为指令的HTML模板。我们实际上希望AngularJS处理HTML的那部分。该处理称为"包含"。

为了使AngularJS处理指令中的HTML,必须将指令定义对象的transclude属性设置为true。我们还必须告诉AngularJS指令的HTML模板的哪一部分包含已包含的HTML。我们可以通过将ng-transclude属性(实际上是一个指令)插入到HTML模板中想要插入被插入HTML的HTML元素中。

这是一个AngularJS指令,显示了如何使用包含:

<mytransclude>This is a transcluded directive {{firstName}}</mytransclude>

<script>
    myapp = angular.module("myapp", []);
    myapp.directive('mytransclude', function() {
        var directive = {};

        directive.restrict = 'E'; /* restrict this directive to elements */
        directive.transclude = true;
        directive.template = "<div class='myTransclude' ng-transclude></div>";

        return directive;
    });
    myapp.controller("MyController", function($scope, $http) {
        $scope.firstName = "Jakob";
    });
</script>

注意&lt;mytransclude>元素内的HTML。该HTML代码包含插值指令{{firstName}}。我们希望AngularJS为我们处理此HTML,以便执行插值指令。为此,我在指令定义对象上将transclude属性设置为true。我还在HTML模板中插入了一个ng-transclude属性。该属性告诉AngularJS将插入的HTML插入到哪个元素中。