AngularJS 自定义指令:模板、作用域和转置

⚡ 智能摘要

AngularJS 中的自定义指令通过自定义可重用的行为来扩展 HTML。指令工厂会返回一个定义对象,AngularJS 会将该行为附加到标记中所有匹配的元素、属性、类或注释处。

  • ????️ 注册: module.directive 方法接受一个规范化的名称以及一个返回指令定义对象的工厂函数。
  • 🔤 命名: 内部名称采用驼峰式命名法。 JavaHTML 内部的脚本和 kebab-case 格式,所以 ngGuru 页面中写成 ng-guru。
  • 🎯 匹配: restrict 属性决定 AngularJS 是否匹配元素、属性、类或注释,其默认值为 EA。
  • 🧬 范围访问: 指令默认与父作用域共享,而 scope 选项则使用 @、=、& 或 < 绑定构建隔离作用域。
  • 🔗 链接功能: link 函数接收 scope、element 和 attrs 参数,它是 DOM 监听器和清理操作的正确位置。
  • 🧱 组成: 指令可以相互嵌套,通过转置包装任意标记,并通过控制器向同级指令公开 API。
  • ⚠️ 支持状态: AngularJS 已于 2021 年 12 月 31 日停止维护,不再接收任何补丁或安全修复程序。

AngularJS 中的自定义指令

AngularJS 中的自定义指令是什么?

A 自定义指令 在 AngularJS 中,指令是一种用户自定义指令,它通过用户自行编写的行为来扩展 HTML。它注册在……上。 模块 使用“指令”功能,AngularJS 会将其附加到页面中匹配的元素或属性所在的任何位置。尽管 AngularJS 拥有许多强大的功能,但它仍然需要一些技巧才能实现。 内置指令 开箱即用,例如 ng-重复有时需要自定义指令。

⚠️ 更正: 原文称自定义指令“替换它所指向的元素”。但默认情况下,它不会这样做: template 被插入 匹配的元素。替换需求 replace: true自 AngularJS 1.3 起已弃用。

⚠️ 支持通知: AngularJS 已达到 生命终结日期为2021年12月31日 并且不再接收任何补丁或安全修复。以下所有内容均适用于维护现有的 AngularJS 1.x 代码;在现代 Angular 中,对应的是组件和属性指令。

使用自定义指令的原因在于重复性。当相同的标记或 DOM 结构出现在多个屏幕上时,指令会将其转换为一个命名标签。

如何创建自定义指令?

让我们来看一个如何创建 AngularJS 自定义指令的例子。

在本例中,自定义指令的作用很简单,就是在调用该指令时,在页面中注入一个包含文本“Angular JS Tutorial”的 div 标签。

下面带注释的屏幕截图对后面解释的清单的四个部分进行了编号。

创建自定义指令

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp">
    <div ng-guru=""></div>

</div>

<script type="text/javascript">
    var app = angular.module('DemoApp',[]);
    app.directive('ngGuru',function(){

    return {
        template: '<div>Angular JS Tutorial</div>'
    }
    });

</script>

</body>
</html>

⚠️ 警告——本商品描述中保留了瑕疵: 代码完全按照已发布版本复现。此处的每个版本都存在三个重复的错误: chrset="UTF 8" 应该 charset="UTF-8"; angular.jsangular.min.js 两者都会加载,所以AngularJS会加载两次; angular-route.js 在它所依赖的文件之前加载。

Code 说明

  1. 我们首先创建一个 模块 适用于我们的 AngularJS 应用程序。这是创建自定义指令所必需的,因为该指令将使用此模块创建。
  2. 我们现在正在创建一个名为“ng”的自定义指令。Guru并定义一个工厂函数,该函数将保存我们指令的自定义代码。
  3. 我们使用的是模板参数,这是 AngularJS 为自定义指令定义的一个参数。在这里,我们定义了每当使用此指令时,都直接使用模板的值并将其注入到调用代码中。
  4. 现在我们使用自定义创建的“ng-guru”指令。这样做时,我们为模板定义的值, <div>Angular JS Tutorial</div>现在将注入此处。

指令命名和规范化

