© Adam Freeman 2018
Adam FreemanPro Angular 6https://doi.org/10.1007/978-1-4842-3649-9_18

18. Using and Creating Pipes

Adam Freeman1 
(1)
London, UK
 
Pipes are small fragments of code that transform data values so they can be displayed to the user in templates. Pipes allow transformation logic to be defined in self-contained classes so that it can be applied consistently throughout an application. Table 18-1 puts pipes in context.
Table 18-1

Putting Pipes in Context

Question

Answer

What are they?

Pipes are classes that are used to prepare data for display to the user.

Why are they useful?

Pipes allow preparation logic to be defined in a single class that can be used throughout an application, ensuing that data is presented consistently.

How are they used?

The @Pipe decorator is applied to a class and used to specify a name by which the pipe can be used in a template.

Are there any pitfalls or limitations?

Pipes should be simple and focused on preparing data. It can be tempting to let the functionality creep into areas that are the responsibility of other building blocks, such as directives or components.

Are there any alternatives?

You can implement data preparation code in components or directives, but that makes it harder to reuse in other parts of the application.

Table 18-2 summarizes the chapter.
Table 18-2

Chapter Summary

Problem

Solution

Listing

Format a data value for inclusion in a template

Use a pipe in a data binding expression

1–6

Create a custom pipe

Apply the @Pipe decorator to a class

7–9

Format a data value using multiple pipes

Chain the pipe names together using the bar character

10

Specify when Angular should reevaluate the output from a pipe

Use the pure property of the @Pipe decorator

11–14

Format numerical values

Use the number pipe

15, 16

Format currency values

Use the currency pipe

17, 18

Format percentage values

Use the percent pipe

19–22

Change the case of strings

Use the uppercase or lowercase pipes

23

Serialize objects into the JSON format

Use the json pipe

24

Select elements from an array

Use the slice pipe

25

Preparing the Example Project

I am going to continue working with the example project that was first created in Chapter 11 and that has been expanded and modified in the chapters since. In the final examples in the previous chapter, component styles and view children queries left the application with a strikingly garish appearance that I am going to tone down for this chapter. In Listing 18-1, I have disabled the inline component styles applied to the form elements.

Tip

You can download the example project for this chapter—and for all the other chapters in this book—from https://github.com/Apress/pro-angular-6 .

import { Component, Output, EventEmitter, ViewEncapsulation } from "@angular/core";
import { Product } from "./product.model";
import { ProductFormGroup } from "./form.model";
@Component({
    selector: "paProductForm",
    templateUrl: "productForm.component.html",
    // styleUrls: ["app/productForm.component.css"],
    // encapsulation: ViewEncapsulation.Emulated
})
export class ProductFormComponent {
    form: ProductFormGroup = new ProductFormGroup();
    newProduct: Product = new Product();
    formSubmitted: boolean = false;
    @Output("paNewProduct")
    newProductEvent = new EventEmitter<Product>();
    submitForm(form: any) {
        this.formSubmitted = true;
        if (form.valid) {
            this.newProductEvent.emit(this.newProduct);
            this.newProduct = new Product();
            this.form.reset();
            this.formSubmitted = false;
        }
    }
}
Listing 18-1

Disabling CSS Styles in the productForm.component.ts File in the src/app Folder

To disable the checkerboard coloring of the table cells, I changed the selector for the PaCellColor directive so that it matches an attribute that is not currently applied to the HTML elements, as shown in Listing 18-2.
import { Directive, HostBinding } from "@angular/core";
@Directive({
    selector: "td[paApplyColor]"
})
export class PaCellColor {
    @HostBinding("class")
    bgClass: string = "";
    setColor(dark: Boolean) {
        this.bgClass = dark ? "bg-dark" : "";
    }
}
Listing 18-2

Changing the Selector in the cellColor.directive.ts File in the src/app Folder

Listing 18-3 disables the deep styles defined by the root component.
import { ApplicationRef, Component } from "@angular/core";
import { Model } from "./repository.model";
import { Product } from "./product.model";
import { ProductFormGroup } from "./form.model";
@Component({
    selector: "app",
    templateUrl: "template.html",
    //styles: ["/deep/ div { border: 2px black solid;  font-style:italic }"]
})
export class ProductComponent {
    model: Model = new Model();
    addProduct(p: Product) {
        this.model.saveProduct(p);
    }
}
Listing 18-3

