angularjs指令_AngularJS指令实用指南–第二部分
阅读原文时间:2021年04月20日阅读:1

angularjs指令

本教程的第一部分提供了AngularJS指令的基本概述。 在本教程的最后,我们还学习了如何隔离指令的范围。 本文将准确介绍第一部分的结尾。 首先,我们将看到如何在保持隔离的作用域的同时在指令内部访问父作用域的属性。 接下来,我们将讨论如何通过探索诸如controller功能和包含在内的概念来为指令选择正确的范围。 本文以笔记应用程序的演练结尾。

隔离和父范围属性之间的绑定

通常,隔离指令的作用域很方便,尤其是在要操纵多个作用域模型时。 但是,您可能还需要访问指令中的某些父范围属性,以使代码正常工作。 好消息是,Angular为您提供了足够的灵活性,可以通过绑定有选择地将父范围属性传递给指令。 让我们重温一下hello world指令 ,当有人在文本字段中键入颜色名称时,该指令会自动更改其背景颜色。 还记得我们隔离了指令的范围,并且代码停止工作了吗? 好吧,现在就让它工作!

假设变量app已初始化并引用了Angular模块。 该指令如下所示。

app.directive('helloWorld', function() {
return {
scope: {},
restrict: 'AE',
replace: true,
template: '<p style="background-color:{{color}}">Hello World</p>',
link: function(scope, elem, attrs) {
elem.bind('click', function() {
elem.css('background-color','white');
scope.$apply(function() {
scope.color = "white";
});
});
elem.bind('mouseover', function() {
elem.css('cursor', 'pointer');
});
}
};
});

下面的代码示例中显示了使用指令的标记。

<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color"/>
<hello-world/>
</body>

该代码当前不起作用。 由于我们具有隔离的范围,因此指令模板内的表达式{{color}}将针对此范围(而不是父级)进行评估。 但是input元素上的ng-model指令引用了父作用域属性color 。 因此,我们需要一种方法来绑定这两个隔离的父范围属性。 在Angular中,可以通过在HTML中的指令元素上设置属性并在指令定义对象中配置scope属性来实现此绑定。 让我们探索设置绑定的几种方法。

选项1:使用@进行单向文本绑定

在指令定义(如下所示)中,我们指定了独立的范围属性color应该绑定到属性colorAttr ,该属性应用于HTML中的指令。 如果查看标记,您会看到{{color}}表达式已分配给color-attr 。 当表达式的值更改时,属性color-attr也会更改。 依次更改隔离范围属性color

app.directive('helloWorld', function() {
return {
scope: {
color: '@colorAttr'
},
....
// the rest of the configurations
};
});

更新的标记如下所示。

<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color"/>
<hello-world color-attr="{{color}}"/>
</body>

我们将这种方式称为绑定,因为使用这种技术,您只能将字符串传递给属性(使用表达式{{}} )。 当父范围属性更改时,隔离的范围模型也会更改。 您甚至可以在指令中观察此scope属性,并在发生更改时触发任务。 但是,事实并非如此! 您无法通过操纵隔离范围来更改父范围模型。

注意:
如果隔离的范围属性和属性名称相同,则可以这样编写指令定义:

app.directive('helloWorld', function() {
return {
scope: {
color: '@'
},
....
// the rest of the configurations
};
});

该指令在HTML中这样调用:

<hello-world color="{{color}}"/>

选项2:使用=进行双向绑定

让我们如下更改指令定义。

app.directive('helloWorld', function() {
return {
scope: {
color: '='
},
....
// the rest of the configurations
};
});

并像这样更改HTML:

<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color"/>
<hello-world color="color"/>
</body>

@不同,此技术使您可以为属性分配实际的范围模型,而不仅仅是纯字符串。 结果,您可以将范围从简单字符串和数组到复杂对象的值传递到隔离范围。 同样,存在两种方式的绑定。 每当父范围属性更改时,相应的隔离范围属性也会更改,反之亦然。 像往常一样,您可以监视此范围属性以进行更改。

选项3:在父级作用域中使用&执行功能

有时有必要从具有独立作用域的指令中调用在父作用域中定义的函数。 要引用外部范围中定义的函数,我们使用& 。 假设我们要从指令中调用函数sayHello() 。 以下代码说明了如何实现。

app.directive('sayHello', function() {
return {
scope: {
sayHelloIsolated: '&amp;'
},
....
// the rest of the configurations
};
});

该指令在HTML中使用如下:

<body ng-controller="MainCtrl">
<input type="text" ng-model="color" placeholder="Enter a color"/>
<say-hello sayHelloIsolated="sayHello()"/>
</body>

Plunker示例演示了此概念。

父母范围vs.孩子范围vs.孤立范围