名称不匹配 Java脚本和 HTML 让大多数初学者感到困惑。AngularJS 正常化 在匹配之前,它会移除所有元素和属性名称:它会移除任何 x- or data- 前缀,然后转换以……分隔的名称 -, : or _ 改为驼峰式命名法。这就是原因。 ngGuru 火柴 ng-guru,为什么 data-ng-guru 也行。驼峰式命名法才是它的正式名称;烤肉串式命名法只是它在 HTML 中的写法。

⚠️ 更正: 原文指出,自定义指令名称“应以字母‘ng’开头”。 官方 AngularJS 文档 建议恰恰相反:永远不要在自己的指令前加上前缀 ng否则,它们可能会与未来内置的功能冲突。请使用您自己的前缀,例如: g99Guru 写成 g99-guru.

如果代码执行成功,则会显示此输出。

输出:

创建自定义指令

输出结果清晰地表明,我们自定义的 ng-guru 指令(其中定义了用于显示自定义文本的模板)已在浏览器中成功显示。下一步是在该模板中添加一些可复用的内容。

如何创建可重用的指令

我们已经看到了自定义指令的强大功能,但我们可以通过构建我们自己的可重用指令将其提升到一个新的水平。

例如,假设我们想要注入一段代码,使其始终在多个屏幕上显示以下 HTML 标签,这基本上只是一个用于输入用户“姓名”和“年龄”的输入框。

创建可重用指令

为了在多个屏幕上重复使用此片段而无需每次都编写代码,我们在 AngularJS 中创建一个主控件或指令来保存这两个控件。

所以现在,我们不再在每个屏幕上重复这段标记,而是将其嵌入到一个指令中,然后调用该指令。下面的截图标记了唯一更改的一行。

创建可重用指令

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp">
    <div ng-guru=""></div>

</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.directive('ngGuru',function(){
        return {

            template: '&nbsp;&nbsp;Name <input type="text"><br><br>&nbsp;&nbsp;&nbsp;Age<input type="text">'
        };
    });

</script>

</body>
</html>

Code 说明

  • 在我们的自定义指令代码片段中,唯一改变的是模板参数的值。我们不再使用普通的标签或一行文本,而是输入页面上需要显示的“姓名”和“年龄”两个输入控件的完整代码片段。

👍 提示: 这么长的模板应该放在单独的文件中。交换 template HPMC胶囊 templateUrl: 'name-age.html'.

如果代码执行成功,则会显示此输出。

输出:

创建可重用指令

从上面的输出可以看出,自定义指令模板中的代码片段已添加到页面中。该模板目前仍是静态的,因此下一步是为其填充数据。

AngularJS 指令和作用域

范围 被定义为将控制器与视图连接起来的粘合剂,它通过管理视图和控制器之间的数据来实现这一功能。

创建自定义 AngularJS 指令时,默认情况下它们可以访问父控制器中的作用域对象。这是同一个作用域对象,而不是副本。

这样,自定义指令就可以轻松利用传递给主控制器的数据。

让我们来看一个例子,了解如何在自定义指令中使用父控制器的作用域。下面的截图标出了四个步骤。

AngularJS 指令和作用域

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp" ng-controller="DemoController">
    <div ng-guru=""></div>

</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.controller('DemoController',function($scope) {
        $scope.tutorialName = "Angular JS";

    });

        app.directive('ngGuru',function(){
        return {
         template: '<div>{{tutorialName}}</div>'
     }
    });

</script>

</body>
</html>

Code 说明

  1. 我们首先创建一个名为“DemoController”的控制器。在这个控制器中,我们定义一个名为tutorialName的变量,并用一条语句将其附加到作用域对象上: $scope.tutorialName = "Angular JS".
  2. 在我们的自定义指令中,我们可以使用表达式调用变量“tutorialName”。此变量是可访问的,因为它是在控制器“DemoController”中定义的,该控制器将成为此指令的父级。
  3. 我们在 div 标签中引用控制器,该标签将充当我们的父 div 标签。请注意,需要先完成此操作,以便我们的自定义指令可以访问 tutorialName 变量。
  4. 最后,我们只需将自定义指令“ng-guru”附加到我们的 div 标签即可。

如果代码执行成功,则会显示此输出。

输出:

AngularJS 指令和作用域

以上输出清楚地表明,我们的自定义指令“ng-guru”使用了父控制器中的作用域变量 tutorialName。共享父级作用域很方便,但并非总是我们想要的。

使用带指令的控制器