Disabling CSS Styles in the component.ts File in the src/app Folder

The next change for the existing code in the example application is to simplify the ProductTableComponent class to remove methods and properties that are no longer required, as shown in Listing 18-4.
import { Component, Input, ViewChildren, QueryList } from "@angular/core";
import { Model } from "./repository.model";
import { Product } from "./product.model";
@Component({
    selector: "paProductTable",
    templateUrl: "productTable.component.html"
})
export class ProductTableComponent {
    @Input("model")
    dataModel: Model;
    getProduct(key: number): Product {
        return this.dataModel.getProduct(key);
    }
    getProducts(): Product[] {
        return this.dataModel.getProducts();
    }
    deleteProduct(key: number) {
        this.dataModel.deleteProduct(key);
    }
}
Listing 18-4

Simplifying the Code in the productTable.component.ts File in the src/app Folder

Finally, I have removed one of the component elements from the root component’s template to disable the checkbox that shows and hides the table, as shown in Listing 18-5.
<div class="row m-2">
  <div class="col-4 p-2">
    <paProductForm (paNewProduct)="addProduct($event)"></paProductForm>
  </div>
  <div class="col-8 p-2">
    <paProductTable [model]="model"></paProductTable>
  </div>
</div>
Listing 18-5

Simplifying the Elements in the template.html File in the src/app Folder

Run the following command in the example folder to start the Angular development tools:
ng serve
Open a new browser tab and navigate to http://localhost:4200 to see the content shown in Figure 18-1.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig1_HTML.jpg
Figure 18-1

Running the example application

Understanding Pipes

Pipes are classes that transform data before it is received by a directive or component. That may not sound like an important job, but pipes can be used to perform some of the most commonly required development tasks easily and consistently.

As a quick example to demonstrate how pipes are used, Listing 18-6 applies one of the built-in pipes to transform the values displayed in the Price column of the table displayed by the application.
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | currency:"USD":"symbol" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-6

Using a Pipe in the productTable.component.html File in the src/app Folder

The syntax for applying a pipe is similar to the style used by command prompts, where a value is “piped” for transformation using the vertical bar symbol (the | character). Figure 18-2 shows the structure of the data binding that contains the pipe.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig2_HTML.jpg
Figure 18-2

The anatomy of data binding with a pipe

The name of the pipe used in Listing 18-6 is currency, and it formats numbers into currency values. Arguments to the pipe are separated by colons (the : character). The first pipe argument specifies the currency code that should be used, which is USD in this case, representing U.S. dollars. The second pipe argument, which is symbol, specifies whether the currency symbol, rather than its code, should be displayed.

When Angular processes the expression, it obtains the data value and passes it to the pipe for transformation. The result produced by the pipe is then used as the expression result for the data binding. In the example, the bindings are string interpolations, and the results can be seen in Figure 18-3.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig3_HTML.jpg
Figure 18-3

The effect of using the currency pipe

Creating a Custom Pipe

I will return to the built-in pipes that Angular provides later in the chapter, but the best way to understand how pipes work and what they are capable of is to create a custom pipe. I added a file called addTax.pipe.ts in the src/app folder and defined the class shown in Listing 18-7.
import { Pipe } from "@angular/core";
@Pipe({
    name: "addTax"
})
export class PaAddTaxPipe {
    defaultRate: number = 10;
    transform(value: any, rate?: any): number {
        let valueNumber = Number.parseFloat(value);
        let rateNumber = rate == undefined ?
            this.defaultRate : Number.parseInt(rate);
        return valueNumber + (valueNumber * (rateNumber / 100));
    }
}
Listing 18-7

The Contents of the addTax.pipe.ts File in the src/app Folder

Pipes are classes to which the @Pipe decorator has been applied and that implement a method called transform. The @Pipe decorator defines two properties, which are used to configure pipes, as described in Table 18-3.
Table 18-3

The @Pipe Decorator Properties

Name

Description

name

This property specifies the name by which the pipe is applied in templates.

pure

When true, this pipe is reevaluated only when its input value or its arguments are changed. This is the default value. See the “Creating Impure Pipes” section for details.

