Angular指令:学习如何在Angular中使用指令

时间:2020-02-23 14:29:31  来源:igfitidea点击:

Angular Directive基本上是带有@Directive装饰器的类。
我们可能想知道装饰器是什么?
装饰器是用于修改JavaScript类的函数。
装饰器用于将元数据添加到类,它知道这些类的配置以及它们应如何工作。

我们会惊讶地发现一个组件也是一个带有模板的指令。
@Component装饰器实际上是具有面向模板功能的@Directive装饰器。
每当Angular呈现指令时,它都会根据指令给出的指令更改DOM。
指令出现在与属性相似的元素标签内。

Angular指令可以分为两种类型:结构指令和属性指令。

结构化指令通过添加,删除和替换DOM中的元素来更改布局。

让我们简要了解两个主要使用的内置结构指令:

<li *ngFor="let movie of movies"></li>
<movie-detail *ngIf="selectedMovie"></movie-detail>
    • ngFor是一个循环变量,它告诉Angular从影片列表中为每个影片取一个<li>。
    • ngIf仅在选择了电影时才包括MovieDetail组件,否则它将从DOM中将其删除。

属性指令可更改现有元素的外观或者行为。
当我们在模板中包含属性指令时,它们看起来像常规HTML属性。
实现双向数据绑定的ngModel指令是属性指令的示例。
ngModel通过设置现有元素的显示属性并响应变化的事件来修改其行为。

<input [(ngModel)]="movie.name">

Angular还有其他一些指令,这些指令可以更改布局结构(例如ngSwitch)或者修改DOM元素和组件的各个方面(例如ngStyle和ngClass),我将着手进行这些工作。
我们也可以编写自己的指令,例如:自定义Angular指令。

现在继续前进,我们将首先讨论属性指令,然后再讨论结构指令,以清楚地了解它们如何工作以及如何实现它们。

Angular指令:属性指令

属性指令基本上用于修改或者更改元素的外观和行为。

选择器是标识属性的属性。
它用作HTML标签,用于在找到该标签的指令类的目标并将其插入。
指令类实现所需的指令行为。

现在,我们将创建一个myHighlight属性指令,以将元素悬停在该元素上时设置其背景颜色。

可以使用以下代码将其应用于任何地方:

Highlight me!

创建高亮指令打字稿文件,例如:src/app/highlight.directive.ts并嵌入以下代码:

import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
selector: '[myHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef) { }
@Input() defaultColor: string;
@Input('myHighlight') highlightColor: string;
@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.highlightColor || this.defaultColor || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
  this.highlight(null);
}
private highlight(color: string) {
  this.el.nativeElement.style.backgroundColor = color;
}
}

import语句指定从Angular核心使用的必需依赖项:

  • 该指令提供@Directive装饰器的功能。

  • ElementRef注入到指令的构造函数中,以便代码可以访问DOM元素。

  • 输入允许数据从绑定表达式流入指令。

接下来,@ Directive装饰器函数在配置对象中将指令元数据包含为参数。

@Directive需要选择器来标识模板中HTML与指令相关联。
其中指令的选择器是[myHighlight]。
Angular在模板中找到具有名为myHighlight的属性的所有元素。

在@Directive元数据之后,以上代码中的下一部分是指令的类,称为HighlightDirective,其中包含指令的逻辑。
导出HighlightDirective使其可被其他组件访问。

Angular为每个匹配的元素创建指令类的新实例,并将Angular ElementRef注入构造函数。
ElementRef是一项服务,可通过其nativeElement属性授予对DOM元素的直接访问权限。
要在模板中使用新的myHighlight,该模板将指令作为属性应用于段落(<p>)元素。
<p>元素是属性主机。

将模板放在应用程序组件文件中,或者我们也可以创建一个新HTML文件并在templateURL中提供其路径:

import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<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>

 Highlight me! 

 Highlight me too! 