作为Angular初学者,在为指令选择正确的范围时可能会感到困惑。 默认情况下,伪指令不会创建新范围,而会使用父级的范围。 但是在很多情况下,这不是我们想要的。 如果您的指令大量操作父范围属性并创建新的属性,则可能会污染范围。 让所有指令使用相同的父范围不是一个好主意,因为任何人都可以修改我们的范围属性。 因此,以下准则可以帮助您为指令选择正确的范围。

  1. 父范围scope: false )–这是默认情况。 如果指令不操纵父范围属性,则可能不需要新的范围。 在这种情况下,可以使用父范围。
  2. 子作用域scope:true )–这将为指令创建一个新的子作用域,该指令的原型通常是从​​父作用域继承的。 如果在范围上设置的属性和函数与其他指令和父级都不相关,则可能应该创建一个新的子范围。 这样,您还可以获得父级定义的所有范围属性和函数。
  3. 隔离范围scope:{} )–这就像一个沙盒! 如果要构建的指令是自包含的并且可重用,则需要此功能。 您的指令可能正在创建许多范围属性和函数,供内部使用,而外界永远不应看到。 如果是这种情况,最好有一个独立的作用域。 如预期的那样,隔离范围不会继承父范围。

包容性

包含是一项功能,可让我们将指令包装在任意内容周围。 稍后我们可以根据正确的范围提取并编译它,最后将其放置在指令模板中的指定位置。 如果您在指令定义中设置transclude:true ,则将创建一个新的被超越的作用域,该作用域典型地从父作用域继承。 如果您希望带有隔离范围的指令包含任意内容,并针对父范围执行该内容,则可以使用包含。

假设我们有一个这样注册的指令:

app.directive('outputText', function() {
return {
transclude: true,
scope: {},
template: '<div ng-transclude></div>'
};
});

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

免费获得这本书

它的用法是这样的:

<div output-text>
<p>Hello {{name}}</p>
</div>

ng-transclude表示放置被排除内容的位置。 在这种情况下,将提取DOM内容<p>Hello {{name}}</p>并将其放入<div ng-transclude></div> 。 要记住的重要一点是,表达式{{name}}针对父作用域而不是隔离作用域中定义的属性进行插值的。 实验用的柱塞位于此处 。 如果您想了解有关范围的更多信息,请阅读本文档

transclude:'element'transclude:true之间的区别

有时我们需要包含应用指令的元素,而不仅仅是内容。 在那些情况下,使用transclude:'element' 。 与transclude:true不同,此方法将元素本身包含在标记为ng-transclude的指令模板中。 作为包含的结果,您的link函数将获得一个预先包含在正确指令范围内的包含链接的函数。 该链接函数也传递给另一个函数,该函数带有要被隐藏的DOM元素的克隆。 您可以执行诸如修改克隆并将其添加到DOM之类的任务。 诸如ng-repeat类的指令使用此技术来重复DOM元素。 看一下下面的Plunker ,它使用此技术重复DOM元素并更改第二个实例的背景颜色。

还要注意,通过使用transclude:'element' ,将在其上应用了指令的元素转换为HTML注释。 因此,如果将transclude:'element'replace:false ,则指令模板本质上会获得innerHTML ed到注释中–这意味着什么都不会发生! 相反,如果您选择replace:true则指令模板将替换HTML注释,并且一切正常。 在要重复DOM元素且不想保留该元素的第一个实例(转换为注释)的情况下,将replace:falsetransclude:'element'结合使用非常transclude:'element'

controller功能及require

如果要允许其他指令与您的指令进行通信,则使用指令的controller功能。 在某些情况下,您可能需要通过组合两个指令来创建特定的UI组件。 例如,您可以将controller功能附加到指令,如下所示。

app.directive('outerDirective', function() {
return {
scope: {},
restrict: 'AE',
controller: function($scope, $compile, $http) {
// $scope is the appropriate scope for the directive
this.addChild = function(nestedDirective) { // this refers to the controller
console.log('Got the message from nested directive:' + nestedDirective.message);
};
}
};
});

此代码将名为outerDirectivecontroller outerDirective到指令。 当另一个指令要通信时,它需要声明它需要您指令的controller实例。 如下所示。

app.directive('innerDirective', function() {
return {
scope: {},
restrict: 'AE',
require: '^outerDirective',
link: function(scope, elem, attrs, controllerInstance) {
//the fourth argument is the controller instance you require
scope.message = "Hi, Parent directive";
controllerInstance.addChild(scope);
}
};
});

标记看起来像这样:

<outer-directive>
<inner-directive></inner-directive>
</outer-directive>

require: '^outerDirective'告诉Angular在元素及其父元素上搜索控制器。 在这种情况下,找到的controller实例将作为第四个参数传递给link函数。 在我们的例子中,我们将嵌套指令的范围发送给父对象。 要尝试一下,请在打开浏览器控制台的情况下打开此Plunker 。 此Angular资源的最后一部分提供了指令间通信的出色示例。 绝对是必读!

笔记应用