AngularJS 提供了以下功能: 直接从自定义指令访问控制器的成员变量 而不需要范围对象。

有时这样做是必要的,因为一个应用程序可能具有属于多个控制器的多个作用域对象,因此很有可能错误地访问到错误控制器的作用域对象。

在这种情况下,我可以通过指令明确地表达“我想访问这个控制器”。

让我们来看一个例子,了解我们如何才能做到这一点。 截图中对四个步骤进行了编号。

使用带指令的控制器

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp" ng-controller="DemoController">
    <div ng-guru99=""></div>

</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.controller('DemoController',function() {
        this.tutorialName = "Angular";

    });

     app.directive('ngGuru99',function(){
        return {
         controller: 'DemoController',

          controllerAs: 'ctrl',

          template: '{{ctrl.tutorialName}}'
     };
    });

</script>

</body>
</html>

Code 说明

  1. 我们首先创建一个名为“DemoController”的控制器。在这个控制器中,我们将定义一个名为“tutorialName”的变量,但这次,我们不会将其附加到作用域对象上,而是直接附加到控制器上。
  2. 在我们的自定义指令中,我们特别提到我们要通过使用控制器参数关键字来使用控制器“DemoController”。
  3. 我们使用“controllerAs”参数创建对控制器的引用。这是AngularJS定义的,用于通过别名引用控制器。
  4. 最后,在我们的模板中,我们使用了步骤 3 中创建的引用,并使用了步骤 1 中直接附加到控制器的成员变量。

注意: 可以通过在指令中指定相应的控制器代码块、controllerAs 和模板语句来访问多个控制器。 请改用指令控制器,使用 require.

如果代码执行成功,则会显示此输出。

输出:

使用带指令的控制器

输出结果清晰地表明,自定义指令正在访问 DemoController 及其关联的成员变量 tutorialName,并显示文本“Angular”。两种方法仍然从共享作用域读取数据,而隔离作用域则可以避免这种情况。

在隔离作用域中,@、=、& 和 < 绑定有何不同?

共享父作用域的指令在每个控制器中只能使用一次,因为两个副本会争用相同的变量。 scope 对象创建了一个 隔离范围该指令只访问您列出的属性,不访问其他任何内容。每个条目都使用一个前缀符号来声明其值如何传递。

图形符号 方向性 收到的价值 用它来做
@ 单行道 属性作为插值字符串 标签、标题、任何纯文本
= 双向 活物或价值本身 模型指令写回
& 外呼 一个函数,用于计算外部作用域中的表达式。 回调函数,例如关闭回调或保存回调。
< 单行道 评估值,从未向上书写。 AngularJS 1.5 版本新增了只读数据功能。

👍 提示: 伸手去拿 < before =单向绑定的监控成本更低,并且可以防止组件在后台悄悄修改调用者的数据。下一节将使用 @.

AngularJS 指令和组件:ng-transclude

正如我们之前提到的,AngularJS旨在扩展以下功能: HTML。我们已经了解了如何通过使用自定义可重用指令来实现代码注入。

但在现代 Web 应用程序开发中,也存在开发的概念ping Web 组件,基本上是指创建我们自己的 HTML 标签,这些标签可以用作我们代码中的组件。

因此,AngularJS 为扩展 HTML 标签提供了另一层强大的功能,它允许将调用者放入标签内的任何标记包裹起来。

这是通过“ng-transclude”指令告诉 AngularJS 捕获标记中自定义标签内的所有内容,并将其放入模板中。

让我们举个例子来说明如何实现这一点。截图中标出了以下五点。

AngularJS 指令和组件

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp">
    <pane title="{{title}}">Angular JS</pane>

</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.directive('pane',function(){
        return {

            transclude:true,
            scope :{title:'@'},
            template: '<div style="border: 1px solid black;"> '+
                        '<ng-transclude></ng-transclude>'+
                            '</div>'
        };
    });

</script>

</body>
</html>

