I. Lời mở đầu
– Một ứng dụng về cơ bản sẽ luôn có một công việc cơ bản: Lấy dữ liệu => Biển đổi (Xào, nấu, nặn nó) => Hiện thị cho User trên giao diện.
Rõ ràng việc hiển thị ngày kiểu February 14, 2019 sẽ nhìn tinh gọn hơn nhiều một dãy dài ngoằn sau:
Thu Feb 14 2019 00:00:00 GMT-0700 (Pacific Daylight Time)….
Rõ ràng chỉ cần một chút chỉnh sửa thôi đã mang lại các giá trị tức thời dễ nhận ra. Và không ai lại không muốn những chỉnh sửa định dạng này chỉ áp dụng một lần hoặc nói cách khác là nếu việc chỉnh sửa định dạng dễ dàng và áp dụng được nhanh chóng thì “nu” gì không xài :v
Và đây…..
II. Angular Pipe – Cho cuộc sống với dữ liệu êm nồng hơn
Pipe là một tính năng được xây dựng sẵn từ Angular 2 với mục tiêu nhằm biến đổi dữ liệu đầu ra hoặc hình tượng mà nói như đúng ý nghĩa đen của nó – pipe (ống nước) giúp sàn lọc (filter) và ở đây là sàn lọc dữ liệu (Asynchronous data & Synchronouse data) để hiển thị lại trên template.
🦊 Có hai loại Pipe:
1. Pure Pipe (Trinh trắng :3 )
2. Impure Pipe (Vấy bẩn :v)
1.Pure Pipe
Bạn sẽ thường bắt gặp “cái ổng nước” này ở trên giao diện HTML. Nó thường kết hợp với kỹ thuật ‘String Interpellation’ và cả có cả ‘Directive’ trong Angular.

- Q: Có bạn thắc mắc là ủa mình định dạng data mà, còn Directive (ngIf, ngFor nó có phải data đâu)???!
- A: ‘E hèm lý do đây: Vì như đã nói pipe filter output và directive cũng là một phần của output data, kiểu như chặn “cái ống nước” ở đầu ra của lệnh For (mục tiêu của For cùng là để lấy data mà)’ – Mình sẽ nói rõ hon trong phần Custom Pipe.
// Basic Example for pure pipe with String Interpellation
import { Component } from '@angular/core';
@Component({
selector: 'app-hero-birthday',
template: `<p>The hero's birthday is {{ birthday | date }}</p>`
})
export class HeroBirthdayComponent {
birthday = new Date(1988, 3, 15); // April 15, 1988
}a. Built-in Pipe (Hàng có sẵn để dùng)
Có tương đối đầy đủ các Pipe với các định dạng cơ bản. Nói cơ bản thôi chứ để vận dụng thuần thục thì cũng ‘không phải dạng vừa đâu’!
🐻 Vào đây để search các loại cơ bản (Search Pipe):
https://angular.io/api?query=pipe

