지시문은 모든 AngularJS 애플리케이션에서 가장 중요한 부분입니다. AngularJS는 이미 매우 풍부한 지시문 세트를 제공하지만 애플리케이션별 지시문을 작성해야 하는 경우가 많습니다. 이 튜토리얼에서는 지침을 사용자 정의하는 방법을 설명하고 실제 프로젝트에서 사용하는 방법을 소개합니다. 이 기사(2부)의 마지막 부분에서는 Angular 지시문을 사용하여 간단한 메모장 애플리케이션을 만드는 방법을 안내합니다.
개요
새로운 HTML 구문을 도입하는 데 사용되는 지시문입니다. 지시문은 요소가 특정 동작을 하도록 하는 DOM 요소의 태그입니다. 예를 들어 정적 HTML은 날짜 선택 컨트롤을 만들고 표시하는 방법을 모릅니다. HTML이 이 구문을 인식하도록 하려면 지시문을 사용해야 합니다. 지시문은 어떤 방식으로든 날짜 선택을 지원하는 요소를 생성합니다. 이 작업이 어떻게 수행되는지 단계별로 살펴보겠습니다. AngularJS 애플리케이션을 작성해 본 적이 있다면 인식 여부에 관계없이 지시어를 사용해 본 적이 있을 것입니다. ng-mode, ng-repeat, ng-show 등과 같은 간단한 명령을 사용해 보셨을 것입니다. 이러한 지시문은 DOM 요소에 특정 동작을 제공합니다. 예를 들어 ng-repeat는 특정 요소를 반복하고, ng-show는 조건부로 요소를 표시합니다. 요소가 끌기를 지원하도록 하려면 이를 구현하기 위한 지시어도 만들어야 합니다. 지시문의 기본 아이디어는 간단합니다. 이벤트 리스너를 요소에 바인딩하거나 DOM을 변경하여 HTML을 진정한 대화형으로 만듭니다.
jQuery 관점
jQuery를 사용하여 날짜 선택기를 만드는 방법을 상상해 보세요. 먼저 HTML에 일반 입력 상자를 추가한 다음 jQuery를 사용하여 $(element).dataPicker()를 호출하여 날짜 선택기로 변환합니다. 하지만 생각해 보세요. 디자이너가 와서 HTML 마크업을 검사할 때 이 필드가 실제로 무엇을 나타내는지 즉시 추측할 수 있습니까? 이것은 단순한 입력 상자입니까, 아니면 날짜 선택기입니까? 이를 확인하려면 jQuery 코드를 살펴봐야 합니다. Angular의 접근 방식은 지시문을 사용하여 HTML을 확장하는 것입니다. 따라서 날짜 선택 지시문의 형식은 다음과 같습니다.
<input type="text" />
또는 이:
<input type="text" />
이러한 UI 구성 요소 생성 방법이 더 직접적이고 명확합니다. 요소를 보면 이것이 무엇인지 쉽게 알 수 있습니다.
맞춤 지시문 만들기:
Angular 지시어는 다음과 같은 네 가지 표현 형식을 가질 수 있습니다. 1. 새로운 HTML 요소(bd58549bf2658b48e28c17c1c4032335208fc95a7811bc2ac6e9f365abba8b59) 2. 요소의 속성(d3299ce2158f2909f25766172d767b31) 3. CSS 클래스(88241ada37d735f02ff0ec00bad2dd58) 4. 주석(35c250549fd6f58485515c3bb70d7bbd) Of 물론 지시어가 HTML에 표시되는 방식을 제어할 수 있습니다. AngularJS에서 일반적인 지시문을 작성하는 방법을 살펴보겠습니다. 지시어 등록은 컨트롤러와 동일한 방식으로 이루어지지만 지시어 구성 속성이 포함된 간단한 객체(지시어 정의 객체)를 반환합니다. 아래 코드는 간단한 Hello World 지시문입니다.
var app = angular.module('myapp', []); app.directive('helloWorld', function() { return { restrict: 'AE', replace: 'true', template: '<h3>Hello World!!</h3>' }; });
위 코드에서 app.directive() 메소드는 모듈에 새로운 지시문을 등록합니다. 이 메소드의 첫 번째 매개변수는 지시문의 이름입니다. 두 번째 매개변수는 지시문 정의 객체를 반환하는 함수입니다. 지시문이 $rootScope, $http 또는 $compile과 같은 다른 개체나 서비스에 의존하는 경우 이때 삽입할 수 있습니다. 이 지시문은 다음과 같이 HTML의 요소로 사용됩니다.
<hello-world/> //OR <hello:world/>
또는 속성으로 사용:
<div hello-world></div> //OR <div hello:world/>
HTML5 사양을 준수하려면 요소 앞에 x- 또는 data- 접두사를 추가할 수 있습니다. 따라서 다음 태그는 helloWorld 지시문과도 일치합니다.
<div data-hello-world></div> //OR <div x-hello-world></div>
注意: 在匹配指令的时候,Angular会在元素或者属性的名字中剔除 x- 或者 data- 前缀。 然后将 – 或者 : 连接的字符串转换成驼峰(camelCase)表现形式,然后再与注册过的指令进行匹配。这是为什么,我们在HTML中以 hello-world 的方式使用 helloWorld 指令。其实,这跟HTML对标签和属性不区分大小写有关。 尽管上面的指令仅仅实现了静态文字的显示,但是这里还是有一些有趣的点值得我们去挖掘。我们在指令定义过程中使用了三个属性来配置指令。我们来一一介绍他们的作用。
restrict – 这个属性用来指定指令在HTML中如何使用(还记得之前说的,指令的四种表示方式吗)。在上面的例子中,我们使用了 ‘AE'。所以这个指令可以被当作新的HTML元素或者属性来使用。如果要允许指令被当作class来使用,我们将 restrict 设置成 ‘AEC'。
template – 这个属性规定了指令被Angular编译和链接(link)后生成的HTML标记。这个属性值不一定要是简单的字符串。template 可以非常复杂,而且经常包含其他的指令,以及表达式({{ }})等。更多的情况下你可能会见到 templateUrl, 而不是 template。所以,理想情况下,你应该将模板放到一个特定的HTML文件中,然后将 templateUrl 属性指向它。
replace – 这个属性指明生成的HTML内容是否会替换掉定义此指令的HTML元素。在我们的例子中,我们用 edd05d9b720c954eda8df66606fc7d416d95f50462131a27fde32c4d9df5bdd4的方式使用我们的指令,并且将 replace 设置成 true。所以,在指令被编译之后,生成的模板内容替换掉了 edd05d9b720c954eda8df66606fc7d416d95f50462131a27fde32c4d9df5bdd4。最终的输出是 684271ed9684bde649abda8831d4d355Hello World!!39528cedfa926ea0c01e69ef5b2ea9b0。如果你将 replace 设置成 false,也就是默认值,那么生成的模板会被插入到定义指令的元素中。
打开这个 plunker,在”Hello World!!”右键检查元素内容,来更形象地明白这些。
Link函数和Scope
指令生成出的模板其实没有太多意义,除非它在特定的scope下编译。默认情况下,指令并不会创建新的子scope。更多的,它使用父scope。也就是说,如果指令存在于一个controller下,它就会使用这个controller的scope。 如何运用scope,我们要用到一个叫做 link 的函数。它由指令定义对象中的link属性配置。让我们来改变一下我们的 helloWorld 指令,当用户在一个输入框中输入一种颜色的名称时,Hello World 文字的背景色自动发生变化。同时,当用户在 Hello World 文字上点击时,背景色变回白色。 相应的HTML标记如下:
<body ng-controller="MainCtrl"> <input type="text" ng-model="color" placeholder="Enter a color" /> <hello-world/> </body>
修改后的 helloWorld 指令如下:
app.directive('helloWorld', function() { return { restrict: 'AE', replace: true, template: '<p style="background-color:{{color}}">Hello World', 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'); }); } }; });
我们注意到指令定义中的 link 函数。 它有三个参数:
scope – 指令的scope。在我们的例子中,指令的scope就是父controller的scope。
elem – 指令的jQLite(jQuery的子集)包装DOM元素。如果你在引入AngularJS之前引入了jQuery,那么这个元素就是jQuery元素,而不是jQLite元素。由于这个元素已经被jQuery/jQLite包装了,所以我们就在进行DOM操作的时候就不需要再使用 $()来进行包装。
attr – 一个包含了指令所在元素的属性的标准化的参数对象。举个例子,你给一个HTML元素添加了一些属性:,那么可以在 link 函数中通过 attrs.someAttribute 来使用它。
link函数主要用来为DOM元素添加事件监听、监视模型属性变化、以及更新DOM。在上面的指令代码片段中,我们添加了两个事件, click,和 mouseover。click 处理函数用来重置 e388a4556c0f65e1904146cc1a846bee 的背景色,而 mouseover 处理函数改变鼠标为 pointer。在模板中有一个表达式 {{color}},当父scope中的 color 发生变化时,它用来改变 Hello World 文字的背景色。 这个 plunker 演示了这些概念。
compile函数
compile 函数在 link 函数被执行之前用来做一些DOM改造。它接收下面的参数:
tElement – 指令所在的元素
attrs – 元素上赋予的参数的标准化列表
要注意的是 compile 函数不能访问 scope,并且必须返回一个 link 函数。但是如果没有设置 compile 函数,你可以正常地配置 link 函数,(有了compile,就不能用link,link函数由compile返回)。compile函数可以写成如下的形式:
app.directive('test', function() { return { compile: function(tElem,attrs) { //do optional DOM transformation here return function(scope,elem,attrs) { //linking function here }; } }; });
大多数的情况下,你只需要使用 link 函数。这是因为大部分的指令只需要考虑注册事件监听、监视模型、以及更新DOM等,这些都可以在 link 函数中完成。 但是对于像 ng-repeat 之类的指令,需要克隆和重复 DOM 元素多次,在 link 函数执行之前由 compile 函数来完成。这就带来了一个问题,为什么我们需要两个分开的函数来完成生成过程,为什么不能只使用一个?要回答好这个问题,我们需要理解指令在Angular中是如何被编译的!
指令是如何被编译的
当应用引导启动的时候,Angular开始使用 $compile 服务遍历DOM元素。这个服务基于注册过的指令在标记文本中搜索指令。一旦所有的指令都被识别后,Angular执行他们的 compile 方法。如前面所讲的,compile 方法返回一个 link 函数,被添加到稍后执行的 link 函数列表中。这被称为编译阶段。如果一个指令需要被克隆很多次(比如 ng-repeat),compile函数只在编译阶段被执行一次,复制这些模板,但是link 函数会针对每个被复制的实例被执行。所以分开处理,让我们在性能上有一定的提高。这也说明了为什么在 compile 函数中不能访问到scope对象。 在编译阶段之后,就开始了链接(linking)阶段。在这个阶段,所有收集的 link 函数将被一一执行。指令创造出来的模板会在正确的scope下被解析和处理,然后返回具有事件响应的真实的DOM节点。
改变指令的Scope
默认情况下,指令获取它父节点的controller的scope。但这并不适用于所有情况。如果将父controller的scope暴露给指令,那么他们可以随意地修改 scope 的属性。在某些情况下,你的指令希望能够添加一些仅限内部使用的属性和方法。如果我们在父的scope中添加,会污染父scope。 其实我们还有两种选择:
一个子scope – 这个scope原型继承子父scope。
一个隔离的scope – 一个孤立存在不继承自父scope的scope。
这样的scope可以通过指令定义对象中 scope 属性来配置。下面的代码片段是一个例子:
app.directive('helloWorld', function() { return { scope: true, // use a child scope that inherits from parent restrict: 'AE', replace: 'true', template: '<h3>Hello World!!</h3>' }; });
上面的代码,让Angular给指令创建一个继承自父socpe的新的子scope。 另外一个选择,隔离的scope:
app.directive('helloWorld', function() { return { scope: {}, // use a new isolated scope restrict: 'AE', replace: 'true', template: '<h3>Hello World!!</h3>' }; });
这个指令使用了一个隔离的scope。隔离的scope在我们想要创建可重用的指令的时候是非常有好处的。通过使用隔离的scope,我们能够保证我们的指令是自包含的,可以被很容易的插入到HTML应用中。 它内部不能访问父的scope,所保证了父scope不被污染。 在我们的 helloWorld 指令例子中,如果我们将 scope 设置成 {},那么上面的代码将不会工作。 它会创建一个新的隔离的scope,那么相应的表达式 {{color}} 会指向到这个新的scope中,它的值将是 undefined. 使用隔离的scope并不意味着我们完全不能访问父scope的属性。其实有一些技术可以允许我们访问父scope的属性,甚至监视他们的变化。我们会在指令这个系列的第二部分中讨论这些技术,以及一些更高级的概念,比如 Controller 函数。 第二部分也会和你一起使用Angular指令创建一个较为丰富的记事本应用。
关于AngularJS 中的指令实践指南(一),小编就给大家介绍到这里,下面文章讲给大家介绍angularjs中的指令实践开发指南二(二),希望对大家有所帮助!