Angular 2内置指令涵盖了广泛的功能,但有时创建我们自己的指令将带来更优雅的解决方案。
创建属性指令
让我们从一个简单的按钮开始,让用户跳转到不同的页面。
@Component({
selector: 'app-visit-rangle',
template: `
<button
type="button"
(click)="visitRangle()">
Visit Rangle
</button>
`
})
export class VisitRangleComponent {
visitRangle() {
location.href = 'https://rangle.io';
}
}
我们有礼貌,所以不是仅仅将用户发送到新页面,而是通过创建属性指令并将其附加到按钮,来询问他们是否确定跳转。
@Directive({
selector: `[appConfirm]`
})
export class ConfirmDirective {
@HostListener('click', ['$event'])
confirmFirst(event: Event) {
return window.confirm('Are you sure you want to do this?');
}
}
通过在类上使用@Directive
装饰器并指定选择器来创建指令。对于指令,选择器名称必须为camelCase,并用方括号括起来,以指定它是属性绑定。我们使用@HostListener
装饰器来监听它所附加的组件或元素上的事件。 在这种情况下,我们正在观察点击事件并传递$event
关键字给出的事件详细信息。 接下来,我们要将此指令附加到我们之前创建的按钮。
template: `
<button
type="button"
(click)="visitRangle()"
appConfirm>
Visit Rangle
</button>
`
然而,请注意,该按钮不能按预期工作。这是因为当我们监听点击事件并显示确认对话框时,组件的点击处理程序在指令的点击处理程序之前运行,两者之间没有通信。为此,我们需要重写我们的指令以使用组件的点击处理程序。
@Directive({
selector: `[appConfirm]`
})
export class ConfirmDirective {
@Input() appConfirm = () => {};
@HostListener('click', ['$event'])
confirmFirst() {
const confirmed = window.confirm('Are you sure you want to do this?');
if(confirmed) {
this.appConfirm();
}
}
}
在这里,我们想要指定在发送确认对话框后需要执行什么操作,为此,我们创建一个类似于组件上的输入绑定。 我们将使用我们的指令名称来进行这个绑定,我们的组件代码的变化如下:
<button
type="button"
[appConfirm]="visitRangle">
Visit Rangle
</button>
现在我们的按钮按预期工作了。 我们可能希望能够自定义确认对话框的消息。 为此,我们将使用另一个绑定。
@Directive({
selector: `[appConfirm]`
})
export class ConfirmDirective {
@Input() appConfirm = () => {};
@Input() confirmMessage = 'Are you sure you want to do this?';
@HostListener('click', ['$event'])
confirmFirst() {
const confirmed = window.confirm(this.confirmMessage);
if(confirmed) {
this.appConfirm();
}
}
}
我们的指令获得一个新的输入属性,它表示确认对话框消息,我们传递给window.confirm
调用。要利用这个新的输入属性,我们添加另一个绑定到我们的按钮。
<button
type="button"
[appConfirm]="visitRangle"
confirmMessage="Click ok to visit Rangle.io!">
Visit Rangle
</button>
现在我们有一个可以在跳转到一个新页面之前自定义确认消息的按钮。
监听Host元素
监听Host元素 - 也就是指令附加的DOM元素 - 是指令扩展组件或元素行为的主要方法。 以前,我们看到了它的常见用例。
@Directive({
selector: '[appMyDirective]'
})
class MyDirective {
@HostListener('click', ['$event'])
onClick() {}
}
我们还可以通过在侦听器中添加目标来响应外部事件,例如从window
或 document
。
@Directive({
selector: `[appHighlight]`
})
export class HighlightDirective {
constructor(private el: ElementRef, private renderer: Renderer) { }
@HostListener('document:click', ['$event'])
handleClick(event: Event) {
if (this.el.nativeElement.contains(event.target)) {
this.highlight('yellow');
} else {
this.highlight(null);
}
}
highlight(color) {
this.renderer.setElementStyle(this.el.nativeElement, 'backgroundColor', color);
}
}
虽然不太常见,我们也可以使用
@HostListener
,如果要在组件的Host 元素上注册侦听器的话。
Host元素
Host元素的概念适用于指令和组件。
对于一个指令,这个概念是相当简单的。 将您的指令属性放在哪个模板标记被认为是Host元素。如果我们像上面这样实现HighlightDirective
:
<div>
<p highlight>
<span>Text to be highlighted</span>
</p>
</div>
``标记将被视为Host元素。 如果我们使用自定义TextBox组件作为Host,代码将如下所示:
<div>
<my-text-box highlight>
<span>Text to be highlighted</span>
</my-text-box>
</div>
在组件的上下文中,Host元素是您通过组件配置中的选择器字符串创建的标记。 对于上面示例中的TextBoxComponent
,组件类的上下文中的Host元素将是``标记。
使用指令设置属性
我们可以使用属性指令通过@HostBinding
装饰器来影响Host节点上的属性的值。
@HostBinding
装饰器允许我们在指令的host元素上编程设置属性值。 它类似于模板中定义的属性绑定,除了它专门定位到host元素。 对每个变化检测周期检查绑定,因此如果需要,它可以动态地改变。
例如,假设我们要为按钮创建一个指令,当我们按下它时动态添加一个类。 这可能看起来像:
import { Directive, HostBinding, HostListener } from '@angular/core';
@Directive({
selector: '[appButtonPress]'
})
export class ButtonPressDirective {
@HostBinding('attr.role') role = 'button';
@HostBinding('class.pressed') isPressed: boolean;
@HostListener('mousedown') hasPressed() {
this.isPressed = true;
}
@HostListener('mouseup') hasReleased() {
this.isPressed = false;
}
}
注意,对于@HostBinding
的两个用例,我们传递一个字符串值到我们想要改变的属性。如果我们不向装饰器提供字符串,那么将使用类成员的名称。
在第一个@HostBinding
中,我们静态地将角色属性设置为button
。对于第二个示例,当isPressed
为true
时,将应用pressed
类。
提示:尽管不太常见,如果需要,@HostBinding也可以应用于组件。