The example pipe is defined in a class called PaAddTaxPipe, and its decorator name property specifies that the pipe will be applied using addTax in templates. The transform method must accept at least one argument, which Angular uses to provide the data value that the pipe formats. The pipe does its work in the transform method and its result is used by Angular in the binding expression. In this example, the transform method accepts a number value and its result is the received value plus sales tax.

The transform method can also define additional arguments that are used to configure the pipe. In the example, the optional rate argument can be used to specify the sales tax rate, which defaults to 10 percent.

Caution

Be careful when dealing with the arguments received by the transform method and make sure that you parse or convert them to the types you need. The TypeScript type annotations are not enforced at runtime, and Angular will pass you whatever data values it is working with.

Registering a Custom Pipe

Pipes are registered using the declarations property of the Angular module, as shown in Listing 18-8.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaToggleView } from "./toggleView.component";
import { PaAddTaxPipe } from "./addTax.pipe";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaToggleView, PaAddTaxPipe],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 18-8

Registering a Custom Pipe in the app.module.ts File in the src/app Folder

Applying a Custom Pipe

Once a custom pipe has been registered, it can be used in data binding expressions. In Listing 18-9, I have applied the pipe to the price value in the tables and added a select element that allows the tax rate to be specified.
<div>
    <label>Tax Rate:</label>
    <select [value]="taxRate || 0" (change)="taxRate=$event.target.value">
        <option value="0">None</option>
        <option value="10">10%</option>
        <option value="20">20%</option>
        <option value="50">50%</option>
    </select>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-9

Applying the Custom Pipe in the productTable.component.html File in the src/app Folder

Just for variety, I defined the tax rate entirely within the template. The select element has a binding that sets its value property to a component variable called taxRate or defaults to 0 if the property has not been defined. The event binding handles the change event and sets the value of the taxRate property. You cannot specify a fallback value when using the ngModel directive, which is why I have split up the bindings.

In applying the custom pipe, I have used the vertical bar character, followed by the value specified by the name property in the pipe’s decorator. The name of the pipe is followed by a colon, which is followed by an expression that is evaluated to provide the pipe with its argument. In this case, the taxRate property will be used if it has been defined, with a fallback value of zero.

Pipes are part of the dynamic nature of Angular data bindings, and the pipe’s transform method will be called to get an updated value if the underlying data value changes or if the expression used for the arguments changes. The dynamic nature of pipes can be seen by changing the value displayed by the select element, which will define or change the taxRate property, which will, in turn, update the amount added to the price property by the custom pipe, as shown in Figure 18-4.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig4_HTML.jpg
Figure 18-4

Using a custom pipe

Combining Pipes

The addTax pipe is applying the tax rate, but the fractional amounts that are produced by the calculation are unsightly—and unhelpful since few tax authorities insist on accuracy to 15 fractional digits.

I could fix this by adding support to the custom pipe to format the number values as currencies, but that would require duplicating the functionality of the built-in currency pipe that I used earlier in the chapter. A better approach is to combine the functionality of both pipes so that the output from the custom addTax pipe is fed into the built-in currency pipe, which is then used to produce the value displayed to the user.

Pipes are chained together in this way using the vertical bar character, and the names of the pipes are specified in the order that data should flow, as shown in Listing 18-10.
...
<td style="vertical-align:middle">
    {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol"  }}
</td>
...
Listing 18-10

Combining Pipes in the productTable.component.html File in the src/app Folder

The value of the item.price property is passed to the addTax pipe, which adds the sales tax, and then to the currency pipe, which formats the number value into a currency amount, as shown in Figure 18-5.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig5_HTML.jpg
Figure 18-5

Combining the functionality of pipes

Creating Impure Pipes

The pure decorator property is used to tell Angular when to call the pipe’s transform method. The default value for the pure property is true, which tells Angular that the pipe’s transform method will generate a new value only if the input data value—the data value before the vertical bar character in the template—changes or when one or more of its arguments is modified. This is known as a pure pipe because it has no independent internal state and all its dependencies can be managed using the Angular change detection process.

Setting the pure decorator property to false creates an impure pipe and tells Angular that the pipe has its own state data or that it depends on data that may not be picked up in the change detection process when there is a new value.

When Angular performs its change detection process, it treats impure pipes as sources of data values in their own right and invokes the transform methods even when there has been no data value or argument changes.

The most common need for impure pipes is when they process the contents of arrays and the elements in the array change. As you saw in Chapter 16, Angular doesn’t automatically detect changes that occur within arrays and won’t invoke a pure pipe’s transform method when an array element is added, edited, or deleted because it just sees the same array object being used as the input data value.

Caution

Impure pipes should be used sparingly because Angular has to call the transform method whenever there is any data change or user interaction in the application, just in case it might result in a different result from the pipe. If you do create an impure pipe, then keep it as simple as possible. Performing complex operations, such as sorting an array, can devastate the performance of an Angular application.

As a demonstration, I added a file called categoryFilter.pipe.ts in the src/app folder and used it to define the pipe shown in Listing 18-11.
import { Pipe } from "@angular/core";
import { Product } from "./product.model";
@Pipe({
    name: "filter",
    pure: true
})
export class PaCategoryFilterPipe {
    transform(products: Product[], category: string): Product[] {
        return category == undefined ?
            products : products.filter(p => p.category == category);
    }
}
Listing 18-11

The Contents of the categoryFilter.pipe.ts File in the src/app Folder

This is a pure filter that receives an array of Product objects and returns only the ones whose category property matches the category argument. Listing 18-12 shows the new pipe registered in the Angular module.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule  } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaToggleView } from "./toggleView.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaToggleView, PaAddTaxPipe,
        PaCategoryFilterPipe],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 18-12

