Angular2 - 指令





5.00/5 (6投票s)
什么是指令?指令是我们分离可重用功能/特性的方式。你能解释得更详细一些吗?回到用户列表,我想将名为“Tu”的用户的名字列的背景颜色设置为红色。UI如下:所以我需要更新模板(html 文件),如下:这没关系
什么是指令?
指令是我们分离可重用功能/特性的方式。
你能解释得更详细一些吗?
回到用户列表,我想将名为“Tu”的用户的名字列的背景颜色设置为红色。UI如下
所以我需要更新模板(html 文件),如下
如果只有一个用户列表,这没问题。
如果用户列表在很多地方使用,这段代码就会重复。代码将难以维护。
在这种情况下,我们将定义新项(在 Angular 中称为指令)。我们将在该指令内部检查并突出显示该列的背景。
所以 Html 将会改为
通过这种方式,任何需要根据用户的名字来突出显示背景的地方,只需像上图一样附加“hightlightBaseOnFirstName”。
将来,我们想改变它的逻辑。只需更新 hightlightBaseOnFirstName 指令。新的更改将影响所有使用此指令的地方。
看来,我们创建了许多文件只是为了替换一行 html。这有必要吗?
对于小型应用程序,只有几个页面,将来也无需扩展功能。是的,我们可能不需要为所有功能创建指令。
但对于大型应用程序,未来功能将会扩展,并且应用程序中有很多功能被重用。将可重用功能/特性分离到独立的组件/指令中非常重要。
这避免了代码重复,并且易于维护代码。许多企业应用程序组织不佳,添加新功能成本高昂。
指令有多少种类型?
目前有 3 种类型的指令。它们是
- 属性指令
- 结构指令
- 组件(带有模板的指令)
属性指令是什么样的?
我假设我的属性指令名为“hightlightBaseOnFirstName”,我们将它用作 html 元素的属性
要声明这种类型的指令,我们需要创建 hightlightBaseOnFirstName.ts 文件,如下所示
@Directive({ selector: "[hightlightBaseOnFirstName]" }) export class HightlightBaseOnFirstName{ constructor(){ console.log("this is HightlightBaseOnFirstName directive"); } }

