65.9K
CodeProject 正在变化。 阅读更多。
Home

Angular2 - 指令

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2017年1月30日

Apache

10分钟阅读

viewsIcon

16072

什么是指令?指令是我们分离可重用功能/特性的方式。你能解释得更详细一些吗?回到用户列表,我想将名为“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");
    }
}
导航到“<root url>/users”,我们可以看到“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]=”添加到 td 标签中。再次运行应用程序,浏览器上的输出相同

好的,明白了。上面的示例中还有一个问题。在 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;”这样简单的事情。这会使代码不必要地复杂。因此,第二种方法是解决这种情况的正确方案。

感谢阅读。

注意:如果您认为这篇文章有用,请评分并分享给您的朋友,我将不胜感激。
© . All rights reserved.