Registering a Pipe in the app.module.ts File in the src/app Folder

Listing 18-13 shows the application of the new pipe to the binding expression that targets the ngFor directive as well as a new select element that allows the filter category to be selected.
<div>
    <label>Tax Rate:</label>
    <select [value]="taxRate || 0" (change)="taxRate=$event.target.value">
        <option value="0">None</option>
        <option value="10">10%</option>
        <option value="20">20%</option>
        <option value="50">50%</option>
    </select>
</div>
<div>
    <label>Category Filter:</label>
    <select [(ngModel)]="categoryFilter">
        <option>Watersports</option>
        <option>Soccer</option>
        <option>Chess</option>
    </select>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts() | filter:categoryFilter;
            let i = index; let odd = odd; let even = even"
            [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol"  }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-13

Applying a Pipe in the productTable.component.html File in the src/app Folder

To see the problem, use the select element to filter the products in the table so that only those in the Soccer category are shown. Then use the form elements to create a new product in that category. Clicking the Create button will add the product to the data model, but the new product won’t be shown in the table, as illustrated in Figure 18-6.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig6_HTML.jpg
Figure 18-6

A problem caused by a pure pipe

The table isn’t updated because, as far as Angular is concerned, none of the inputs to the filter pipe has changed. The component’s getProducts method returns the same array object, and the categoryFilter property is still set to Soccer. The fact that there is a new object inside the array returned by the getProducts method isn’t recognized by Angular.

The solution is to set the pipe’s pure property to false, as shown in Listing 18-14.
import { Pipe } from "@angular/core";
import { Product } from "./product.model";
@Pipe({
    name: "filter",
    pure: false
})
export class PaCategoryFilterPipe {
    transform(products: Product[], category: string): Product[] {
        return category == undefined ?
            products : products.filter(p => p.category == category);
    }
}
Listing 18-14

Marking a Pipe as Impure in the categoryFilter.pipe.ts File in the src/app Folder

If you repeat the test, you will see that the new product is now correctly displayed in the table, as shown in Figure 18-7.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig7_HTML.jpg
Figure 18-7

Using an impure pipe

Using the Built-in Pipes

Angular includes a set of built-in pipes that perform commonly required tasks. These pipes are described in Table 18-4 and demonstrated in the sections that follow.
Table 18-4

The Built-in Pipes

Name

Description

number

This pipe performs location-sensitive formatting of number values. See the “Formatting Numbers” section for details.

currency

This pipe performs location-sensitive formatting of currency amounts. See the “Formatting Currency Values” section for details.

percent

This pipe performs location-sensitive formatting of percentage values. See the “Formatting Percentages” section for details.

date

This pipe performs location-sensitive formatting of dates. See the “Formatting Dates” section for details.

uppercase

This pipe transforms all the characters in a string to uppercase. See the “Changing String Case” section for details.

lowercase

This pipe transforms all the characters in a string to lowercase. See the “Changing String Case” section for details.

json

This pipe transforms an object into a JSON string. See the “Serializing Data as JSON” section for details.

slice

This pipe selects items from an array or characters from a string, as described in the “Slicing Data Arrays” section.

async

This pipe subscribes to an observable or a promise and displays the most recent value it produces. This pipe is demonstrated in Chapter 23.

Formatting Numbers

The number pipe formats number values using locale-sensitive rules. Listing 18-15 shows the use of the number pipe, along with the argument that specifies the formatting that will be used. I have removed the custom pipes and the associated select elements from the template.
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">{{item.price | number:"3.2-2" }}</td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-15

Using the number Pipe in the productTable.component.html File in the src/app Folder

The number pipe accepts a single argument that specifies the number of digits that are included in the formatted result. The argument is in the following format (note the period and hyphen that separate the values and that the entire argument is quoted as a string):
"<minIntegerDigits>.<minFactionDigits>-<maxFractionDigits>"
Each element of the formatting argument is described in Table 18-5.
Table 18-5

The Elements of the number Pipe Argument

Name

Description

minIntegerDigits

This value specifies the minimum number of digits. The default value is 1.

minFractionDigits

This value specifies the minimum number of fractional digits. The default value is 0.

maxFractionDigits

This value specifies the maximum number of fractional digits. The default value is 3.

The argument used in the listing is "3.2-2", which specifies that at least three digits should be used to display the integer portion of the number and that two fractional digits should always be used. This produces the result shown in Figure 18-8.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig8_HTML.jpg
Figure 18-8

Formatting number values

The number pipe is location sensitive, which means that the same format argument will produce differently formatted results based on the user’s locale setting. Angular applications default to the en-US locale by default and require other locales to be loaded explicitly, as shown in Listing 18-16.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaToggleView } from "./toggleView.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { LOCALE_ID } from "@angular/core";
import localeFr from '@angular/common/locales/fr';
import { registerLocaleData } from '@angular/common';
registerLocaleData(localeFr);
@NgModule({
    imports: [BrowserModule, FormsModule, ReactiveFormsModule],
    declarations: [ProductComponent, PaAttrDirective, PaModel,
        PaStructureDirective, PaIteratorDirective,
        PaCellColor, PaCellColorSwitcher, ProductTableComponent,
        ProductFormComponent, PaToggleView, PaAddTaxPipe,
        PaCategoryFilterPipe],
    providers: [{ provide: LOCALE_ID, useValue: "fr-FR" }],
    bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 18-16

Setting the Locale in the app.module.ts File in the src/app Folder

Setting the locale requires importing the locale you require from the modules that contain each region’s data and registering it by calling the registerLocaleData function, which is imported from the @angular/common module. In the listing, I have imported the fr-FR locale, which is for French as it is spoken in France. The final step is to configure the providers property, which I describe in Chapter 20, but the effect of the configuration in Listing 18-16 is to enable the fr-FR locale, which changes the formatting of the numerical values, as shown in Figure 18-9.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig9_HTML.jpg
Figure 18-9

Locale-sensitive formatting

Formatting Currency Values

The currency pipe formats number values that represent monetary amounts. Listing 18-6 used this pipe to introduce the topic, and Listing 18-17 shows another application of the same pipe but with the addition of number format specifiers.
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-17

Using the currency Pipe in the productTable.component.html File in the src/app Folder

The currency pipe can be configured using four arguments, which are described in Table 18-6.
Table 18-6

The Types of Web Forms Code Nuggets

Name

Description

currencyCode

This string argument specifies the currency using an ISO 4217 code. The default value is USD if this argument is omitted. You can see a list of currency codes at http://en.wikipedia.org/wiki/ISO_4217 .

display

This string indicates whether the currency symbol or code should be displayed. The supported values are code (use the currency code), symbol (use the currency symbol), and symbol-narrow (which shows the concise form when a currency has narrow and wide symbols). The default value is symbol.

digitInfo

This string argument specifies the formatting for the number, using the same formatting instructions supported by the number pipe, as described in the “Formatting Numbers” section.

locale

This string argument specifies the locale for the currency. This defaults to the LOCALE_ID value, the configuration of which is shown in Listing 18-16.

The arguments specified in Listing 18-17 tell the pipe to use the U.S. dollar as the currency (which has the ISO code USD), to display the symbol rather than the code in the output, and to format the number so that it has at least two integer digits and exactly two fraction digits.

This pipe relies on the Internationalization API to get details of the currency—especially its symbol—but doesn’t select the currency automatically to reflect the user’s locale setting.

This means that the formatting of the number and the position of the currency symbol are affected by the application’s locale setting, regardless of the currency that has been specified by the pipe. The example application is still configured to use the fr-FR locale, which produces the results shown in Figure 18-10.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig10_HTML.jpg
Figure 18-10

Location-sensitive currency formatting

To revert to the default locale, Listing 18-18 removes the fr-FR setting from the application’s root module.
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { ProductComponent } from "./component";
import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { PaAttrDirective } from "./attr.directive";
import { PaModel } from "./twoway.directive";
import { PaStructureDirective } from "./structure.directive";
import { PaIteratorDirective } from "./iterator.directive";
import { PaCellColor } from "./cellColor.directive";
import { PaCellColorSwitcher } from "./cellColorSwitcher.directive";
import { ProductTableComponent } from "./productTable.component";
import { ProductFormComponent } from "./productForm.component";
import { PaToggleView } from "./toggleView.component";
import { PaAddTaxPipe } from "./addTax.pipe";
import { PaCategoryFilterPipe } from "./categoryFilter.pipe";
import { LOCALE_ID } from "@angular/core";
import localeFr from '@angular/common/locales/fr';
import { registerLocaleData } from '@angular/common';
registerLocaleData(localeFr);
@NgModule({
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  declarations: [ProductComponent, PaAttrDirective, PaModel,
    PaStructureDirective, PaIteratorDirective,
    PaCellColor, PaCellColorSwitcher, ProductTableComponent,
    ProductFormComponent, PaToggleView, PaAddTaxPipe,
    PaCategoryFilterPipe],
  //providers: [{ provide: LOCALE_ID, useValue: "fr-FR" }],
  bootstrap: [ProductComponent]
})
export class AppModule { }
Listing 18-18

Removing the locale Setting in the app.module.ts File in the src/app Folder

Figure 18-11 shows the result.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig11_HTML.jpg
Figure 18-11

Formatting currency values

Formatting Percentages

The percent pipe formats number values as percentages, where values between 0 and 1 are formatted to represent 0 to 100 percent. This pipe has optional arguments that are used to specify the number formatting options, using the same format as the number pipe, and override the default locale. Listing 18-19 re-introduces the custom sales tax filter and populates the associated select element with option elements whose content is formatted with the percent filter.
<div>
    <label>Tax Rate:</label>
    <select [value]="taxRate || 0" (change)="taxRate=$event.target.value">
        <option value="0">None</option>
        <option value="10">{{ 0.1 | percent }}</option>
        <option value="20">{{ 0.2 | percent }}</option>
        <option value="50">{{ 0.5 | percent }}</option>
        <option value="150">{{ 1.5 | percent }}</option>
    </select>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-19

Formatting Percentages in the productTable.component.html File in the src/app Folder

Values that are greater than 1 are formatted into percentages greater than 100 percent. You can see this in the last item shown in Figure 18-12, where the value 1.5 produces a formatted value of 150 percent.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig12_HTML.jpg
Figure 18-12

Formatting percentage values

The formatting of percentage values is location-sensitive, although the differences between locales can be subtle. As an example, while the en-US locale produces a result such as 10 percent, with the numerals and the percent sign next to one another, many locales, including fr-FR, will produce a result such as 10 %, with a space between the numerals and the percent sign.

Formatting Dates

The date pipe performs location-sensitive formatting of dates. Dates can be expressed using JavaScript Date objects, as a number value representing milliseconds since the beginning of 1970 or as a well-formatted string. Listing 18-20 adds three properties to the ProductTableComponent class, each of which encodes a date in one of the formats supported by the date pipe.
import { Component, Input, ViewChildren, QueryList } from "@angular/core";
import { Model } from "./repository.model";
import { Product } from "./product.model";
@Component({
    selector: "paProductTable",
    templateUrl: "productTable.component.html"
})
export class ProductTableComponent {
    @Input("model")
    dataModel: Model;
    getProduct(key: number): Product {
        return this.dataModel.getProduct(key);
    }
    getProducts(): Product[] {
        return this.dataModel.getProducts();
    }
    deleteProduct(key: number) {
        this.dataModel.deleteProduct(key);
    }
    dateObject: Date = new Date(2020, 1, 20);
    dateString: string = "2020-02-20T00:00:00.000Z";
    dateNumber: number = 1582156800000;
}
Listing 18-20

Defining Dates in the productTable.component.ts File in the src/app Folder

All three properties describe the same date, which is February 20, 2020, with no time specified. In Listing 18-21, I have used the date pipe to format all three properties.
<div class="bg-info p-2 text-white">
    <div>Date formatted from object: {{ dateObject | date }}</div>
    <div>Date formatted from string: {{ dateString | date }}</div>
    <div>Date formatted from number: {{ dateNumber | date }}</div>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name}}</td>
        <td style="vertical-align:middle">{{item.category}}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-21