什么是 @Directive?
这是一个 Angular 装饰器。我们使用这个装饰器来定义一个指令(与 @Component 含义相同)。
我们需要注意“selector”属性。它采用“[name-of-directive]”模式(“[”和“]”是必需的)。
Selector 声明将在应用程序中引用的指令名称。
在上面的示例中,我们可以通过使用“hightlightBaseOnFirstName”来使用我们的高亮指令,例如
<div hightlightBaseOnFirstName ><div>
或
<p hightlightBaseOnFirstName></p>
我一直按照本文中的说明操作,但我的指令不起作用?
到目前为止,我们只声明了一个名为“hightlightBaseOnFirstName”的新指令,并在 users.html 模板中使用它。
在编译时,Angular 将“hightlightBaseOnFirstName”理解为 html 标签的普通属性。因此,我们需要将此指令注册到 Angular。这样,“hightlightBaseOnFirstName”将被视为指令名称,而不是普通的 html 属性。
转到 userModules.ts 文件,添加此代码
让我们尝试编译并刷新浏览器,我们将看到“this is HightlightBaseOnFirstName directive”被打印到浏览器的控制台
如何获取我的指令当前附加到的 DOM 对象的引用?
大多数情况下,我们想对我们的指令当前附加到的 DOM 元素执行一些操作,例如:添加事件监听器、更改标记等。
让我们将指令的构造函数更改为
constructor(ui: ElementRef){ console.log(ui); }
在运行时,Angular 注入了我们的指令当前附加到的对象的引用。
运行应用程序,我们从浏览器的控制台接收到如下内容
我们还可以通过以下方式更新此 DOM 元素的背景
constructor(ui: ElementRef) { ui.nativeElement.style.backgroundColor = "red"; }
好的,酷。下一个问题是:如何向我的指令传递参数?
我们的指令可以从其他组件(例如:Users 组件,或在其他指令中)调用。因此,我们的指令可以从这些组件接收参数(输入参数),并根据这些输入参数确定其反应。
让我们再次回顾一下我们的“hightlightBaseOnFirstName”指令。我将添加“color”输入参数。该值将用于设置列表中相应行/用户的背景。
hightlightBaseOnFirstName.ts 将更改如下
@Directive({ selector: "[hightlightBaseOnFirstName]" }) export class HightlightBaseOnFirstName implements AfterViewChecked { @Input() color: string = "white"; ngAfterViewChecked(){ console.log(this.color); } }
在此类中,我们添加了一个名为“color”的新 @Input 参数。因此,其他组件/指令使用“hightlightBaseOnFirstName”指令可以通过此属性传递适当的颜色。
什么是“ngAfterViewChecked”?
这是 Angular 组件/指令的事件,……这让我们可以在事件发生时指定我们的指令想要做什么。
目前请只关注指令,我们将在其他文章中更详细地讨论这些事件(或生命周期钩子)。
同意,如何为“hightlightBaseOnFirstName”指令的颜色输入属性传递值?
我们需要在 users.html 中更新如下
<td hightlightBaseOnFirstName [color]="user.color">{{user.firstName}}</td>
在此 html 中,我们添加了新的“[color]='user.color'”,这会将“hightlightBaseOnFirstName”指令的颜色设置为每个用户的“color”属性。
再次运行应用程序,我们将看到颜色已打印到控制台,如下所示
我明白了,你能完成“hightlightBaseOnFirstName”指令吗?只需接收颜色并相应地设置背景?
“hightlightBaseOnFirstName”指令的完整代码如下
@Directive({ selector: "[hightlightBaseOnFirstName]" }) export class HightlightBaseOnFirstName implements AfterViewChecked { @Input() color: string = "white"; private dom: any = null; constructor(ui: ElementRef) { this.dom = ui.nativeElement; } ngAfterViewChecked() { let firstName = this.dom.innerText; if (firstName.indexOf("Tu") < 0) { return; } this.dom.style.backgroundColor = this.color; } }
请注意以下几点
- 我们获取了指令所附加的 DOM 对象的引用,并将此引用保存在私有属性(名为 ui)中。
- 在 ngAfterViewChecked 中,我们检查逻辑并更新当前 DOM 对象的 backgroundColor 属性的适当值。
结果如下
在这张照片中我们看到,第四个项目(techcoaching)的默认背景颜色(白色),因为它的名字不是以“Tu”开头的。
如何向我的指令传递多个参数?
让我们更新我们的指令,它将接收另一个名为“text”的参数,因此 HightlightBaseOnFirstName 类的代码将更改如下
export class HightlightBaseOnFirstName implements AfterViewChecked { @Input() color: string = "white"; @Input() text: string = "Tu"; private dom: any = null; constructor(ui: ElementRef) { this.dom = ui.nativeElement; } ngAfterViewChecked() { let firstName = this.dom.innerText; if (firstName.indexOf(this.text) < 0) { return; } this.dom.style.backgroundColor = this.color; } }
在这段代码中,我们添加了一个新的“text”参数,也被声明为 @Input。users.html 也需要更新
<td hightlightBaseOnFirstName [text]="'Tu'" [color]="user.color">{{user.firstName}}</td>
我们将新的“[text]=
好的,明白了。上面的示例中还有一个问题。在 hightlightBaseOnFirstName 指令中,color 和 text 都是文本并标记为 @Input 字符串。在标记中,color 是 [color]="user.color",但 text 是 [text]="'TU'"。为什么我们对 text 属性有双引号外加单引号?
这是一个非常好的问题。在 Angular 中,绑定文本值时,我们应该将其放在 ''(单引号)中。否则,Angular 将理解 ""(双引号)中的值是一个变量。应用程序可能无法按预期运行
<td hightlightBaseOnFirstName [text]="Tu" [color]="user.color">{{user.firstName}}</td>
在运行时,映射到指令 text 属性的值是“undefined”,因为“Tu”是未知变量
如果我们在 Users 类 (users.ts) 中定义了变量
export class Users { public Tu:string="Tran Thanh Tu"; .... }
再次运行应用程序,输出已更改
我们有这种情况,因为“Tu”被理解为 Users 类(users.ts 和 users.html)上下文中的变量。
第二种情况,在“”中使用多个词
Angular 将在浏览器控制台中引发错误,因为带有空格的变量名无效
为了解决这个问题,只需将此文本添加到单引号中。它会很好地工作
为了将字符串值绑定到 text 属性。我可以使用 '"Tu Tran"'(单引号在双引号之外)而不是 "'Tu Tran'"(双引号在单引号之外)吗?
当然可以,"(双引号)和 '(单引号)可以互换。
记住单引号和双引号很麻烦。有没有其他解决方案可以让开发者摆脱这种困境?
是的,我们有。字符串属性还有另一个不错的解决方案。而不是使用
<td hightlightBaseOnFirstName [text]="'Tu Tran'"></td>
我们可以使用
<td hightlightBaseOnFirstName text="Tu Tran"></td>
在这种情况下,我们从 **[text]="'Tu Tran'"** 更改为 **text="Tu Tran"**。Angular 会将值视为字符串,即使它在上下文中是一个变量。请看下面的代码
<td hightlightBaseOnFirstName text="user.color">{{user.firstName}}</td>
Angular 仍然将 user.color 理解为字符串而不是绑定变量
我们需要将 user.color 放在 "{{" 和 "}}" 中(这意味着 {{user.color}})才能解决问题。
在上面的示例中,我们需要编写很多代码(<td hightlightBaseOnFirstName [color]="user.color"></td>)来设置背景颜色,我们可以让它更短吗?
是的,Angular 中还有另一个概念,它是 @Input 别名。我们将把 HightlightBaseOnFirstName 类更改为
export class HightlightBaseOnFirstName implements AfterViewChecked { @Input("hightlightBaseOnFirstName") color: string = "white"; /* the rest of code was not changed*/ }
并修改 users.html 为
<td [hightlightBaseOnFirstName]="user.color"></td>
应用程序的输出是相同的
我的指令如何向外部发布更改?
- 我们需要这种情况来通知调用者(使用我们的指令)一个预期的事件。我们需要遵循以下顺序
- 使用 @Output 和 EventEmitter 类型声明该属性。例如 @Output output: EventEmitter<bool> = new EventEmitter())。
- 通过调用 EventEmitter 实例的 emit 方法发布更改。例如:outout.emit(true);
- 通过 (<输出变量名>)=callback($event) 使用该输出属性,当指令调用该输出属性的 emit 函数时,将调用此回调函数。
- 您的逻辑将在上述回调函数中实现。
很难理解你上面的回答。你能提供一个具体的例子吗?
好的,让我们更新一下我们的示例。
在 HightlightBaseOnFirstName 类中,我们添加了一个新的输出属性
export class HightlightBaseOnFirstName implements AfterViewChecked { @Output() isCustombackground: EventEmitter<boolean> = new EventEmitter(); /*The remain code is the same */ }
并更新 ngAfterViewChecked 函数如下
ngAfterViewChecked() { let firstName = this.dom.innerText; if (firstName.indexOf(this.text) < 0) { return; } this.dom.style.backgroundColor = this.color; this.isCustombackground.emit(true); }
在我们的指令中,如果附加 DOM 的名字以“TU”开头,isCustombackground 将以 true 触发。
在 users.html 中,将输出属性映射到回调函数
<td hightlightBaseOnFirstName (isCustombackground)="onBackgroundChanged(user, $event)">{{user.firstName}}</td>
我们看到 isCustombackground 输出属性以“(<属性名>)”模式调用。它与 @Input 属性不同。
在这种情况下,onBackgroundChanged 函数将以 2 个参数调用(第一个是当前用户,第二个是当前上下文中的 $event)。$event 是在指令内部 emit 函数中传递的值。
转到 Users 类并添加 onBackgroundChanged 函数的逻辑,如下所示
public onBackgroundChanged(user: any, isCustom: boolean) { user.isCustomBackground=isCustom; }
逻辑相当简单,只需将当前用户的 isCustomBackground 属性设置为 emit 函数中传递的适当值。
让我们编译并运行应用程序。
点击 #1(“Tu Tran”用户),我们看到以下结果
点击 #4(“Tech Coaching”用户),结果如下
明白了,我们写了许多代码只是为了更新用户的 'isCustomBackground' 属性。有没有其他更好的解决方案?
当然,输出属性有约定。新值将直接更新到映射属性。
让我们更新上面 isCustombackground 输出属性的示例代码。将 HightlightBaseOnFirstName 类更改为
export class HightlightBaseOnFirstName implements AfterViewChecked { @Input() color: string = "white"; @Input() text: string = "Tu"; @Input() isCustombackground: boolean; @Output() isCustombackgroundChange: EventEmitter<boolean> = new EventEmitter(); private dom: any = null; constructor(ui: ElementRef) { this.dom = ui.nativeElement; } ngAfterViewChecked() { let firstName = this.dom.innerText; if (firstName.indexOf(this.text) < 0) { return; } this.dom.style.backgroundColor = this.color; this.isCustombackgroundChange.emit(true); } }
在这个代码中
- 我们添加了一个名为 isCustombackground 的新 @Input 参数,类型为 boolean。
- 我们还添加了一个名为 isCustombackgroundChange 的新 @Output 参数,类型为 EventEmitter<boolean>。
对此有一个约定:@Output 参数 = @Input 参数 + "Change"。
我们还需要更新 users.html
<td hightlightBaseOnFirstName [(isCustombackground)]="user.isCustomBackground">{{user.firstName}}</td>
我们使用不同语法的 isCustombackground 属性:**[(<输入属性名称>)]**
在这种情况下,我们不需要像以前那样在 Users 类中定义任何回调函数。只需删除上一个示例中 Users 类中的 onBackgroundChanged 函数即可。
让我们编译并清除浏览器的缓存。只需确保您的 html 文件和 js 文件不是过时的即可。结果与上面的示例相同
我看到我们有两种解决方案来完成相同的事情(更新相应用户的 isCustomBackground 属性)。我需要知道它们两者吗?
是的,你应该知道。
对于第一种,我们将在向指令外部触发事件(例如:按钮点击)时使用。因此,父级(使用我们指令的组件/指令)将决定如何响应指定的事件。
对于第二种情况,设想一下,用户对象有 10 个属性需要从 HightlightBaseOnFirstName 指令更新,类似于 isCustomBackground 属性。按照第一种方法,我们需要定义一个回调函数,只做“user.isCustomBackground = isCustom;”这样简单的事情。这会使代码不必要地复杂。因此,第二种方法是解决这种情况的正确方案。
CodeProject感谢阅读。