属性指令改变DOM元素的外观或行为。
Angular有三种指令:
组件是三个指令中最常见的。 您在Starter App中看到了一个简单的组件。
结构指令改变了视图的结构。 两个例子是NgFor和NgIf。 在“结构指令”页面中了解它们。
属性指令被用作元素的属性。 例如,“模板语法”页面中的内置NgStyle指令可以同时更改多个元素样式。
属性指令有两种:
创建一个基于类的属性指令需要编写一个用@Directive()注解的控制器类,它指定标识属性的选择器。控制器类实现指令所需的行为。
本页演示了如何构建一个简单的myHighlight属性指令当用户悬停在那个元素上时来设置元素的背景颜色 你可以像这样应用它:
<p myHighlight>Highlight me!</p>
按照设置说明创建名为attribute_directives的新本地项目。
在指定的文件夹中创建以下源文件:lib/src/highlight_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
@Directive(selector: '[myHighlight]')
class HighlightDirective {
HighlightDirective(Element el) {
el.style.backgroundColor = 'yellow';
}
}
@Directive()需要一个CSS选择器来标识与该指令相关联的模板中的HTML。属性的CSS选择器是方括号中的属性名称。这里指令的选择器是[myHighlight]。 Angular定位模板中具有名为myHighlight的属性的所有元素。
为什么不叫它“highlight”? 虽然highlight是比myHighlight更简洁的名字,并会工作,最佳做法是为选择器名称加上前缀,以确保它们不与标准HTML属性发生冲突。这也降低了与第三方指令名称相冲突的风险。 请确保您不要对highlight指令名称使用ng前缀,因为该前缀是为Angular保留的,并且使用它可能会导致难以诊断的错误。对于简单的演示,简短的前缀my可以帮助区分您的自定义指令。
在@Directive()元数据之后是指令的控制器类,称为HighlightDirective,它包含指令的逻辑。
Angular为每个匹配元素创建一个指令控制器类的新实例,将HTML元素注入到构造函数中。
要使用新的HighlightDirective,请创建一个将该指令作为属性应用于段落(<p>)元素的模板。 对Angular来说,<p>元素是属性宿主。
将模板放在它自己的app_component.html文件中,如下所示:lib/app_component.html
<h1>My First Attribute Directive</h1>
<p myHighlight>Highlight me!</p>
现在在AppComponent中引用此模板,并将Highlight指令添加到指令列表中。 当Angular在模板中遇到myHighlight时,就会识别该指令。
import 'package:angular/angular.dart';
import 'src/auto_id_directive.dart';
import 'src/highlight_directive.dart';
@Component(
selector: 'my-app',
templateUrl: 'app_component.html',
directives: const [autoIdDirective, HighlightDirective],
)
class AppComponent {
String color;
}
刷新浏览器。 应用程序运行,myHighlight指令突出显示段落文本。
你记得设置@Component的指令属性吗?很容易忘记! 在浏览器工具中打开控制台,找到如下错误:
EXCEPTION: Template parse errors:
Can't bind to 'myHighlight' since it isn't a known property of 'p'.
Angular检测到你正试图绑定到某个东西,但是它找不到这个指令。 您可以通过在directives列表中列出HighlightDirective让Angular知道。
总而言之,Angular在<p>元素上找到了myHighlight属性。它创建了一个HighlightDirective类的实例,并将<p>元素的引用注入到指令的构造函数中,该构造函数将<p>元素的背景样式设置为黄色。
目前,myHighlight只是设置一个元素的颜色。 该指令可能更具动态性。 它可以检测到用户将鼠标移入或移出元素,并通过设置或清除高亮颜色来进行响应。
添加两个事件处理程序,当鼠标进入或离开时进行响应,每个都由HostListener注解装饰。
@HostListener('mouseenter')
void onMouseEnter() {
_highlight('yellow');
}
@HostListener('mouseleave')
void onMouseLeave() {
_highlight();
}
void _highlight([String color]) {
_el.style.backgroundColor = color;
}
@HostListener注解允许您订阅托管属性指令的宿主DOM元素的事件,在这种情况下是<p>。
当然,你可以用标准的JavaScript访问DOM,并手动添加事件监听器。 这种方法至少有三个问题:
处理程序委托给一个帮助器方法,该方法设置DOM元素_el的颜色,在构造函数中声明并初始化它。
lib/src/highlight_directive.dart (constructor)
final Element _el;
HighlightDirective(this._el);
以下是更新后的指令:lib/src/highlight_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
@Directive(selector: '[myHighlight]')
class HighlightDirective {
final Element _el;
HighlightDirective(this._el);
@HostListener('mouseenter')
void onMouseEnter() {
_highlight('yellow');
}
@HostListener('mouseleave')
void onMouseLeave() {
_highlight();
}
void _highlight([String color]) {
_el.style.backgroundColor = color;
}
}
刷新浏览器。 确认当鼠标悬停在p上时出现背景颜色,并在移出时消失。
目前,高亮颜色在指令中被硬编码。 这是不灵活的。 在本节中,您将为开发人员提供在应用指令时设置突出显示颜色的能力。
开始通过像这样的指令类添加一个highlightColor属性:lib/src/highlight_directive.dart (highlightColor)
@Input()
String highlightColor;
注意@Input注解。 它将元数据添加到使指令的highlightColor属性可用于绑定的类。
它被称为输入属性,因为数据从绑定表达式流入指令。 没有这个输入元数据,Angular拒绝绑定; 请参阅下面的更多关于这一点。
尝试通过向AppComponent模板添加以下指令绑定变量:lib/app_component.html (excerpt)
<p myHighlight highlightColor="yellow">Highlighted in yellow</p>
<p myHighlight [highlightColor]="'orange'">Highlighted in orange</p>
将color属性添加到AppComponent。lib/app_component.dart (class)
class AppComponent {
String color = 'yellow';
}
让它用一个属性绑定来控制高亮颜色。lib/app_component.html (excerpt)
<p myHighlight [highlightColor]="color">Highlighted with parent component's color</p>
这很好,但同时应用指令并将颜色设置为相同的属性会很好。
<p [myHighlight]="color">Highlight me!</p>
[myHighlight]属性绑定都将highlighting 显示指令应用于<p>元素,并使用属性绑定来设置指令的突出显示颜色。您正在重新使用该指令的属性选择器([myHighlight])来执行这两个任务。 这是一个清晰,紧凑的语法。
您必须将指令的highlightColor属性重命名为myHighlight,因为这是现在的颜色属性绑定名称。
lib/src/highlight_directive.dart (renamed to match directive selector)
@Input()
String myHighlight;
这是不愉快的。 myHighlight这个词是一个可怕的财产名称,它并不表达财产的意图。
幸运的是,您可以根据需要命名指令属性,并将其别名用于绑定目的。
还原原始属性名称,并将选择器指定为@Input参数中的别名。
lib/src/highlight_directive.dart (color property with alias)
@Input('myHighlight')
String highlightColor;
该指令内的属性被称为highlightColor。 在指令之外,绑定到它的地方,它被称为myHighlight。
您可以得到两全其美的效果:所需的属性名称和所需的绑定语法:
<p [myHighlight]="color">Highlight me!</p>
现在你已经绑定了highlightColor,修改了onMouseEnter()方法来使用它。如果有人忽略绑定到highlightColor,以红色突出显示:lib/src/highlight_directive.dart (mouse enter)
@HostListener('mouseenter')
void onMouseEnter() => _highlight(highlightColor ?? 'red');
这是指令类的最新版本。
lib/src/highlight_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
@Directive(selector: '[myHighlight]')
class HighlightDirective {
final Element _el;
HighlightDirective(this._el);
@Input('myHighlight')
String highlightColor;
@HostListener('mouseenter')
void onMouseEnter() => _highlight(highlightColor ?? 'red');
@HostListener('mouseleave')
void onMouseLeave() => _highlight();
void _highlight([String color]) {
_el.style.backgroundColor = color;
}
}
可能很难想象这个指令是如何工作的。在本节中,您将把AppComponent转换为一个线束,让您用单选按钮选取高亮颜色,并将您的颜色选择绑定到指令。
更新app_component.html,如下所示:
<h1>My First Attribute Directive</h1>
<h4>Pick a highlight color</h4>
<div>
<input type="radio" name="colors" (click)="color='lightgreen'">Green
<input type="radio" name="colors" (click)="color='yellow'">Yellow
<input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [myHighlight]="color">Highlight me!</p>
修改AppComponent.color,使其没有初始值。
class AppComponent {
String color;
}
刷新浏览器。 这是执行中的线束和指令。
这个highlight指令有一个可定制的属性。 在一个真正的应用程序,它可能需要更多。
目前,默认的颜色 - 直到用户选择高亮颜色为止的颜色 - 被硬编码为“red”。 让模板开发人员设置默认颜色。
将第二个输入属性添加到HighlightDirective,名为defaultColor:lib/src/highlight_directive.dart (defaultColor)
@Input()
String defaultColor;
修改指令的onMouseEnter,使其首先尝试用highlightColor高亮显示,然后用defaultColor,如果两个属性都是未定义的,则回退到“红色”。
@HostListener('mouseenter')
void onMouseEnter() => _highlight(highlightColor ?? defaultColor ?? 'red');
当您已经绑定到myHighlight属性名称时,如何绑定到第二个属性?
与组件一样,您可以根据需要添加尽可能多的指令属性绑定,方法是在模板中将它们串起来。 开发人员应该能够编写下面的模板HTML绑定到AppComponent.color并回退到“violet”作为默认颜色。
<p [myHighlight]="color" defaultColor="violet">
Highlight me too!
</p>
Angular知道defaultColor绑定属于HighlightDirective,因为您使用@Input注解将其公开。
刷新浏览器。 编码完成后,下方演示应该如何工作。
一个函数指令是一个无状态的指令。 您可以通过使用@Directive()注解一个公共的顶级函数来创建一个函数指令。
创建以下功能属性指令:lib/src/auto_id_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
int _idCounter = 0;
@Directive(selector: '[autoId]')
void autoIdDirective(
Element el,
@Attribute('autoId') String prefix,
) {
el.id = '$prefix${_idCounter++}';
}
像基于类的指令中的构造函数参数一样,函数参数定义了函数指令的依赖关系。
当您编写功能指令时,请遵循以下规则:
虽然函数指令是无状态的,但它们可能是不纯的(利用全局状态),正如autoId指令所示。
在应用程序组件模板的末尾添加以下行:lib/app_component.html (autoId)
<h4 #h1 autoId="heading-">Auto-ID at work</h4>
<p>The previous heading has ID {{h1.id}}</p>
<h4 #h2 autoId="heading-">Auto-ID at work, again</h4>
<p>The previous heading has ID {{h2.id}}</p>
刷新浏览器。 该应用报告标题ID heading-0 和 heading-1。
该页面介绍了如何:
最终的源代码如下:
lib/app_component.dart
import 'package:angular/angular.dart';
import 'src/auto_id_directive.dart';
import 'src/highlight_directive.dart';
@Component(
selector: 'my-app',
templateUrl: 'app_component.html',
directives: const [autoIdDirective, HighlightDirective],
)
class AppComponent {
String color;
}
lib/app_component.html
<h1>My First Attribute Directive</h1>
<h4>Pick a highlight color</h4>
<div>
<input type="radio" name="colors" (click)="color='lightgreen'">Green
<input type="radio" name="colors" (click)="color='yellow'">Yellow
<input type="radio" name="colors" (click)="color='cyan'">Cyan
</div>
<p [myHighlight]="color">Highlight me!</p>
<p [myHighlight]="color" defaultColor="violet">
Highlight me too!
</p>
<hr>
<h4 #h1 autoId="heading-">Auto-ID at work</h4>
<p>The previous heading has ID {{h1.id}}</p>
<h4 #h2 autoId="heading-">Auto-ID at work, again</h4>
<p>The previous heading has ID {{h2.id}}</p>
lib/src/auto_id_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
int _idCounter = 0;
@Directive(selector: '[autoId]')
void autoIdDirective(
Element el,
@Attribute('autoId') String prefix,
) {
el.id = '$prefix${_idCounter++}';
}
lib/src/highlight_directive.dart
import 'dart:html';
import 'package:angular/angular.dart';
@Directive(selector: '[myHighlight]')
class HighlightDirective {
final Element _el;
HighlightDirective(this._el);
@Input()
String defaultColor;
@Input('myHighlight')
String highlightColor;
@HostListener('mouseenter')
void onMouseEnter() => _highlight(highlightColor ?? defaultColor ?? 'red');
@HostListener('mouseleave')
void onMouseLeave() => _highlight();
void _highlight([String color]) {
_el.style.backgroundColor = color;
}
}
web/main.dart
import 'package:angular/angular.dart';
import 'package:attribute_directives/app_component.dart';
void main() {
bootstrap(AppComponent);
}
web/index.html
<!DOCTYPE html>
<html>
<head>
<script>
// WARNING: DO NOT set the <base href> like this in production!
// Details: https://webdev.dartlang.org/angular/guide/router
(function () {
var m = document.location.pathname.match(/^(\/[-\w]+)+\/web($|\/)/);
document.write('<base href="' + (m ? m[0] : '/') + '" />');
}());
</script>
<title>Attribute Directives</title>
<link rel="stylesheet" href="styles.css">
<link rel="icon" type="image/png" href="favicon.png">
<script defer src="main.dart" type="application/dart"></script>
<script defer src="packages/browser/dart.js"></script>
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>
在这个演示中,hightlightColor属性是HighlightDirective的输入属性。 你已经看到它没有使用别名:
@Input()
String highlightColor;
它使用别名:
@Input('myHighlight')
String highlightColor;
无论哪种方式,@Input注解告诉Angular这个属性是由父组件公开的,并可以进行绑定。没有@Input,Angular拒绝绑定到属性。
您之前已将模板HTML绑定到组件属性,并且从未使用@Input。 有什么不同?
差别是一个信任的问题。 Angular将组件的模板视为属于组件。组件和它的模板隐式互相信任。因此,组件自己的模板可以绑定到该组件的任何属性,无论有没有@Input注解。
但是组件或指令不应该盲目地信任其他组件和指令。 默认情况下,组件或指令的属性是隐式绑定的。从Angular绑定角度来看,它们是私密的。当用@Input注解装饰时,该属性从Angular绑定的角度变成公共的。只有这样它才能受到其他组件或指令的绑定。
您可以通过绑定中属性名称的位置来判断是否需要@Input。
现在将该推理应用于以下示例:
<p [myHighlight]="color">Highlight me!</p>