Formatting Dates in the productTable.component.html File in the src/app Folder

The pipe works out which data type it is working with, parses the value to get a date, and then formats it, as shown in Figure 18-13.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig13_HTML.jpg
Figure 18-13

Formatting dates

The date pipe accepts an argument that specifies the date format that should be used. Individual date components can be selected for the output using the symbols described in Table 18-7.
Table 18-7

The Date Pipe Format Symbols

Name

Description

y, yy

These symbols select the year.

M, MMM, MMMM

These symbols select the month.

d, dd

These symbols select the day (as a number).

E, EE, EEEE

These symbols select the day (as a name).

j, jj

These symbols select the hour.

h, hh, H, HH

These symbols select the hour in 12- and 24-hour forms.

m, mm

These symbols select the minutes.

s, ss

These symbols select the seconds.

Z

This symbol selects the time zone.

The symbols in Table 18-7 provide access to the date components in differing levels of brevity so that M will return 2 if the month is February, MM will return 02, MMM will return Feb, and MMMM will return February, assuming that you are using the en-US locale. The date pipe also supports predefined date formats for commonly used combinations, as described in Table 18-8.
Table 18-8

The Predefined date Pipe Formats

Name

Description

short

This format is equivalent to the component string yMdjm. It presents the date in a concise format, including the time component.

