属性型指令会修改元素、组件或其他指令的行为。它的名字反映了该指令的应用方式:作为宿主元素的一个属性。
如果你要试验本指南中所讲的应用,请在浏览器中运行它或下载并在本地运行它。
本范例应用的 HighlightDirective
会根据数据绑定中的颜色或默认颜色(浅灰)来设置元素的背景色。它还会把该元素的自定义属性(customProperty
)设置为 true
,当然这除了示范本技术之外别无它用。
import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
@Directive({ selector: '[highlight]' })
/**
* Set backgroundColor for the attached element to highlight color
* and set the element's customProperty to true
*/
export class HighlightDirective implements OnChanges {
defaultColor = 'rgb(211, 211, 211)'; // lightgray
@Input('highlight') bgColor = '';
constructor(private el: ElementRef) {
el.nativeElement.style.customProperty = true;
}
ngOnChanges() {
this.el.nativeElement.style.backgroundColor = this.bgColor || this.defaultColor;
}
}
它在整个应用中都用到过,也许最简单的是在 AboutComponent
中:
import { Component } from '@angular/core';
@Component({
template: `
<h2 highlight="skyblue">About</h2>
<h3>Quote of the day:</h3>
<twain-quote></twain-quote>
`
})
export class AboutComponent { }
要想在 AboutComponent
中测试 HighlightDirective
的特定用法,只需要浏览组件测试场景中的“嵌套组件测试”一节中提到的各种技巧。
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ AboutComponent, HighlightDirective ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
.createComponent(AboutComponent);
fixture.detectChanges(); // initial binding
});
it('should have skyblue <h2>', () => {
const h2: HTMLElement = fixture.nativeElement.querySelector('h2');
const bgColor = h2.style.backgroundColor;
expect(bgColor).toBe('skyblue');
});
但是,测试单个用例不太可能涉及指令的全部能力。要找到并测试那些使用了该指令的所有组件会很乏味、很脆弱,而且几乎不可能做到完全覆盖。
纯类测试可能会有一点帮助,但像这种属性型指令往往会操纵 DOM。孤立的单元测试不会触及 DOM,因此也无法给人带来对指令功效的信心。
更好的解决方案是创建一个人工测试组件来演示应用该指令的所有方法。
@Component({
template: `
<h2 highlight="yellow">Something Yellow</h2>
<h2 highlight>The Default (Gray)</h2>
<h2>No Highlight</h2>
<input #box [highlight]="box.value" value="cyan"/>`
})
class TestComponent { }
这个 <input>
用例将 HighlightDirective
绑定到输入框中颜色值的名称。初始值是单词“cyan”,应该把它设为输入框的背景颜色。
下面是对该组件的一些测试:
beforeEach(() => {
fixture = TestBed.configureTestingModule({
declarations: [ HighlightDirective, TestComponent ]
})
.createComponent(TestComponent);
fixture.detectChanges(); // initial binding
// all elements with an attached HighlightDirective
des = fixture.debugElement.queryAll(By.directive(HighlightDirective));
// the h2 without the HighlightDirective
bareH2 = fixture.debugElement.query(By.css('h2:not([highlight])'));
});
// color tests
it('should have three highlighted elements', () => {
expect(des.length).toBe(3);
});
it('should color 1st <h2> background "yellow"', () => {
const bgColor = des[0].nativeElement.style.backgroundColor;
expect(bgColor).toBe('yellow');
});
it('should color 2nd <h2> background w/ default color', () => {
const dir = des[1].injector.get(HighlightDirective) as HighlightDirective;
const bgColor = des[1].nativeElement.style.backgroundColor;
expect(bgColor).toBe(dir.defaultColor);
});
it('should bind <input> background to value color', () => {
// easier to work with nativeElement
const input = des[2].nativeElement as HTMLInputElement;
expect(input.style.backgroundColor)
.withContext('initial backgroundColor')
.toBe('cyan');
input.value = 'green';
// Dispatch a DOM event so that Angular responds to the input value change.
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(input.style.backgroundColor)
.withContext('changed backgroundColor')
.toBe('green');
});
it('bare <h2> should not have a customProperty', () => {
expect(bareH2.properties['customProperty']).toBeUndefined();
});
一些技巧值得注意:
By.directive
谓词是一种获取那些不知道类型但都附有本指令的元素的好办法。By.css('h2:not([highlight])')
中的 :not伪类可以帮助你找到那些没有该指令的 <h2>
元素。By.css('*:not([highlight])')
可以找到没有该指令的任意元素。DebugElement.styles
提供了对元素样式的访问,即使没有真正的浏览器也是如此,这要归功于 DebugElement
提供的抽象。但是,如果 nativeElement
显得比使用其抽象版本更容易或更清晰,那就把它暴露出来。<h2>
上的注入器来获取它的 HighlightDirective
实例及其 defaultColor
。DebugElement.properties
允许访问本指令设置的自定义属性。