Code 说明

  1. 我们使用该指令定义了一个名为“pane”的自定义HTML标签,并通过一个工厂函数提供其代码。输出结果中,“pane”标签会显示一个带有黑色实线边框的矩形框,框内文字为“Angular JS”。
  2. 必须将“transclude”属性设置为true,这样AngularJS才能保留原始的内部标记并使其可用。 ng-transclude.
  3. 在作用域对象中,我们声明了一个标题绑定。属性通常以名称/值对的形式编写,例如: name="value"在我们的例子中,窗格标签的属性是“title”,而“@”符号要求AngularJS将该属性作为插值字符串复制过来。
  4. 该模板绘制了黑色实线边框以供我们控制,并标记了插入嵌入标记的位置。
  5. 最后,我们调用自定义 HTML 标签以及已定义的 title 属性。

⚠️ 更正: 原文将“@”描述为“Angular的要求”。这并非形式主义: @ 是上表中四个隔离作用域绑定符号之一,并且始终返回一个字符串。此模板也永远不会渲染。 {{title}}并且没有控制器定义 title因此,只有被嵌入的文本可见。

如果代码执行成功,则会显示此输出。

输出:

AngularJS 指令和组件

  • 输出结果显示自定义窗格元素渲染为一个带边框的框,其中包含嵌入的文本“Angular JS”。

指令还可以包含其他指令,这就是组装更大组件的方式。

嵌套指令

AngularJS 中的指令可以嵌套,就像任何模块或函数内部都可以嵌套一样。 编程语言,您可能需要在彼此内嵌入指令。

通过查看下面的示例,您可以更好地理解这一点。

在这个例子中,我们创建了两个名为“outer”和“inner”的指令。

  • inner 指令显示名为“Inner”的文本。
  • 而外部指令实际上调​​用内部指令来显示名为“Inner”的文本。

下面的屏幕截图标记了两个指令定义以及一个指令调用另一个指令的位置。

嵌套指令

</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp">
    <outer></outer>
</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.directive('outer',function(){
        return {

            restrict:'E',
            template: '<div><h1>Outer</h1><inner></inner></div>',
        }});

    app.directive('inner',function(){
        return {

            restrict:'E',
            template: '<div><h1>Inner</h1></div>',
        }
    });
</script>

</body>
</html>

⚠️ 警告——本商品描述中保留了缺陷信息: 已公布的清单从……开始 </head>错过了开幕式 <!DOCTYPE html>, <html><head> 请在运行前添加这些行。

Code 说明

  1. 我们正在创建一个名为“outer”的指令,它将充当我们的父指令。然后,该指令将调用“inner”指令。
  2. restrict:'E' 该选项告诉 AngularJS 按以下方式匹配每个指令 element 名称,这就是为什么它们可以写成标签的原因。 <outer><inner>字母“E”是单词“Element”(元素)的缩写形式。
  3. 这里我们创建内部指令,在 div 标签中显示文本“Inner”。
  4. 在外部指令的模板中,我们调用了内部指令。所以这里我们将内部指令的模板注入到外部指令中。
  5. 最后,我们直接调用外部指令。

⚠️ 更正: 原文声称 restrict:'E' 需要这样做是为了“确保内部指令中的数据可供外部指令使用”。但它实际上并没有这样做;它只是控制 形成一种 可以编写指令。嵌套之所以有效,是因为编译器会不断遍历它刚刚插入的模板。要共享数据,请使用 requirecontroller.

如果代码执行成功,则会显示此输出。

输出:

嵌套指令

从输出来看,

  • 可以看出,外部指令和内部指令都已被调用,并且两个 div 标签中的文本都已显示。

指令的功能只有一半,那就是渲染标记;另一半是响应用户操作。

指令中的事件处理

活动 诸如鼠标点击或按钮点击之类的事件可以直接在指令内部处理。这是通过 link 函数实现的。link 函数允许指令将自身附加到 HTML 页面中的 DOM 元素上。

语法:

链接函数的语法如下所示。

link: function ($scope, element, attrs)

链接函数通常接受三个参数:作用域、指令关联的元素以及目标元素的属性。另外两个参数,即必需的控制器和转置函数,会在需要时添加。

编译阶段与链接阶段

AngularJS 分两步处理指令,了解哪一步是哪一步可以告诉你代码应该放在哪里。

  • 汇编 它对模板元素运行一次,在任何作用域存在之前。它适用于所有克隆共享的结构性编辑。
  • 链接 每个实例运行一次,在模板克隆并附加作用域之后运行。DOM 监听器和作用域读取操作都应在此处进行。

因为 `compile` 没有作用域,所以任何需要数据的东西都应该放在 `link` 中,这就是为什么下面的示例定义了 `compile`。 link 单独。