medium

This format is equivalent to the component string yMMMdjms. It presents the date as a more expansive format, including the time component.

shortDate

This format is equivalent to the component string yMd. It presents the date in a concise format and excludes the time component.

mediumDate

This format is equivalent to the component string yMMMd. It presents the date in a more expansive format and excludes the time component.

longDate

This format is equivalent to the component string yMMMMd. It presents the date and excludes the time component.

fullDate

This format is equivalent to the component string yMMMMEEEEd. It presents the date fully and excludes the date format.

shortTime

This format is equivalent to the component string jm.

mediumTime

This format is equivalent to the component string jms.

The date pipe also accepts an argument that specifies a time zone and an argument that can be used to override the locale. Listing 18-22 shows the use of the predefined formats as arguments to the date pipe, rendering the same date in different ways.
...
<div class="bg-info p-2 text-white">
  <div>Date formatted as shortDate: {{ dateObject | date:"shortDate" }}</div>
  <div>Date formatted as mediumDate: {{ dateObject | date:"mediumDate" }}</div>
  <div>Date formatted as longDate: {{ dateObject | date:"longDate" }}</div>
</div>
...
Listing 18-22

Formatting Dates in the productTable.component.html File in the src/app Folder

Formatting arguments are specified as literal strings. Take care to capitalize the format string correctly because shortDate will be interpreted as one of the predefined formats from Table 18-8, but shortdate (with a lowercase letter d) will be interpreted a series of characters from Table 18-7 and produce nonsensical output.

