這是從 Udemy 截下來的圖片,它想舉個例子,假設在 TypeScript 中有個 Max
字串,希望在畫面 render 時全部都是大寫,但又不希望改變 Max
,希望 TypeScript 中還是保留本來的狀態,這時會使用 Pipe 語法。
|--app
|--app.component.html // 更動
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name }}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date}}
</li>
</ul>
</div>
</div>
</div>
Pipe 語法是為了在 Template 中改變樣式而存在的語法,因此這段程式理所當然會寫在 Template 中,使用上就在 Interpolation 中加上 |
以及想要轉變的型態即可。
|--app
|--app.component.html // 更動
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name }}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate'}}
</li>
</ul>
</div>
</div>
</div>
這邊的範例則是在 Pipe 中額外加入其他參數,讓它改變呈現的樣式,如果要看參數怎麼設定可以直接參考 Angular 官網。
而除了單一個 Pipe 外,還可以將好幾個 Pipe 連續串接在一起,使用 Pipe 時程式碼是由左而右運行 :
|--app
|--app.component.html // 更動
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name }}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>
使用指令 ng g p pipe/shorten --spec false
,這樣 Angular CLI 會產出能客製化 Pipe 的 TypeScript 檔,接著改寫裡面的內容 :
|--app
|--app.component.html // 更動
|--pipe
|--shorten.pipe.ts // 更動
@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(value: any) {
if(value.length > 10) {
return value.substr(0, 10) + '...';
}
return value;
}
}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name | shorten}}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>
Template 的部分單純使用自己定義好的 Pipe,而 TypeScript 的內容相對做了比較多的改動,第一個是在上面有 @Pipe
的 Decorator,並且指定它的 name 屬性讓外部的其他 Component 可以抓到,第二個是必須實作 PipeTransform
這個介面,並且實作內部的 transform(..)
,transform(..)
預設上可以有多個參數,第一個參數是想要 transform 的變數,後面的參數則是看要不要對它做參數化,目前先忽略即可。
嘗試將其參數化 :
|--app
|--app.component.html // 更動
|--pipe
|--shorten.pipe.ts // 更動
@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(value: any, limit: number) {
if(value.length > limit) {
return value.substr(0, limit) + '...';
}
return value;
}
}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name | shorten: 5}}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>
首先使用指令 ng g p filter pipe/filter --spec false
,再來對檔案做些改動 :
|--app
|--app.component.ts // 更動
|--app.component.html // 更動
|--pipe
|--filter.pipe.ts // 更動
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<input type="text" [(ngModel)]="filterStatus">
<hr>
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers | filter : filterStatus : 'status'"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name | shorten: 5}}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>
export class AppComponent {
servers = [
{
instanceType: 'medium',
name: 'Production Server',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'large',
name: 'User Database',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Development Server',
status: 'offline',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Testing Environment Server',
status: 'stable',
started: new Date(15, 1, 2017)
}
];
filterStatus = '';
getStatusClasses(server: {instanceType: string, name: string, status: string, started: Date}) {
return {
'list-group-item-success': server.status === 'stable',
'list-group-item-warning': server.status === 'offline',
'list-group-item-danger': server.status === 'critical'
};
}
}
@Pipe({
name: 'filter'
})
export class FilterPipe implements PipeTransform {
transform(value: any, filterString: string, propName: string): any {
if(value.length == 0){
return value;
}
const resultArray = [];
for(const item of value){
if(item[propName] === filterString){
resultArray.push(item);
}
}
return resultArray;
}
}
在 AppComponent
中加入 filterStatus
,利用雙向綁定與 Template 中的 <input>
綁定在一起。 filter 的部分,這部分想做的是將陣列中符合使用者輸入的值給留下來,因此第二個參數放入的是要過濾出來的字串、第三個參數則是放入想要以什麼屬性來過濾物件。Template 的部分,*ngFor
使用 Pipe 將其進行過濾,可以觀察到如果在 <input>
打上 offline
或是 stable
,*ngFor
才會將其迭代出來。
|--app
|--app.component.ts // 更動
|--app.component.html // 更動
|--pipe
|--filter.pipe.ts // 更動
export class AppComponent {
servers = [
{
instanceType: 'medium',
name: 'Production Server',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'large',
name: 'User Database',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Development Server',
status: 'offline',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Testing Environment Server',
status: 'stable',
started: new Date(15, 1, 2017)
}
];
filterStatus = '';
getStatusClasses(server: {instanceType: string, name: string, status: string, started: Date}) {
return {
'list-group-item-success': server.status === 'stable',
'list-group-item-warning': server.status === 'offline',
'list-group-item-danger': server.status === 'critical'
};
}
onAddServer() {
this.servers.push({
instanceType: 'small',
name: 'New Server',
status: 'stable',
started: new Date(15, 1, 2017)
});
}
}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<input type="text" [(ngModel)]="filterStatus">
<br>
<button class="btn btn-primary" (click)="onAddServer()">Add Server!</button>
<hr>
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers | filter : filterStatus : 'status'"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name | shorten: 5}}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>
@Pipe({
name: 'filter',
pure: false
})
export class FilterPipe implements PipeTransform {
transform(value: any, filterString: string, propName: string): any {
if(value.length == 0){
return value;
}
const resultArray = [];
for(const item of value){
if(item[propName] === filterString){
resultArray.push(item);
}
}
return resultArray;
}
}
Pipe 在預設上,物件就算有所變動也不會觸發 Pipe 的內容,只有當 Pipe 的參數有所變動,才會刷新 Pipe 的內容,這主要是效能上的考量,如果將 @Pipe
內部中的 pure
改成 false
就會更改這樣的機制。
async
可以幫忙把已經變成 fulfillment
的 Promise 解析出來,以及將訂閱到的 Observable 解析出來,讓它不會一開始就解析物件而得到 [Object Promise]
的字串 :
|--app
|--app.component.ts // 更動
|--app.component.html // 更動
export class AppComponent {
appStatus = new Promise((resolve, reject) => {
setTimeout(function(){
resolve('stable')
}, 2000)
})
servers = [
{
instanceType: 'medium',
name: 'Production Server',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'large',
name: 'User Database',
status: 'stable',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Development Server',
status: 'offline',
started: new Date(15, 1, 2017)
},
{
instanceType: 'small',
name: 'Testing Environment Server',
status: 'stable',
started: new Date(15, 1, 2017)
}
];
filterStatus = '';
getStatusClasses(server: {instanceType: string, name: string, status: string, started: Date}) {
return {
'list-group-item-success': server.status === 'stable',
'list-group-item-warning': server.status === 'offline',
'list-group-item-danger': server.status === 'critical'
};
}
onAddServer() {
this.servers.push({
instanceType: 'small',
name: 'New Server',
status: 'stable',
started: new Date(15, 1, 2017)
});
}
}
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
<input type="text" [(ngModel)]="filterStatus">
<br>
<button class="btn btn-primary" (click)="onAddServer()">Add Server!</button>
<br><br>
<h2>App Status: {{appStatus|async}}</h2>
<hr>
<ul class="list-group">
<li
class="list-group-item"
*ngFor="let server of servers | filter : filterStatus : 'status'"
[ngClass]="getStatusClasses(server)">
<span
class="badge">
{{ server.status }}
</span>
<strong>{{ server.name | shorten: 5}}</strong> |
{{ server.instanceType | uppercase }} |
{{ server.started | date: 'fullDate' | uppercase}}
</li>
</ul>
</div>
</div>
</div>