[Angular 基础] - 指令(directives)
[Angular 基础] - 指令(directives)
这里假设已经知道如何创建 Angular 组件以及数据绑定,不然可以参考前两篇笔记:
-
[Angular 基础] - Angular 渲染过程 & 组件的创建
-
[Angular 基础] - 数据绑定(databinding)
就像中文翻译一样,directives 就是指令,它就是一系列 DOM 中存在的指令
component directive
Component
这个 declaration 就是一个最基础的指令,当运行:
import { Component } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `<p>Hello, world!</p>`,
})
export class MyComponent {}
这段代码的时候,其实就已经是在运行一个指令,这个指令做了下面几件事情:
@Component
表明下面声明的类是一个组件selector
定义一个元素,该元素会接受当前组件内的 HTML Templatetemplate
定义一个 HTML Template,将会展现在app-my-component
这个 HTML 元素中
这就是当前需要遵从的指令,我个人理解是通知 DOM 需要渲染哪个对应的 HTML Template
以此类推,@NgModule
, @Directive
等也是对应的 component directive
structural directives
structural directives 的指令是通知 DOM 增加或删除特定的 HTML 元素。目前主流的内置 structural directives 有 3 个:*ngIf
, *ngFor
和 *ngSwitch
,当然,这不代表 Angular 之提供了这三个 structural directives
除此之外,开发者自己也可以创建相应的 structural directives
*ngIf
语法为 *ngIf="expression"
,并且这个表达式(expression) 需要返回一个 boolean。依旧使用之前的案例,这里假设点击 add server
的按钮会实现对应功能,并且需要向用户显示新的组件已经生成
对应的 View 层修改如下:
<!-- 其余显示添加 server 的 input 和 button 这里不重复了 -->
<p *ngIf="serverCreated">Sever was created, server name is {{ serverName }}</p>
对应的 VM 层修改如下:
// 新增加的变量,只有在用户点击创建 server 的 btn 时才需要显示信息
serverCreated = false;
onCreateServer() {
this.serverCreationStatus = 'Server was created!';
this.serverCreated = true;
}
效果如下:
可以看到只有当 *ngif
里的条件为 true
时,该 DOM 才会渲染
有了 if
也会有 else
条件,这时候 V 层的代码可以这么修改:
<p *ngIf="serverCreated; else noServer">
Sever was created, server name is {{ serverName }}
</p>
<ng-template #noServer>
<p>No server was created!</p>
</ng-template>
这里使用了一个特殊的 HTML 元素——ng-template
,它主要的用途是为了搭配 structural directives 去使用的;这里 #noServer
是对 ng-template
这一元素的引用变量名
*ngIf
v17 的更新
目前有一个新的语法可以重置 ngIf
,它的语法就是 @for
,使用方式如下:
@if (serverCreated) {
<p>Sever was created, server name is {{ serverName }}</p>
} @else {
<p>No server was created!</p>
}
其展示的效果是一样的,else if
的语法则为 @else if
这个语法是 v17 最新推出的版本,暂时还不是 production-ready,所以只在这里提一下,不会深入研究
ngFor
这是一个可以循环渲染 HTML 元素的方式,用法如下:
-
修改 VM 层
这里会新增一个
servers
的数组,并且在onCreateServer
中将新添的serverName
推到数组中去:servers = ['Test Server', 'Test Server2']; onCreateServer() { this.serverCreationStatus = 'Server was created!'; this.servers.push(this.serverName); this.serverCreated = true; }
-
修改 V 层
这里就使用
*ngFor
去渲染整个数组:<app-server *ngFor="let server of servers"></app-server>
最终效果如下:
⚠️:这里还没有涉及到组件之间数据的传输,所以 serverName
不会动态渲染
相比较 ngIf
,ngFor
的语法更复杂一些,它的完整语法如下:
<div
*ngFor="
let item of servers;
trackBy: trackByFn;
index as i;
first as isFirst;
last as isLast;
even as isEven;
odd as isOdd
"
>
{{ i }}: {{ item }}
<span *ngIf="isFirst">(first)</span>
<span *ngIf="isLast">(last)</span>
<span *ngIf="isEven">(even)</span>
<span *ngIf="isOdd">(odd)</span>
</div>
其中 trackByFn
写在了 VM 层:
trackByFn(index: number, item: string) {
return item;
}
最终展现的效果如下:
这里的 trackBy: trackByFn;
是 Angular 提出的一个优化方案,当它存在的时候,Angular 会检查当前返回值是否变更,如果不变更的话将不会重新渲染当前的 DOM 结点
至于 first
, last
这四个是 Angular 提供的值,以便可以轻松检查这些边界条件
*ngFor
v17 的更新
这个语法如下:
@for (item of items: track item.id) {
<li>{{ item.title }}</li>
}
这个语法是 v17 最新推出的版本,暂时还不是 production-ready,所以只在这里提一下,不会深入研究
ngSwitch
它的语法则类似于 switch
,修改的代码如下:
<div
*ngFor="
let item of servers;
trackBy: trackByFn;
index as i;
first as isFirst;
last as isLast;
even as isEven;
odd as isOdd
"
>
{{ i }}: {{ item }}
<span *ngIf="isFirst">(first)</span>
<span *ngIf="isLast">(last)</span>
<span *ngIf="isEven">(even)</span>
<span *ngIf="isOdd">(odd)</span>
<div [ngSwitch]="item">
<p *ngSwitchCase="'red'">You picked red server!</p>
<p *ngSwitchCase="'blue'">You picked blue server!</p>
<p *ngSwitchCase="'green'">You picked green server!</p>
<p *ngSwitchDefault>Pick a server name</p>
</div>
</div>
效果如下:
*ngSwitch
v17 的更新
这个语法如下:
@switch (expression) { @case value1:
<p>value1</p>
@case value2:
<p>value2</p>
}
这个语法是 v17 最新推出的版本,暂时还不是 production-ready,所以只在这里提一下,不会深入研究
attribute directives
structural directives 控制的是 DOM 元素的增删(是否渲染),那么 attribute directives 控制的则是渲染 DOM 中的属性
这里也会列举主流用的 3 个,同样,这也不代表 Angular 仅仅提供了这 3 个 structural directives,而且开发者同样也可以创建自己的 structural directives
ngModel
这个在之前的笔记里已经提过了,NgModel
主要提供的是双向绑定的功能
ngStyle
ngStyle
就是比较简单的控制 CSS 的地方,用法如下:
<p
[ngStyle]="{
'background-color': serverStatus === 'offline' ? 'red' : 'green'
}"
>
{{ "Server" }} with ID {{ serverId }} is {{ getServerStatus() }}
</p>
效果如下:
⚠️:以上代码修改在 server.component.html
中
❗:如果无法正确渲染,请查看 servers.component.html
中的 ngFor
是不是返回 <app-server></app-server>
⚡:ngStyle
是 attribute directives,[ngStyle]
则是使用了 property binding
🛣️:'backgound-color'
还有另一个写法是用驼峰命名法 backgroundColor
,而 :
后面的也只是需要一个表达式,这里用了三元式,也可以单独创建一个方法
ngClass
这个指令则是动态更新类名,这里实现代码如下:
<p
[ngStyle]="{
'background-color': serverStatus === 'offline' ? 'red' : 'green'
}"
[ngClass]="{
online: serverStatus === 'online',
offline: serverStatus === 'offline'
}"
>
{{ "Server" }} with ID {{ serverId }} is {{ getServerStatus() }}
</p>
至于 VM 层也需要添加对应的 CSS:
@Component({
selector: 'app-server',
templateUrl: './server.component.html',
styles: [
`
.online {
color: cyan;
}
.offline {
color: lightgray;
}
`,
],
})
export class ServerComponent {}
最终效果如下:
总结
下面都是个人理解,对我来说 directives 解决的是下面的问题:
-
component directive
-
what
其实也可以用
which
代替,目前接触过的有两个 directives:-
对
@Component
来说是哪个 HTML Template -
对
@NgModule
是哪个对应的NgModule
-
-
where
这个目前接触到的是
@Component
中的selector
,这里决定哪里会渲染对应的指代组件
-
-
structural directive
这个可以解决一个
when
的问题,即什么情况下会渲染对应的组件如
if/else
,switch
和for
循环,都是在满足一定条件下才会渲染对应的组件 -
attribute directive
这个则是解决了一个
how
的问题,即如何渲染对应组件ngModel
的双向绑定,ngClass
的添加对应类,ngStyle
的对应样式,都可以满足一个how
的问题