Caution

Date parsing and formatting is a complex and time-consuming process. As a consequence, the pure property for the date pipe is true; as a result, changes to individual components of a Date object won’t trigger an update. If you need to reflect changes in the way that a date is displayed, then you must change the reference to the Date object that the binding containing the date pipe refers to.

Date formatting is location-sensitive, which means you will receive different components for different locales. Do not assume that a date format that makes sense in one locale will have any meaning in another. Figure 18-14 shows the formatted dates, in the en-US and fr-FR locales.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig14_HTML.jpg
Figure 18-14

Location-sensitive date formatting

Changing String Case

The uppercase and lowercase pipes convert all the characters in a string to uppercase or lowercase, respectively. Listing 18-23 shows both pipes applied to cells in the product table.
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name | uppercase }}</td>
        <td style="vertical-align:middle">{{item.category | lowercase }}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-23

Changing Character Case in the productTable.component.html File in the src/app Folder

These pipes use the standard JavaScript string methods toUpperCase and toLowerCase, which are not sensitive to locale settings, as shown in Figure 18-15.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig15_HTML.jpg
Figure 18-15

Changing character case

Serializing Data as JSON

The json pipe creates a JSON representation of a data value. No arguments are accepted by this pipe, which uses the browser’s JSON.stringify method to create the JSON string. Listing 18-24 applies this pipe to create a JSON representation of the objects in the data model.
<div class="bg-info p-2 text-white">
    <div>{{ getProducts() | json }}</div>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts(); let i = index; let odd = odd;
            let even = even" [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name | uppercase }}</td>
        <td style="vertical-align:middle">{{item.category | lowercase }}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-24

Creating a JSON String in the productTable.component.html File in the src/app Folder

This pipe is useful during debugging, and its decorator’s pure property is false so that any change in the application will cause the pipe’s transform method to be invoked, ensuring that even collection-level changes are shown. Figure 18-16 shows the JSON generated from the objects in the example application’s data model.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig16_HTML.jpg
Figure 18-16

Generating JSON strings for debugging

Slicing Data Arrays

The slice pipe operates on an array or string and returns a subset of the elements or characters it contains. This is an impure pipe, which means it will reflect any changes that occur within the data object it is operating on but also means that the slice operation will be performed after any change in the application, even if that change was not related to the source data.

The objects or characters selected by the slice pipe are specified using two arguments, which are described in Table 18-9.
Table 18-9

The Slice Pipe Arguments

Name

Description

start

This argument must be specified. If the value is positive, the start index for items to be included in the result counts from the first position in the array. If the value is negative, then the pipe counts back from the end of the array.

end

This optional argument is used to specify how many items from the start index should be included in the result. If this value is omitted, all the items after the start index (or before in the case of negative values) will be included.

Listing 18-25 demonstrates the use of the slice pipe in combination with a select element that specifies how many items should be displayed in the product table.
<div>
    <label>Number of items:</label>
    <select [value]="itemCount || 1" (change)="itemCount=$event.target.value">
        <option *ngFor="let item of getProducts(); let i = index" [value]="i + 1">
            {{i + 1}}
        </option>
    </select>
</div>
<table class="table table-sm table-bordered table-striped">
    <tr><th></th><th>Name</th><th>Category</th><th>Price</th><th></th></tr>
    <tr *paFor="let item of getProducts() | slice:0:(itemCount || 1);
            let i = index; let odd = odd; let even = even"
            [class.bg-info]="odd" [class.bg-warning]="even">
        <td style="vertical-align:middle">{{i + 1}}</td>
        <td style="vertical-align:middle">{{item.name | uppercase }}</td>
        <td style="vertical-align:middle">{{item.category | lowercase }}</td>
        <td style="vertical-align:middle">
            {{item.price | addTax:(taxRate || 0) | currency:"USD":"symbol":"2.2-2" }}
        </td>
        <td class="text-center">
            <button class="btn btn-danger btn-sm" (click)="deleteProduct(item.id)">
                Delete
            </button>
        </td>
    </tr>
</table>
Listing 18-25

Using the slice Pipe in the productTable.component.html File in the src/app Folder

The select element is populated with option elements created with the ngFor directive. This directive doesn’t directly support iterating a specific number of times, so I have used the index variable to generate the values that are required. The select element sets a variable called itemCount, which is used as the second argument of the slice pipe, like this:
...
<tr *paFor="let item of getProducts() | slice:0:(itemCount || 1);
    let i = index; let odd = odd; let even = even"
    [class.bg-info]="odd" [class.bg-warning]="even">
...
The effect is that changing the value displayed by the select element changes the number of items displayed in the product table, as shown in Figure 18-17.
../images/421542_3_En_18_Chapter/421542_3_En_18_Fig17_HTML.jpg
Figure 18-17

Using the slice pipe

Summary

In this chapter, I introduced pipes and explained how they are used to transform data values so they can be presented to the user in the template. I demonstrated the process for creating custom pipes, explained how some pipes are pure and others are not, and demonstrated the built-in pipes that Angular provides for handling common tasks. In the next chapter, I introduce services, which can be used to simplify the design of Angular applications and allow building blocks to easily collaborate.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.123.120