7. Displaying Data with pipes
In this section, we will focus on ways to display data.
1. Built-in Pipes
Angular provides a number of built-in pipes to transform data. Below are some examples of commonly used buit-in directives:
| Built-in Pipe | Usage | Example |
|---|---|---|
UppercasePipe | Transforms all text to uppercase | {{ event?.name | uppercase }} |
DatePipe | Formats a date according to locale rules Multiple date format options available | {{ event?.date | date:'shortDate' }} |
CurrencyPipe | Formats a number to a currency according to locale rules Multiple group-sizing, separators and locale-specific configurations available | {{ event?.price | currency:'USD' }} |
JsonPipe | Converts a value to its JSON representation Very useful when debugging | {{ customObject| json }} |
DecimalPipe | Formats a value according to digit options and locale rules | {{ pi | number:'1.1-5':'fr' }} |
AsyncPipe | Unwraps a value from an asynchronous primitive → Very useful, does the subscribe and unsubscribe automatically | *ngIf="(customData$ | async) as customData; else noResults" |
2. Custom Pipes
We can create a custom pipe by defining a custom class using the Pipe decorator and implementing the PipeTransform interface.
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 Day';
default: return value.toString();
}
}
}
We need to let the component that will use our pipe aware that a new pipe is now available. To do this, we go to the module declaring our component to also declare the pipe in the declarations section.
@NgModule({
declarations: [
DurationPipe,
CustomComponent
],
...
)}
export class CustomModule { }
Inside our CustomComponent template we can now use the pipe very easily
...
<span>Duration: {{session.duration | duration }}</span>
...
3. Filtering & Sorting Data
DO IT YOURSELF
There is no built-in "filter" or "order by" pipe in Angular. Objects and Arrays in JavaScript are mutable and the algorithm to check if an array / object / property of an object has changed is very long and complex. This caused a lot of performance problems in AngularJS.
Furthermore, pipes should be written in a pure way, running only when the identity of the object has changed (very quick for primitive types).
On the other hand, impure pipes run on every change detection cycle. This can be very costly and they can run unnecessarily every time a change happens.
⚠️ By default, Angular only runs a pipe when the identity of the source data has changed. However, when we use fitlering and sorting, the data is usually objects and this will not trigger on object changes.
💡 For this reason, it is strongly recommended to implement filtering and sorting on our own !
3.1. Filtering Data
Creating a filtering display
In our component code, we create a simple filterBy property and initiate it.
import { Component } from '@angular/core';
@Component({
selector: 'app-event-details',
templateUrl: 'event-details.component.html'
})
export class EventDetailsComponent {
event: Event;
filterBy: string = 'all';
...
}
Then in our HTML code, we create the different buttons to trigger the changes of states of the filterBy property. We need to pass this filterBy setting/property to a child component's input property
<div>
<button [class.active]="filterBy === 'all'" (click)="filterBy = 'all'">All</button>
<button [class.active]="filterBy === 'beginner'" (click)="filterBy = 'beginner'">Beginner</button>
<button [class.active]="filterBy === 'intermediate'" (click)="filterBy = 'intermediate'">Intermediate</button>
<button [class.active]="filterBy === 'advanced'" (click)="filterBy = 'advanced'">Advanced</button>
</div>
<session-list [sessions]="event?.sessions" [filterBy]="filterBy"></session-list>
Filtering the Data
In order to take action when the value of the filterBy property changes, our child component needs to implement the OnChanges interface and its ngOnChanges() method
import { Component, Input, OnChanges } from '@angular/core';
import { ISession } from '../shared/session.ts';
@Component({
selector: 'app-session-list',
templateUrl: 'session-list.component.html'
})
export class SessionListComponent implements OnChanges {
@Input() sessions: ISession[];
@Input() filterBy: string;
visibleSessions: ISession[];
ngOnChanges() {
if (this.sessions) {
this.filterSessions(this.filterBy);
}
}
filterSessions(filter) {
if (filter === 'all') {
this.visibleSessions = this.sessions.slice(0); // Create a clone of the original array
} else {
this.visibleSessions = this.sessions.filter(sessions => session.level.toLowerCase() === filter);
}
}
}
A new property visibleSessions is created for display purposes instead of using the input set of sessions. To achieve our filter we use:
- If we want to display
allsessions, we clone the originalsessionsarray usingslice - If we want to display a specific session, we use the
filterByproperty tofilterthe array.
In our HTML code, we only need to loop through our visibleSessions that are dynamically filtered!
<div *ngFor="let session of visibleSessions">
<div>
<h6>{{session.presenter}}</h6>
<span>Duration: {{session.duration}}</span>
<span>Level: {{session.level}}</span>
<p>{{session.abstract}}</p>
</div>
</div>
3.2. Sorting Data
We can implement custom sorting in a similar fashion in our code.
Creating a sorting display
In our component code, we create a simple sortBy property and initiate it.
import { Component } from '@angular/core';
@Component({
selector: 'app-event-details',
templateUrl: 'event-details.component.html'
})
export class EventDetailsComponent {
event: Event;
sortBy: string = 'name';
}
Then in our HTML code, we create the different buttons to trigger the changes of states of the sortBy property. We need to pass this sortBy setting/property to a child component's input property
<div>
<button [class.active]="sortBy === 'name'" (click)="sortBy = 'name'">Name</button>
<button [class.active]="sortBy === 'votes'" (click)="sortBy = 'votes'">Votes</button>
</div>
<session-list [sessions]="event?.sessions" [sortBy]="sortBy"></session-list>
Sorting the Data
In order to take action when the value of the sortBy property changes, our child component needs to implement the OnChanges interface and its ngOnChanges() method. A new property visibleSessions is created for display purposes instead of using the input set of sessions. To achieve our sorting we use sort method on the input array.
import { Component, Input, OnChanges } from '@angular/core';
import { ISession } from '../shared/session.ts';
@Component({
selector: 'app-session-list',
templateUrl: 'session-list.component.html'
})
export class SessionListComponent implements OnChanges {
@Input() sessions: ISession[];
@Input() sortBy: string;
visibleSessions: ISession[];
ngOnChanges() {
if (this.sessions) {
this.sortBy === 'name' ? this.visibleSessions.sort(sortByNameAsc) : this.visibleSessions.sort(sortByVotesDesc);
}
}
}
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;
}
In our HTML code, we only need to loop through our visibleSessions that are dynamically sorted!
<div *ngFor="let session of visibleSessions">
<div>
<h6>{{session.presenter}}</h6>
<span>Duration: {{session.duration}}</span>
<span>Level: {{session.level}}</span>
<p>{{session.abstract}}</p>
</div>
</div>