- Q: Nếu mọi người để ý sẽ không thấy hai loại FilterPipe và OrderByPipe mà rõ ràng chúng rất hay sử dụng. Uả đâu rồi ta ??!
- A: Không phải người ta bỏ sót đâu. Lý do tụi nó không được hỗ trợ vì theo như Angular Team nói hiệu suất chúng khá là tệ và đặc biệt cản trở kỹ thuật minification (Kỹ thuật xử lý để bỏ đi các khoảng trống dư thừa, xuống dòng và comment trong code để giảm đi cỡ và cải thiện thời gian load).
Parameterizing a pipe – Pipe cũng có tham số nhé
Việc bỏ thêm các tham số của Pipe sẽ cho tao khả năng tùy chỉnh Pipe hiệu quả hơn và đúng như mong muốn của ta hơn mà không cần phải tạo ra quá nhiều Pipe.
- Cú pháp [<PipeName>] : (Parameter1): (Parameter2)
<p>The hero's birthday is {{ birthday | date:"MM/dd/yy" }} </p>Chaining Pipe – Trò chơi nối ống
Một ống không đủ thỏa mãn thì ta “đút” thêm ống khác :3 Rõ ràng là trò chơi thân thiện mà nhỉ !!!
{{ birthday | date:'fullDate' | uppercase}}Lưu ý mạnh: Thứ tự các Pipe khá quan trọng! Ví dụ ở trên nếu ta đổi chỗ 2 pipe date và uppercase cho nhau thì sẽ ‘bay màu’ output. Lý do thì mình chưa rõ ai biết thì inb trực tiếp page để mình edit lại nhen plzz ^^
Mình chỉ rõ là nó sẽ làm như sau: đầu tiên date sẽ áp vào birthday rồi uppercase sẽ áp vào nguyên cụm nó ở phía trước nếu ngược lại thì uppercase áp vào cụm phía trước
b. Cách đổi locale
Mình muốn tách phần này thành phần riêng vì phần Update này khá quan trọng. Hiện thì toàn bộ dự án chúng ta đang dùng hằng ngày locale mặc định là en-US. NHƯNG nếu chúng ta đang xây dựng một dự án “thuần Việt” thì mọi chuyện lại khác.
Pipe mặc nhiên sẽ trả về định dạng en-US. Ví dụ:
{{ row.NgayBatDau | date }} // Feb 14,2019Mặc dù trong tài liệu có ghi chỉ cần chaning với locale country của bạn. Ví dụ với DatePipe:
{{ value_expression | date [ : format [ : timezone [ : locale ] ] ] }}
NHƯNG đời không như mơ, nếu easy như vậy thì không có gì bàn rồi cơ mà sure kèo nếu chỉ vậy thì Angular nó “lì” lắm không chịu convert cho các bạn đâu. Và đây là giải pháp:
import { NgModule, LOCALE_ID } from '@angular/core';
import localeGB from '@angular/common/locales/vi';
registerLocaleData(localeGB);
@NgModule({
declarations: [
AppComponent
],
imports : [
],
bootstrap : [
AppComponent
],
providers: [
{ provide: LOCALE_ID, useValue: "vi" }
]
})- Nếu các bạn để ý rõ ràng mặc dù locale VietNam phải là “vi_VN” nhưng ta khai báo chỉ là “vi”.
- Phần import localeGB nếu các bạn extend ngay chỗ vi (Ctrl+Space) các bạn cũng chỉ tìm được mỗi vi.
Dưới đây là link danh sách các locale để các bạn tham khảo:
https://saimana.com/list-of-country-locale-code/
c. Custom Pipe – Tạo ra đồ chơi của riêng bạn
Rõ ràng hàng có sẵn thì sao ngon bằng đồ mod nhỉ :v Okay đến lúc để lại dấu ấn riêng rồi. Mình sẽ có giới thiệu càng nhiều kiểu càng tốt! Nêu về sau có thêm sẽ bổ sung thêm. Các bạn coi thêm phần ngày đăng ở đầu bài để cập nhật tình hình cập nhât nhen.
🐯 Vậy một Custom Pipe sẽ như thế nào >>>
- Nó là một Class và được ‘trang trí’ bằng metadata (Là nguyên cái phần @Pipe á)
- Nó buộc phải implement PipeTransform interface và có khai báo phương thức transform (valueToTransform: <type>, Chaining1stParameter: <type>, …)
- Nó phải có khai báo @Pipe decorator (Đây là nơi khai báo tên của pipe mà sau đó bạn sẽ sử dụng trong template expressions – giống như cách sử dụng built-in pipe í. Lưu ý là phải đúng định danh JS – không để bị trùng)
- Nó phải được khai báo trong declarations: [ ] trong file __.module.ts