在本节中,我们将使用指令构建一个简单的笔记应用程序。 我们将使用HTML5 localStorage来存储注释。 最终产品将看起来像这样 。 我们将创建一个将呈现记事本的指令。 用户可以查看他/她所做的笔记列表。 当他单击按钮时, add new记事本将变为可编辑状态,并允许创建记事。 单击back按钮时,便笺将自动保存。 注释使用一个名为notesFactory的工厂在localStorage帮助下保存。 工厂代码非常简单易懂。 因此,让我们仅关注指令代码。

第1步

我们首先注册指令notepad

app.directive('notepad', function(notesFactory) {
return {
restrict: 'AE',
scope: {},
link: function(scope, elem, attrs) {
},
templateUrl: 'templateurl.html'
};
});

请注意有关该指令的一些注意事项:

  • 范围是隔离的,因为我们希望该指令可重用。 该指令将具有许多与外部无关的属性和功能。
  • 作为指定由该指令可以被用作属性或元素restrict属性。
  • link功能最初为空。
  • 该指令从templateurl.html获取其templateurl.html

第2步

以下HTML形成了指令的模板。

<div class="note-area" ng-show="!editMode">
<ul>
<li ng-repeat="note in notes|orderBy:'id'">
<a href="#" ng-click="openEditor(note.id)">{{note.title}}</a>
</li>
</ul>
</div>
<div id="editor" ng-show="editMode" class="note-area" contenteditable="true" ng-bind="noteText"></div>
<span><a href="#" ng-click="save()" ng-show="editMode">Back</a></span>
<span><a href="#" ng-click="openEditor()" ng-show="!editMode">Add Note</a></span>

要注意的重要点是:

  • note对象封装了titleidcontent
  • ng-repeat用于循环浏览notes ,并按自动生成的id升序对其进行排序。
  • 我们将有一个属性editMode ,它将指示我们所处的模式。在编辑模式下,此属性为true ,并且可编辑div可见。 用户在这里写下笔记。
  • 如果editModefalse我们处于查看模式并显示notes
  • 这两个按钮也基于editMode显示/隐藏。
  • ng-click指令用于对按钮单击做出React。 这些方法以及诸如editMode之类的属性将添加到范围中。
  • 可编辑的div绑定到noteText ,后者保存用户输入的文本。 如果要编辑现有注释,则此模型将使用该注释内容初始化该div

第三步

让我们在我们的范围内创建一个名为restore()的新函数,该函数将为应用程序初始化各种控件。 当link功能运行且每次单击save按钮时,将调用此方法。

scope.restore = function() {
scope.editMode = false;
scope.index = -1;
scope.noteText = '';
};

我们在link函数内部创建此函数。 已经解释了editModenoteTextindex用于跟踪正在编辑的注释。 如果我们要创建新笔记,则index为-1。 如果我们要编辑现有注释,则其中包含该note对象的id

第4步

现在,我们需要创建两个作用域函数来处理编辑和保存操作。

scope.openEditor = function(index) {
scope.editMode = true;

if (index !== undefined) {
scope.noteText = notesFactory.get(index).content;
scope.index = index;
} else {
scope.noteText = undefined;
}
};

scope.save = function() {
if (scope.noteText !== '') {
var note = {};

note.title = scope.noteText.length > 10 ? scope.noteText.substring(0, 10) + '. . .' : scope.noteText;
note.content = scope.noteText;
note.id = scope.index != -1 ? scope.index : localStorage.length;
scope.notes = notesFactory.put(note);
}

scope.restore();
};

这些功能的重点是:

  • openEditor准备编辑器。 如果我们正在编辑注释,则它会获取该注释的内容并div ng-bind来更新可编辑的div
  • 如果要创建新笔记,则需要将noteText设置为undefined ,以便观察者在保存笔记时触发。
  • 如果函数参数index未定义,则意味着用户将要创建一个新注释。
  • save功能从notesFactory获得帮助以保存注释。 保存后,它将刷新notes数组,以便观察者可以检测到更改并可以更新note列表。
  • save函数最后会调用restore()来重置控件,以便我们可以从编辑模式返回查看模式。

第5步

link函数运行时,我们初始化notes数组并将keydown事件绑定到可编辑的div以便我们的noteText模型与div内容保持同步。 我们使用此noteText保存注释内容。

var editor = elem.find('#editor');

scope.restore(); // initialize our app controls
scope.notes = notesFactory.getAll(); // load notes

editor.bind('keyup keydown', function() {
scope.noteText = editor.text().trim();
});

第6步

最后,与其他任何HTML元素一样使用该指令,并开始做笔记!

<h1 class="title">The Note Making App</h1>
<notepad/>

结论

需要注意的重要一点是,我们使用jQuery所做的任何事情都可以使用Angular指令以更少的代码完成。 因此,在使用jQuery之前,请先弄清楚是否可以在没有任何DOM操作的情况下以更好的方式完成相同的操作。 尽量减少将jQuery与Angular结合使用。

关于笔记记录演示,故意删除了删除笔记功能。 鼓励读者尝试并实现此功能。 该演示的源代码可从GitHub下载。

翻译自: https://www.sitepoint.com/practical-guide-angular-directives-part-two/

angularjs指令