در
قسمت قبل بیان شد که Angular برای اعمال Pipe بر روی Template expressions باید تمامی رخدادهای برنامه را تحت نظر قرار داده و با مشاهدهی هر تغییری بر روی عبارت ورودی Pipe، فراخوانی Pipe را آغاز کند. از جمله این رخدادها میتوان به رخدادهای mouse move، timer tick، server response و فشرده شدن کلیدهای ماوس و یا کیبورد اشاره کرد. واضح است که بررسی تغییرات عبارت در این همه رخداد میتواند مخرب باشد و بر روی کارآئی (Performance) تاثیر منفی خواهد گذاشت. اما Angular برای حل این مشکل و همچنین هنگام مشاهده سریع تغییرات هنگام استفاده از Pipeها، الگوریتمهای سریع و سادهای در نظر گرفته است که آنها را در این بخش مورد برسی قرار خواهیم داد.
Pipeهای Pure و Impure
Pipeها کلا در دو دستهی Pure و Impure قرار میگیرند. هنگام ساخت Pipe سفارشی در صورتیکه نوع Pipe مشخص نشود، به صورت پیش فرض از نوع Pure خواهد بود. برای تعریف Pipeهایی از نوع Impure کافی است در متادیتای Pipe@، پرچم Pure را به مقدار false تنظیم کنید.
@Pipe({ name: 'impurePipe', pure: false })
تفاوت این Pipeها در زمان فراخوانی دوباره آنها است.
Pure Pipe
این نوع Pipeها تنها زمانی فراخوانی مجدد میشوند که یک تغییر محض (Pure Change) بر روی عبارت ورودی آنها رخ دهد. هر نوع تغییری بر روی عبارات ورودی از جنس string ، number ، Boolean ، Symbol و عبارات اولیه، یا هرنوع تغییری در ارجاع یک شیء مانند Date ، Array ، Function و Object نیز تغییر محض محسوب میشود. به عنوان مثال هیچکدام از تغییرات زیر یک تغییر محض محسوب نمیشوند:
numbers.push(10);
obj.name = ‘javad’;
زیرا با اضافه شدن عنصری به یک آرایه یا تغییر خصوصیتی از یک شیء، باعث تغییری در ارجاع آنها نمیشود و همانطور که اشاره شد، در عبارات از نوع آرایه و Object، فقط تغییر در ارجاع آنها یک تغییر محض محسوب میشود.
حالا میتوان به این نتیجه رسید که اضافه شدن مقداری به آرایه یا بهروزرسانی یک property از object، باعث فراخوانی مجدد Pure Pipe نخواهد نشد. شاید این نوع از Pipeها محدود کننده باشند، اما بسیار سریع هستند (برسی تغییر در ارجاع یک شیء بسیار سریعتر از بررسی کامل یک شیء، صورت میگیرد).
Impure Pipe
این نوع Pipeها در اغلب رخدادهای کامپوننت از جمله فشره شدن کلید یا حرکت ماوس و رخدادهای دیگر فراخوانی مجدد میشوند. با در نظر گرفتن این نگرانی، هنگام پیاده سازی این نوع Pipeها باید مراقب بود؛ زیرا این نوع Pipeها با اجرای طولانی خود میتوانند رابط کاربری شما را نابود کنند. برای درک کامل تفاوت این دو نوع از Pipeها مثالی را دنبال میکنیم.
مثال: قصد داریم Pipe سفارشی را پیاده سازی کنیم تا آرایهای از اعداد را دریافت و فقط اعداد زوج را فیلتر کرده و نمایش دهد.
برای این منظور
یک فایل جدید را با نام even-numbers.pipe.ts با محتویات زیر ایجاد میکنیم:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'evenNumbers'
})
export class EvenNumbersPipe implements PipeTransform {
transform(numbers: Array<number>): Array<number> {
var x=numbers.filter(r => r % 2 == 0);
return x;
}
}
همانطور که مشخص است این Pipe در متد transform، آرایهای از اعداد را دریافت کرده و فقط اعداد زوج را بازگشت میدهد. حالا باید Pipe تعریف شده خود را در AppModule در قسمت declares تعریف کنیم.
// . . .
import { EvenNumbersPipe } from './pipes/even-numbers.pipe'
@NgModule({
declarations: [
. . .
EvenNumbersPipe
],
. . .
})
export class AppModule { }
سپس در کامپوننت مورد نظر خود متغیری را به نام numbers از نوع آرایه، با مقدار اولیهی اعداد از یک تا ده، تعریف میکنیم:
numbers: Array<number> = [1,2,3,4,5,6,7,8,9,10];
برای نمایش این اعداد در رابط کاربری تگهای زیر را به قالب کامپوننت خود اضافه میکنیم:
<h1>All numbers</h1>
<span *ngFor="let number of numbers">
{{number}}
</span>
همچنین با استفاده از تگهای زیر یک input برای اضافه کردن مقدار جدید به آرایه درنظر میگیریم:
<p>
<input type="text" #number />
<input type="button" (click)="numbers.push(number.value)" value="Add number"/>
</p>
تگهای زیر را نیز برای اعمال Pipe نمایش اعداد زوج، به قالب کامپوننت اضافه میکنیم:
<h1>even numbers</h1>
<span *ngFor="let number of numbers | evenNumbers">
{{number}}
</span>
بعد از اجرای برنامه، یک عدد جدید زوج را به آرایه اضافه کنید. متوجه خواهید شد با اینکه لیست اعداد در قسمت All numbers بهروز میشوند، ولی Pipe، متوجه تغییری بر روی آرایه نشدهاست و همچنان اعداد قبلی را نمایش میدهد. دلیل این امر همانطور که قبلا نیز اشاره شد، بخاطر Pure بودن Pipe و عدم فراخوانی مجدد این نوع Pipeها در زمان اضافه شدن مقداری به آرایه یا تغییری در خصوصیت یک شیء است.
برای حل این مشکل، هنگام اضافه شدن عدد به آرایه، اگر ارجاع آرایه را تغییر دهیم، Pure Pipe متوجه تغییرات خواهد شد و لیست اعداد را بهروز رسانی میکند (تغییر در ارجاع یک شیء، از نوع تغییرات محض است):
<p>
<input type="text" #number />
<input type="button" (click)="numbers = numbers.concat(number.value)" value="Add number"/>
</p>
با تغییر نحوه اضافه شدن عنصر به آرایه به شکل بالا خواهیم دید که با افزودن اعداد جدید، لیست اعداد زوج نیز در لحظه اعمال خواهند شد. این راهحل همیشه کارآمد نخواهد بود. همیشه تشخیص محل اضافه شدن عنصر به آرایه در برنامه کار سادهای نیست تا در آنجا ارجاع آرایه را نیز تغییر دهیم. راهحل، استفاده از Impure Pipe است. کافی است متادیتای Pipe@ را هنگام تعریف به شکل زیر تغییر دهید:
@Pipe({
name: 'evenNumbers',
pure: false
})
export class EvenNumbersPipe implements PipeTransform {
//…
}
کسانیکه با Angular 1.x آشنایی دارند، شاید اکنون متوجه این شدهاند که چرا در Angular به مشابه Angular 1.x دیگر خبری ازfilter و orderBy نیست. با توجه به اینکه این دو فیلتر فقط با عبارات از نوع object سروکار داشتند، پیادهسازی آنها فقط با Impure Pipeها امکان پذیر بود و با توجه به اینکه Impure Pipeها در هر بار چرخه تغییرات کامپوننت اجرا خواهند شد، باعث کندی در صفحات خواهند شد.