import { Pipe, PipeTransform } from '@angular/core';
/*
* Raise the value exponentially
* Takes an exponent argument that defaults to 1.
* Usage:
* value | exponentialStrength:exponent
* Example:
* {{ 2 | exponentialStrength:10 }}
* formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
transform(value: number, exponent: string): number {
let exp = parseFloat(exponent);
return Math.pow(value, isNaN(exp) ? 1 : exp);
}
}import { Component } from '@angular/core';
@Component({
selector: 'app-power-booster',
template: `
<h2>Power Booster</h2>
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
`
})
export class PowerBoosterComponent { }
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filter',
pure: false
})
export class FilterPipe implements PipeTransform {
transform(value: any, filterString: string, propName: string): any {
if (value.length === 0 || filterString === '') {
return value;
}
const resultArray = [];
for (const item of value) {
if (item[propName] === filterString) {
resultArray.push(item);
}
}
return resultArray;
}
}
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(value: any, limit: number) {
if (value.length > limit) {
return value.substr(0, limit) + ' ...';
}
return value;
}
}2. Impure Pipe
a. Một chút Hack não
Okay trước khi đến với Impure Pipe thì ta điểm qua qua một vấn của đề Hack não của Pure Pipe bên dưới đây. Sau đây là code của 4 file:
flying-heroes.component.html ,
flying-heroes.component.ts ,
flying-heroes.pipe.ts ,
heroes.ts (Model + Initialized Array)
< flying-heroes.component.html >
New hero:
<input type="text" #box
(keyup.enter)="addHero(box.value); box.value=''"
placeholder="hero name">
<input id="can-fly" type="checkbox" [(ngModel)]="canFly"> can fly
</p>
<h4>Heroes who fly (piped)</h4>
{{hero.name}}
</div>
<h4>All Heroes (no pipe)</h4>
{{hero.name}}
</div>< flying-heroes.component.ts >
export class FlyingHeroesComponent {
heroes: any[] = [];
canFly = true;
constructor() { this.reset(); }
addHero(name: string) {
name = name.trim();
if (!name) { return; }
let hero = {name, canFly: this.canFly};
this.heroes.push(hero);
}
reset() { this.heroes = HEROES.slice(); }
}
< flying-heroes.pipe.ts >
import { Pipe, PipeTransform } from '@angular/core';
import { Flyer } from './heroes';
@Pipe({ name: 'flyingHeroes' })
export class FlyingHeroesPipe implements PipeTransform {
transform(allHeroes: Flyer[]) {
return allHeroes.filter(hero => hero.canFly);
}
}
< heroes.ts >
export interface Flyer { canFly: boolean; }
export const HEROES = [
{name: 'Windstorm', canFly: true},
{name: 'Bombasto', canFly: false},
{name: 'Magneto', canFly: false},
{name: 'Tornado', canFly: true}
];
(Nhìn code hiều rồi thì chuyển xuống đoạn thứ 2 nhen :v Đoạn dưới là diễn dịch văn xuôi thôi)
—Như ta thấy Custom Pipe của ta có nhiệm vụ lọc các ‘anh hùng’ có khả năng bay ‘canFly:true’. Ở file _html sẽ có một sự kiện được theo dõi (Đây là cơ chế change detection process) là (keyup.enter) với kết quả là trigger hàm addHero bên file _.ts với chức năng là đẩy name cùng với giá trị của checkbox (tương ứng với ‘khả năng bay được hay không’) vào mảng heroes (
this.heroes.push(hero) ) và hiển thị ngay sau đó bên dưới. —
Okay nói chung thì chương trình chẳng có vấn đề gì khó hiểu nhưng vấn đề là ‘cái ống nước’ của chúng ta lúc này nó không lọc được dữ liệu @@
Okay Fineeeee….
Tới lúc ‘đạo hàm, tích phân… google’ để tìm lý do rồi. Processing….
Okay lý do như sau: Như các bạn đã biết hoặc chưa biết thì giờ biết – Angular tìm kiếm các thay đổi đối với các giá trị ràng buộc dữ liệu thông qua cái gọi là change detection process và việc này sẽ thực hiện sau từng sự kiện DOM (‘every keystroke, mouse move, timer tick, and server response’). Và thực ra sau này team Angular nhận ra việc này cực kì tiêu tốn tài nguyên và vì vậy họ nổ lực tối giản nó, tìm ra cách phù hợp hơn.
Đấy, lý do đấy :v Như ví dụ ở trên, khi bạn thêm vào mảng heroes thì cái mảng đó cho dù thay đổi số phần tử nó vẫn là cái mảng đó. Vậy nếu như trước Angular liên tục phải theo dõi sự thay đổi của cái mảng đó thì rõ ràng sẽ tốn tài nguyên. Và chính vì không muốn như vậy họ mới làm lại thuật toán change detection mới, nó sẽ khiến Angular hiểu như thế này:
same array, no change, no display update.
Như vậy cách khắc phục là tạo ra một mảng mới, thêm ‘anh hùng’ vào mảng đó và sau đó gán mảng đó lại vào mảng heroes (Okay nghe tới đây thì ta sẽ ngâm khúc ca than thở rằng có mỗi cái thêm thôi mà nghe ghê vậy – Thật ra thì cả đoạn trên ta chỉ cần gọi mỗi 1 hàm concat( ) thôi p/s: Cái tên hình tượng vãi :v ). Như vậy lúc này Pipe sẽ được thực hiện và danh sách anh hùng biết bay sẽ được cập nhật trên giao diện.
// Replace this.heroes.push(hero) by below code
this.heroes = this.heroes.concat(hero);
b. Ẻm đây rồi
Okay vậy thì nếu như vì một lý do nào lớn ta muốn chơi lớn (Tài nguyên thì anh không thiếu chí thiếu một lý do thôi :v), muốn Angular thực hiện (Impure) Pipe sau từng component change detection cycle (Hiểu nôm na là theo dõi thay đổi liên tục í) thì chỉ cần làm như sau:
@Pipe({
name: 'flyingHeroesImpure',
pure: false
})
Đúng rồi đó, chỉ cần cập nhật metadata bằng việc thay đổi pure thành false.
c. The impure AsyncPipe – Kẻ mà ai cũng biết (nhìn thấy hoàiiiii).
AsyscPipe là một ví dụ thú vị về Impure Pipe. Nó chấp nhập Promise hoặc Observable như một input (Khi trước là imput là một String Interpellation hoặc một dữ liệu nào đó lấy ra từ Directive á) và subscribes nó một cách tự động, cuối cùng nó trả về emitted values.
Các khái niệm về Promise, Observable hay Emiited Values/Events mình sẽ có bài viết cụ thể sau nhen. Các bạn nhớ đón xem và theo dõi channel cũng như Page, Blog mình thường xuyên nhen ^^
Đặc biệt design của AsynPipe là Statefule (Các bạn google trước để hiểu trước mình sẽ bổ sung bài viết khác để giải thích rõ hơn) – Ở đây có thể hiểu stateful là AsyncPipe sẽ duy trì Subcription với Observable input và giữ cho việc cung cấp các giá trị từ Observable đó khi chúng đến. (Ngắn gọn là duy trì theo dõi í)
Sau đây sẽ là ví dụ về việc binding biến messing$ Observable<string> (Cái dấu $ đó là ký hiệu cho biến Observable thui à – https://bit.ly/2Hc5gOe) để hiện lên view với Async Pipe
import { Component } from '@angular/core';
import { Observable, interval } from 'rxjs';
import { map, take } from 'rxjs/operators';
@Component({
selector: 'app-hero-message',
template: `
<h2>Async Hero Message and AsyncPipe</h2>
<p>Message: {{ message$ | async }}</p>
<button (click)="resend()">Resend</button>`,
})
export class HeroAsyncMessageComponent {
message$: Observable<string>;
private messages = [
'You are my hero!',
'You are the best hero!',
'Will you be my hero?'
];
constructor() { this.resend(); }
resend() {
this.message$ = interval(500).pipe(
map(i => this.messages[i]),
take(this.messages.length)
);
}
}
Giải thích xíu hen: interval(500) là hàm emitting data (có thể hiểu là khởi tạo) tự động các giá trị là số nguyên tăng dần từ 0 với (tham số) là thời gian đợi mỗi tăng (1000 = 1s). Như nói ở trên pipe sẽ duy trì trạng thái theo dõi nhờ vậy sẽ không bỏ sót bất kì giá trị nào được tạo ra. Hàm map( ) thì mục đích là thao tác với các dữ liệu i (là giá trị tao ra của hàm interval) nhận được (i sẽ tương ứng với các vị trí phần tử trong mảng messages) ; Kết hợp với take( ) lấy ra số lượng các phần tử trong mảng messages và sau hết tất cả message$ sẽ nhận từng giá trị trong mảng messages và được in trên giao diện theo thứ tự.
Uả giải thích nãy giờ vậy thằng async pipe trên _.html nó làm gì?! :3 Nó sẽ giúp bạn có thể in ra giao diện mà không cần phải viết thêm code nữa. Vì bình thường để in ra / thao tác gì với các giác trị được map( ) vào Observable property thì mấy chế phải subcribe tụi nó. Ở ví dụ trên thì phải thêm đoạn code block như thế này:
message$.subscribe(
(number: number) => {
console.log(number);
}
);
d. An Impure Caching Cache
Bây giờ chúng ta cùng vọc một ‘ống nước’ mới giúp lấy dữ liệu bằng HTTP request.
- Lưu ý khi bạn tao một impure pipe liên quan đến request, nghĩa là nó sẽ được gọi mỗi milliseconds, thì hay tuyệt đối cẩn thận – nếu bạn không cẩn thận thì nó rất dễ gây hại/sập server với hàng tá requests.
import { HttpClient } from '@angular/common/http';
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'fetch',
pure: false
})
export class FetchJsonPipe implements PipeTransform {
private cachedData: any = null;
private cachedUrl = '';
constructor(private http: HttpClient) { }
transform(url: string): any {
if (url !== this.cachedUrl) {
this.cachedData = null;
this.cachedUrl = url;
this.http.get(url).subscribe(result => this.cachedData = result);
}
return this.cachedData;
}
Custom Impure Pipe trên sẽ chỉ gọi server khi và chỉ khi request URL thay đổi (Nhờ vậy tránh tình trạng gây nguy hiểm cho server như nói trên). Sau đó nó sẽ cache (lưu) lại server response. Bên dưới là cách dùng:
< _.component.ts >
import { Component } from '@angular/core';
@Component({
selector: 'app-hero-list',
template: `
<h2>Heroes from JSON File</h2>
{{hero.name}}
<p>Heroes as JSON:
{{'assets/heroes.json' | fetch | json}}
</p>`
})
export class HeroListComponent { }
< heroes.json >
[
{"name": "Windstorm", "canFly": true},
{"name": "Bombasto", "canFly": false},
{"name": "Magneto", "canFly": false},
{"name": "Tornado", "canFly": true}
]
Để đơn giản thì dùng File json để tượng trưng cho response của server vì dữ liệu mình lấy từ server cũng ở định dạng json mà :v .Một số lưu ý rút ra sau đây đối với caching pipe ở trên các bạn nên xem qua:
- Each binding gets its own pipe instance. (Mỗi một lần bind dữ liệu là tạo 1 pipe instance)
- Each pipe instance caches its own URL and data. (Mỗi instance thì caches URL và data riêng của nó – Vì trong custome pipe của mình xây dựng cache data mà)
- Each pipe instance only calls the server once.
III. Túm cái váy lại
Okay, chương trình ngày hôm nay sẽ giúp bạn xài “ống nước” như một người sửa ổng nước chuyên nghiệp :v Ống nước thật sự rất tiện dụng, bạn chỉ cần biết lắp ở đâu và lắp loại ống nào thì sẽ cho dòng suối (dòng dữ liệu) tuôn trào như cách bạn mong muốn.
Mọi thắc mắc hoặc phản hồi mình rất vui lòng nhận, các bạn có thể inb trực tiếp vào page CarrY4U của mình hoặc ngay trên Channel Youtube. Mình sẽ còn trở lại với nhiều bài viết hơn, mong các bạn ủng hộ nhen.
[Carry by: TungTTr – Date: 24/02/2019]
[Update: 03/03/2019 – Impure Caching Pipe]
[Update: 04/04/2019 – Chaining Locale Pipe / Language System]