让我们来看一个例子,看看我们如何才能做到这一点。 截图中标出了以下四点。

处理指令中的事件

<!DOCTYPE html>
<html>
<head>
    <meta chrset="UTF 8">
    <title>Event Registration</title>
</head>
<body>

<script src="https://code.angularjs.org/1.6.9/angular-route.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.js"></script>
<script src="https://code.angularjs.org/1.6.9/angular.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>

<h1> Guru99 Global Event</h1>

<div ng-app="DemoApp">
    <div ng-guru="">Click Me</div>
</div>

<script type="text/javascript">

    var app = angular.module('DemoApp',[]);

    app.directive('ngGuru',function(){
        return {

            link:function($scope,element,attrs) {
                element.bind('click',function () {
                    element.html('You clicked me');
                });}
        }});
</script>

</body>
</html>

Code 说明

  1. 我们使用 AngularJS 中定义的 link 函数,使指令能够访问 HTML DOM 中的事件。
  2. 我们使用 `element` 关键字是因为我们需要响应 HTML DOM 元素的事件,在本例中是 `div` 元素。然后,我们使用 `bind` 函数为该元素的点击事件添加自定义功能。`click` 关键字用于表示任何 HTML 控件的点击事件。
  3. 这里我们想表达的是,我们要将元素(在本例中是 div 元素)的内部 HTML 替换为文本“你点击了我”。
  4. 在这里我们定义 div 标签以使用 ng-guru 自定义指令。

👍 提示: 指令应该能够自我清理。移除任何在元素生命周期结束后仍然存活的监听器。 element.on('$destroy', ...)或者页面存在内存泄漏。

如果代码执行成功,则会显示此输出。

输出:

处理指令中的事件

  • 最初会向用户显示文本“点击我”,因为这是 div 标签内定义的文本。点击 div 标签后,会显示以下输出。

处理指令中的事件

以上每个示例都使用了同一个定义对象的一个​​或两个属性。完整的属性集值得保留。ping 手边有货。

指令定义对象属性参考

指令工厂返回的对象是 指令定义对象每个属性都回答了一个关于指令行为方式的问题。

特性 默认 它做什么
限制 EA 哪些形式匹配:E 元素、A 属性、C 类、M 注释
模板 没有 将内联标记插入到匹配的元素中
模板网址 没有 模板文件路径,获取一次后缓存
范围 false false 表示共享父作用域,true 表示创建子作用域,true 表示对象隔离。
调节器 没有 构造函数公开 API,其他指令可能需要
控制器 没有 模板内控制器的别名
要求 没有 要注入的另一个指令的控制器;^ 搜索父级,^^ 跳过自身,? 可选
链接 没有 每个实例的功能:作用域、元素、属性、控制器、transcludeFn
没有 在任何作用域存在之前,对模板运行一次。
插入 false 保留 ng-transclude 的原始内部标记
优先 0 共享元素的编译顺序;数值越高,运行顺序越靠前。
终端 false 停止对该元素执行优先级较低的指令
更换 false 将元素替换为模板。自 1.3 版本起已弃用。

👍 提示: 开始 restrict, templatelink。 加 scope 可重复使用, controllerrequire 对于指令间的调用,并离开 priorityterminal 单独。

常见问题

组件(在 AngularJS 1.5 中新增)是一种指令,它具有预设的默认行为:仅匹配元素、隔离作用域以及控制器。组件适用于自包含视图,而指令适用于需要链接级 DOM 访问的情况。

通常情况下,名称没有正确规范化,或者限制排除了您使用的形式。一条注册为我的指令。Box 匹配 my-box,而不是 mybox,并且元素标签需要限制以包含 E。

是的。 AI 助手将隔离作用域映射到输入和输出装饰器,并将模板映射到组件模板。 Rev手动查看嵌入和链接函数 DOM 代码,因为这些代码很少能被干净地翻译。

人工智能助手生成 单元测试 这些框架会编译指令、附加作用域并对渲染输出进行断言。它们擅长列出各种边界情况,因此可以将生成的断言视为初始草稿。

当标记内容超过一两行时,请使用 templateUrl。该文件可以使用常规 HTML 工具进行编辑,并且只需获取一次,之后每次实例都会从模板缓存中提供。

总结一下这篇文章: