angular2-chapter09 -- Displaying Data with Pipes

作者 Guanghui Wang 日期 2017-05-04

Displaying Data with Pipes

Check code at: https://github.com/wghglory/angular2-fundamental

Using Built-in Pipes

Now in event list page, you can see event title case is not unique, so we can use pipe to make all titles uppercase.

Take event-detail.component.html for instance:

1
2
3
<h2>{{event?.name | uppercase}} </h2>
<div><strong>Date:</strong> {{event?.date | date:'shortDate'}}</div>
<div><strong>Price:</strong> {{event?.price | currency:'USD':true}}</div>

Creating a Custom Pipe

Now a problem is duration shows number instead of readable text in event-detail’s session list.

1) create events/shared/duration.pipe.ts. Register it in barrel, AppModule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Pipe, PipeTransform } from '@angular/core'

@Pipe({ name: 'duration' })
export class DurationPipe implements PipeTransform {
transform(value: number): string {
switch (value) {
case 1: return 'Half Hour'
case 2: return 'One Hour'
case 3: return 'Half Day'
case 4: return 'Full Hour'
default: return value.toString();
}
}
}

2) session-list add duration pipe:

1
<span>Duration: {{session.duration | duration}}</span>

Sorting and Filtering Overview

Angular 1 has performance issues. One is using filter, sort by pipe. If a object array have thousands of objects and each object has many properties, it will be very costly to filter or sort. Angular 1 checks if each property of all objects has changed…

Angular 2 pipe is no longer responsible for sorting or filtering. By default, angular 2 runs the pipe only when the identity of source has changed. Angular 2 also can use “impure pipes”, which is same mechanism as angular 1 with performance issue

Filtering

In event-detail page, session-list has “level”, and we want to filter session-list by “level”. We put 4 buttons on top.

event-detail.component.ts add “filterBy” property:

1
2
3
4
5
6
7
8
9
...
export class EventDetailComponent implements OnInit {
constructor(private eventService: EventService, private route: ActivatedRoute) { }

event: IEvent
addMode: boolean = false
+ filterBy: string = 'all';
...
}

event-detail.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div class="row" style="margin-bottom:10px;">
<div class="col-md-2">
<h3 style="margin:0">Sessions</h3>
</div>
+ <div class="col-md-7">
+ <button class="btn btn-default" [class.active]="filterBy==='all'" (click)="filterBy='all'">All</button>
+ <button class="btn btn-default" [class.active]="filterBy==='beginner'" (click)="filterBy='beginner'">Beginner</button>
+ <button class="btn btn-default" [class.active]="filterBy==='intermediate'" (click)="filterBy='intermediate'">Intermediate</button>
+ <button class="btn btn-default" [class.active]="filterBy==='advanced'" (click)="filterBy='advanced'">Advanced</button>
+ </div>
<div class="col-md-2">
<a (click)="addSession()">Add Session</a>
</div>
</div>

+ <session-list [filterBy]="filterBy" *ngIf="!addMode" [sessions]="event?.sessions"></session-list>

session-list.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+ import { Component, Input, OnChanges } from '@angular/core'
import { ISession } from '../shared/index'

@Component({
selector: 'session-list',
templateUrl: 'app/events/event-detail/session-list.component.html'
})
export class SessionListComponent implements OnChanges {
@Input() sessions: ISession[]
+ @Input() filterBy: string
+ visibleSessions: ISession[] = [];

//whenever the input variable changes
+ ngOnChanges() {
+ if (this.sessions) {
+ this.filterSessions(this.filterBy);
+ }
+ }

+ filterSessions(filter) {
+ if (filter === 'all') {
+ // this.visibleSessions = this.sessions; //wrong, we want to make a copy
+ this.visibleSessions = this.sessions.slice(0); //clone the arr
+ } else {
+ this.visibleSessions = this.sessions.filter(s => {
+ return s.level.toLocaleLowerCase() == filter;
+ })
+ }
+ }
}

session-list.component.html: use visiableSessions instead of sessions

1
<div class="row" *ngFor="let session of visibleSessions">

Sorting

sorting by name, voters

event-detail.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
<div class="row" style="margin-bottom:10px;">
<div class="col-md-2">
<h3 style="margin:0">Sessions</h3>
</div>
<div class="col-md-7">
+ <div class="btn-group btn-group-sm" style="margin:0 20px;">
+ <button class="btn btn-default" [class.active]="sortBy==='name'" (click)="sortBy='name'">By Name</button>
+ <button class="btn btn-default" [class.active]="sortBy==='votes'" (click)="sortBy='votes'">By Votes</button>
+ </div>

<div class="btn-group btn-group-sm">
<button class="btn btn-default" [class.active]="filterBy==='all'" (click)="filterBy='all'">All</button>
<button class="btn btn-default" [class.active]="filterBy==='beginner'" (click)="filterBy='beginner'">Beginner</button>
<button class="btn btn-default" [class.active]="filterBy==='intermediate'" (click)="filterBy='intermediate'">Intermediate</button>
<button class="btn btn-default" [class.active]="filterBy==='advanced'" (click)="filterBy='advanced'">Advanced</button>
</div>
</div>
<div class="col-md-2">
<a (click)="addSession()">Add Session</a>
</div>
</div>

+ <session-list [sortBy]="sortBy" [filterBy]="filterBy" *ngIf="!addMode" [sessions]="event?.sessions"></session-list>
<create-session *ngIf="addMode" (saveNewSession)="saveNewSession($event)" (cancelAddSession)="cancelAddSession()"></create-session>
</div>

event-detail.component.ts add sortBy property:

1
sortBy: string = 'votes';  //default votes

session-list.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { Component, Input, OnChanges } from '@angular/core'
import { ISession } from '../shared/index'

@Component({
selector: 'session-list',
templateUrl: 'app/events/event-detail/session-list.component.html'
})
export class SessionListComponent implements OnChanges {
@Input() sessions: ISession[]
@Input() filterBy: string
visibleSessions: ISession[] = [];
+ @Input() sortBy: string

//whenever the input variable changes
ngOnChanges() {
if (this.sessions) {
this.filterSessions(this.filterBy);
+ this.sortBy === 'name' ? this.visibleSessions.sort(sortByNameAsc) : this.visibleSessions.sort(sortByVotesDesc)
}
}

filterSessions(filter) {
if (filter === 'all') {
// this.visibleSessions = this.sessions; //wrong, we want to make a copy
this.visibleSessions = this.sessions.slice(0); //clone the arr
} else {
this.visibleSessions = this.sessions.filter(s => {
return s.level.toLocaleLowerCase() == filter;
})
}
}
}

+ function sortByNameAsc(s1: ISession, s2: ISession) {
+ if (s1.name > s2.name) return 1;
+ else if (s1.name === s2.name) return 0;
+ else return -1;
+ }

+ function sortByVotesDesc(s1: ISession, s2: ISession) {
+ return (s2.voters.length - s1.voters.length);
+ }