` 
}) 
export class AppComponent { }

接下来,添加导入语句以获取Highlight指令并将该类添加到声明NgModule元数据中。
这样,当Angular在模板中遇到myHighlight时,它就会识别该指令。
在下面的代码中,我们可以看到如何在主模块中导入指令。

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HighlightDirective } from './highlight.directive';
import { AppComponent } from './app.component';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent, HighlightDirective ],
bootstrap: [ AppComponent ]
})
export class AppModule { }

现在,当应用程序运行时,myHighlight指令突出显示段落文本,angular检测到我们试图绑定到某物,并在模块的声明数组中找到该指令。
在声明数组中指定HighlightDirective之后,Angular知道可以将指令应用于此模块中声明的组件。

总而言之,Angular在<p>元素上找到了myHighlight属性。
它创建了HighlightDirective类的实例,并将对<p>元素的引用注入到指令的构造函数中,该指令设置了<p>元素的背景样式。

再次,回头看一下指令代码,我们添加了两个事件处理程序,它们在鼠标进入或者离开时做出响应,每个事件处理程序都由HostListener装饰器装饰。

import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({selector: '[myHighlight]' })
export class HighlightDirective {
constructor(private el: ElementRef) { }
@Input() defaultColor: string;
@Input('myHighlight') highlightColor: string;
@HostListener('mouseenter') onMouseEnter() {
  this.highlight(this.highlightColor || this.defaultColor || 'red');
}
@HostListener('mouseleave') onMouseLeave() {
  this.highlight(null);
}
private highlight(color: string) {
  this.el.nativeElement.style.backgroundColor = color;
}
}

@HostListener装饰器使我们可以订阅承载属性指令的DOM元素的事件。

处理程序委托给一个辅助方法,该方法设置DOM元素el的颜色,该元素在构造函数中声明和初始化。

注意@Input装饰器。
它将元数据添加到类中,从而使指令的highlightColor属性可用于绑定。
之所以称为输入属性,是因为数据从绑定表达式流入指令。
如果没有该输入元数据,Angular将拒绝绑定。

[myHighlight]属性绑定既将突出显示指令应用于<p>元素,又通过属性绑定设置指令的突出显示颜色。
我们正在重新使用指令的属性选择器([myHighlight])来完成这两项工作。

@Input('myHighlight') highlightColor: string;

在指令内部,该属性称为HighlightColor。
在指令外部(绑定到该指令),它称为myHighlight。

<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>

 Highlight me! 

 Highlight me too!

在上面的代码部分中,我们将把AppComponent变成一个线束,使我们可以使用单选按钮选择突出显示的颜色并将颜色选择绑定到指令。
Angular知道defaultColor绑定属于HighlightDirective,因为我们已使用@Input装饰器将其公开。

@Input('myHighlight') highlightColor: string;

无论哪种方式,@Input装饰器都会告诉Angular此属性是公共的,并且可用于由父组件进行绑定。
如果没有@ Input,Angular将拒绝绑定到该属性。

组件及其模板相互隐式信任。
因此,使用或者不使用@Input装饰器,组件自己的模板都可以绑定到该组件的任何属性。
但是,组件或者指令不应盲目地信任其他组件和指令。
默认情况下,组件或者指令的属性从绑定中隐藏。
从Angular绑定Angular来看,它们是私有的。
当使用@Input装饰器进行装饰时,该属性将从Angular绑定Angular变为公共。
只有这样,它才能被其他组件或者指令绑定。

当属性出现在等号(=)右侧的模板表达式中时,它属于模板的组件,并且不需要@Input装饰器。
当它出现在等号(=)左侧的方括号([])中时,该属性属于某个其他组件或者指令。
该属性必须使用@Input装饰器进行装饰。

<span style="color: #ff0000;"></pre>

 Highlight me!

现在查看上面的代码,我们可以轻松推断出:

右侧表达式中的color属性属于模板的组件。
模板及其组件相互信任。
color属性不需要@Input

左侧的myHighlight属性引用HighlightDirective的别名属性,而不是模板组件的属性。
存在信任问题。
因此,指令属性必须带有@Input装饰器。

现在,在这个Angular Directive教程 中讨论Attribute Directives之后,我们应该继续进行结构化Directive。

Angular指令:结构指令

结构指令通常通过添加,删除或者操纵元素来对DOM的结构进行造型或者重新塑形。
与其他指令类似,我们可以将结构指令应用于宿主元素。
然后,该指令执行与该宿主元素相关的任何设计。
结构指令很容易识别。
指令属性名称前带有星号(*)。
它不需要括号或者括号,例如attribute指令。

NgIf,NgFor和NgSwitch是三种常见的内置结构指令。
让我们看看它们在模板中的外观:

<div *ngIf="movie" >{{movie.name}}</div>

<ul>    
  
<li *ngFor="let movie of movies">{{movie.name}}</li>
</ul>

<div [ngSwitch]="movie?.genre">  
  <action-movie *ngSwitchCase="'action'" [movie]=" movie "></action-movie>  
  <horror-movie *ngSwitchCase="'horror'" [movie]=" movie "></horror-movie>  
  <thriller-movie *ngSwitchCase="'thriller'" [movie]=" movie "></thriller-movie>  
  <unknown-movie *ngSwitchDefault [movie]=" movie "></unknown-movie>
</div>

指令可以用UpperCamelCase和LowerCamelCase编写。
这是因为NgIf引用指令类,而ngIf引用指令的属性名称。
当我们谈论它的属性和指令的作用时,我们将参考指令类。
而在描述如何将指令应用于HTML模板中的元素时,我们将引用属性名称。

NgIf

NgIf是最简单的结构指令,也是最容易理解的指令。
它需要一个布尔表达式,并使整个DOM块出现或者消失。
我们可以假定它就像编程语言中的语句一样

Expression is true and ngIf is true. 
  This paragraph will be included in the DOM.     
  Expression is false and ngIf is false. 
  This paragraph will not be included in the DOM.

ngIf指令不会隐藏元素。
它从DOM中物理地添加和删除它们。
我们可以使用浏览器开发人员工具检查DOM来进行确认。
当条件为false时,NgIf从DOM中删除其宿主元素,将该组件与Angular变化检测分离,并销毁它。

不要在删除和隐藏之间混淆。
对于一个简单的段落,隐藏和删除之间的区别并不重要。
但是,当主机元素连接到资源密集型组件时,这确实很重要。
组件保持添加到其DOM元素。
它一直在监听事件。
Angular会继续检查可能影响数据绑定的更改。

尽管该元素是不可见的,但该组件侦听DOM。
性能和内存负担可能很大,响应能力可能下降,并且用户看不到任何东西。
从积极的一面看,再次显示该元素是很快的。
组件的先前状态将保留并准备显示。
该组件不会重新初始化-这可能是昂贵的操作。
因此,隐藏和显示有时是正确的选择。

但是,如果要删除用户看不到的DOM元素并恢复未使用的资源,在这种情况下,可以使用NgIf之类的结构指令。

为什么我们使用星号(*)?

我们肯定注意到指令名称的星号()前缀,并且想知道为什么它很重要吗?
如果英雄存在,则
ngIf在此处显示英雄的名称。

<div *ngIf="movie">{{movie.name}}</div>

在内部,Angular分两个阶段将其解耦。
首先,它将* ngIf ="…"转换为模板属性template =" ngIf…",然后变为:

<div template="ngIf movie">{{movie.name}}</div>

然后,它将template属性转换为<ng-template>元素,软件包在host元素周围,如下所示。

<ng-template [ngIf]="movie">
<div>
    {{movie.name}} 
</div>
</ng-template>
    • ngIf指令移至<ng-template>元素,在此它成为属性绑定[ngIf]。
  • <div>的其余部分(包括其class属性)在<ng-template>内部移动

这些形式均未实际呈现。
只有成品最终出现在DOM中。

NgFor和NgSwitch指令遵循相同的模式。

* ngFor

Angular以类似的方式通过模板属性将* ngFor从星号(*)语法转换为<ng-template>元素。

看起来像这样:

<div *ngFor="let movie of movies">{{movie.name}}</div>

<div template="ngFor let movie of movies">{{movie.name}}</div>

<ng-template ngFor let-movie [ngForOf]="movies">{{movie.name}}
</ng-template>

ngFor需要一个循环变量(播放电影)和一个列表(电影)。
ngFor字符串之外的所有内容都随宿主元素(<div>)一起在<ng-template>中移动。

Angular微语法使我们可以在紧凑,友好的字符串中配置指令。
微语法解析器将该字符串转换为<ng-template>上的属性:

  • let关键字声明我们在模板中引用的模板输入变量。在此示例中,输入变量是电影。解析器将let电影转换为名为let-movie的变量。

  • 当NgFor指令遍历列表时,它会设置和重置其自身上下文的属性

模板输入变量

模板输入变量是可以在模板的单个实例中引用其值的变量。
所有关键字均以let开头。

我们可以使用let关键字(let hero)声明一个模板输入变量。
变量的范围仅限于重复模板的单个实例。
我们可以在其他结构指令的定义中再次使用相同的变量名。

每个主机元素只能应用一个结构指令。
例如,如果要重复HTML块,但仅当特定条件为true时,才重复。
我们将尝试将* ngFor和* ngIf放在同一宿主元素上。
Angular不会让你。
我们只能对一个元素应用一个结构指令。

原因是简单。
结构化指令可以对宿主元素进行复杂的处理。
当两个指令对同一个主机元素声明所有权时,哪个优先?
NgIf或者NgFor应该先走哪一个?

很难回答这些问题。
这个用例有一个简单的解决方案:将* ngIf放在包装* ngFor元素的容器元素上。
一个或者两个元素可以是ng容器。
因此,如果我们不必引入额外HTML级别,则可以在这种情况下使用NgSwitch。

NgSwitch指令

Angular NgSwitch实际上是一组协作指令:NgSwitch,NgSwitchCase和NgSwitchDefault。

这是一个例子。

<div [ngSwitch]="movie?.genre">  
  <action-movie *ngSwitchCase="'action'" [movie]=" movie "></action-movie>  
  <horror-movie *ngSwitchCase="'horror'" [movie]=" movie "></horror-movie>  
  <thriller-movie *ngSwitchCase="'thriller'" [movie]=" movie "></thriller-movie>  
  <unknown-movive *ngSwitchDefault [movie]=" movie "></unknown-movie>
</div>

分配给NgSwitch的开关值(movie.genre)确定显示哪些开关盒。

NgSwitchCase和NgSwitchDefault是结构指令。
我们可以使用星号(*)前缀表示法将它们添加到元素上。
NgSwitchCase的值与开关值匹配时,将显示其主机元素。
如果没有同级NgSwitchCase与开关值匹配,则NgSwitchDefault将显示其主机元素。

向其应用指令的元素是其宿主元素。
<action-movie>是快乐的* ngSwitchCase的宿主元素。
<unknown-movie>是* ngSwitchDefault的主机元素。

<div [ngSwitch]="movie?.genre">  
  <action-movie *ngSwitchCase="'action'" [movie]=" movie "></action-movie>  
  <horror-movie *ngSwitchCase="'horror'" [movie]=" movie "></horror-movie>  
  <thriller-movie *ngSwitchCase="'thriller'" [movie]=" movie "></thriller-movie>  
  <unknown-movive *ngSwitchDefault [movie]=" movie "></unknown-movie>
</div>

在内部,它也被转换为<ng-template>元素形式。
然后看起来像:

<div [ngSwitch]="movie?.genre">  
<ng-template [ngSwitchCase]="'action'">    
  <action-movie [movie]="movie"></action-movie >  
</ng-template>  
<ng-template [ngSwitchCase]="'sad'">    
  <horror-movie [movie]="movie"></horror-movie>  
</ng-template>  
<ng-template [ngSwitchCase]="'confused'">    
  <thriller-movie [movie]="movie"></thriller-movie>  
</ng-template >  
<ng-template ngSwitchDefault>    
  <unknown-movie [movie]="movie"></unknown-movie>
</ng-template>
</div>

<ng-template>是用于呈现HTML的Angular元素。
它永远不会直接显示。
实际上,在渲染视图之前,Angular用注释替换了<ng-template>及其内容。

现在,在这个Angular Directive教程 中,让我们看看如何编写自定义结构指令。

定制结构指令

要编写自定义结构指令:

  • 导入指令装饰器(而不是组件装饰器)。

  • 导入Input,TemplateRef和ViewContainerRef符号;我们将需要它们用于任何结构性指令。

  • 将装饰器应用于指令类。

  • 设置CSSattribute选择器,该选择器在应用于模板中的元素时标识指令。

这是我们开始创建指令的方式:

import{Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core'; 
@Directive({
selector: '[myCustom]'
})
export class myCustomDirective {}

指令的选择器通常是方括号[myCustom]中的指令属性名称。
指令属性名称应在lowerCamelCase中拼写,并以前缀开头。
不要使用ng。
该前缀属于Angular。
指令类名称以指令结尾

TemplateRef和ViewContainerRef

一个简单的结构化Angular指令会从Angular生成的<ng-template>创建一个嵌入式视图,并将该视图插入到该指令的原始<p>宿主元素附近的视图容器中。
我们将使用TemplateRef获取<ng-template>内容,并通过ViewContainerRef访问视图容器。
我们可以将两个指令都作为类的私有变量注入指令构造器中。