معرفی دایرکتیو ng-template
همانطور که از اسم آن نیز مشخص است، ng-template به معنای قالب انگیولار است و هدف از آن، ارائهی قسمتی از قالب نهایی یک کامپوننت میباشد. فریم ورک Angular از دایرکتیو ng-template در پشت صحنهی دایرکتیوهای ساختاری مانند ngIf، ngFor و ngSwitch استفاده میکند. برای مثال، قسمت if، تبدیل به یک ng-template شده و else آن نیز تبدیل به یک ng-template ضمنی دیگر خواهد شد.
روش فعالسازی و نمایش قالبها
باید دقت داشت که تعریف یک ng-template سبب رندر هیچگونه خروجی در صفحه نمیشود و باید به طریقی درخواست فعالسازی و رندر آنرا ارائه داد.
یکی از روشهای معمول نمایش قالبها، استفاده از ngIf/else است. در این مثال اگر آرایهی فرضی دروس دارای عضوی باشد، div مرتبط نمایش داده میشود؛ در غیراینصورت، قالبی که توسط یک template reference variable به نام loading مشخص شدهاست، نمایش داده خواهد شد (loading# در اینجا).
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کنندهی div اصلی تشکیل میشود که از دید ما پنهان است.
استفاده از ngIf برای نمایش یک قالب، یکی از روشهای کار با آنها است. روش دیگر، استفاده از ng-container است:
در اینجا دایرکتیو ساختاری ngTemplateOutlet، قالبی را که توسط loading# مشخص شدهاست، وهله سازی کرده و به درون ng-container تزریق میکند که در این حالت سبب نمایش آن نیز خواهد شد.
سطوح دسترسی در قالبها
اکنون این سؤال مطرح است: «آیا یک قالب میدان دید متغیرهای خاص خودش را دارد؟ این قالب به چه متغیرهایی دسترسی دارد؟»
درون بدنه یک تگ ng-template، به همان متغیرهایی که در قالب خارجی آن قابل دسترسی هستند، دسترسی خواهیم داشت؛ برای نمونه در مثال فوق به همان متغیر lessons. به عبارتی تمام وهلههای ng-templateها، به همان متغیرهای زمینهی قالبی که درون آن جایگرفتهاند، دسترسی دارند. به علاوه هر قالب میتواند متغیرهای خاص خود را نیز تعریف کند.
در ادامه قالب یک کامپوننت را به صورت ذیل فرض کنید:
با کدهای ذیل
در اینجا قالب تعریف شده، توسط پیشوند -let دارای یک متغیر ورودی به نام lessonsCounter شدهاست (میتواند چندین متغیر ورودی داشته باشد). شکل کلی آن به صورت "let-{{templateVariableName}}=”contextProperty است.
این متغیر lessonsCounter تنها داخل این قالب است که قابل مشاهده و دسترسی میباشد و نه خارج از آن. مقدار این متغیر نیز توسط عبارت estimate تامین میشود. این عبارت زمانیکه ng-container سبب وهله سازی estimateTemplate میشود، توسط شیء ویژهای به نام context مقدار دهی خواهد شد.
برای اینکه عبارت estimate در قالب، قابل استخراج از شیء context باشد، باین دقیقا خاصیتی به همین نام در این شیء تعریف شده باشد (و برای سایر متغیرها نیز به همین ترتیب). به همین جهت است که خاصیت عمومی ctx در کلاس AppComponent به صورت یک شیء دارای خاصیت estimate تعریف شدهاست تا بتوان نگاشتی را بین این مقدار و عبارت estimate برقرار کرد.
نکته 1:اگر در اینجا متغیری تعریف شود، اما محل تامین آن مشخص نگردد، به دنبال خاصیتی به نام implicit$ خواهد گشت. برای مثال در قالب ذیل، متغیر default تعریف شدهاست؛ اما عبارت تامین کنندهی آن مشخص نیست:
در این حالت مقدار default از خاصیت implicit$ شیء منتسب به context دریافت میشود:
نکته 2:نحوهی تعریف شیء context را به صورت ذیل نیز میتوان مشخص کرد:
دسترسی به قالبها در کدهای کامپوننتها
در اینجا قالبی را مشاهده میکنید که توسط یک template reference variable به نام defaultTabButtons مشخص شدهاست:
برای دسترسی به آن در کدهای کامپوننت مرتبط، میتوان از طریق تعریف یک ViewChild هم نام با این متغیر استفاده کرد:
در اینجا متغیر defaultTabButtonsTpl با ویژگی ViewChild مزین شدهاست. البته این یک روش عمومی برای دسترسی به تمام عناصر DOM در کدهای یک کامپوننت میباشد.
یکی از کاربردهای این قابلیت، امکان تعویض پویای قالبهای یک دربرگیرندهاست:
توسط دایرکتیو ساختاری ngTemplateOutlet میتوان در زمان اجرا، قالبهای مختلفی را توسط کدهای کامپوننت مشخص کرد.
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی میشود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر میخواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم میتوان یک خاصیت عمومی مزین شدهی با Input از نوع TemplateRef را مشخص کرد:
در این حالت این کامپوننت ویژه میتواند به صورت ذیل، قالب خودش را با انتساب به این خاصیت عمومی دریافت کند:
همانطور که از اسم آن نیز مشخص است، ng-template به معنای قالب انگیولار است و هدف از آن، ارائهی قسمتی از قالب نهایی یک کامپوننت میباشد. فریم ورک Angular از دایرکتیو ng-template در پشت صحنهی دایرکتیوهای ساختاری مانند ngIf، ngFor و ngSwitch استفاده میکند. برای مثال، قسمت if، تبدیل به یک ng-template شده و else آن نیز تبدیل به یک ng-template ضمنی دیگر خواهد شد.
روش فعالسازی و نمایش قالبها
باید دقت داشت که تعریف یک ng-template سبب رندر هیچگونه خروجی در صفحه نمیشود و باید به طریقی درخواست فعالسازی و رندر آنرا ارائه داد.
<div class="lessons-list" *ngIf="lessons else loading"> ... </div><ng-template #loading><div>Loading...</div></ng-template>
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کنندهی div اصلی تشکیل میشود که از دید ما پنهان است.
استفاده از ngIf برای نمایش یک قالب، یکی از روشهای کار با آنها است. روش دیگر، استفاده از ng-container است:
<ng-container *ngTemplateOutlet="loading"></ng-container>
سطوح دسترسی در قالبها
اکنون این سؤال مطرح است: «آیا یک قالب میدان دید متغیرهای خاص خودش را دارد؟ این قالب به چه متغیرهایی دسترسی دارد؟»
درون بدنه یک تگ ng-template، به همان متغیرهایی که در قالب خارجی آن قابل دسترسی هستند، دسترسی خواهیم داشت؛ برای نمونه در مثال فوق به همان متغیر lessons. به عبارتی تمام وهلههای ng-templateها، به همان متغیرهای زمینهی قالبی که درون آن جایگرفتهاند، دسترسی دارند. به علاوه هر قالب میتواند متغیرهای خاص خود را نیز تعریف کند.
در ادامه قالب یک کامپوننت را به صورت ذیل فرض کنید:
<ng-template #estimateTemplate let-lessonsCounter="estimate"><div> Approximately {{lessonsCounter}} lessons ...</div></ng-template><ng-container *ngTemplateOutlet="estimateTemplate;context:ctx"></ng-container>
export class AppComponent { totalEstimate = 10; ctx = {estimate: this.totalEstimate}; }
این متغیر lessonsCounter تنها داخل این قالب است که قابل مشاهده و دسترسی میباشد و نه خارج از آن. مقدار این متغیر نیز توسط عبارت estimate تامین میشود. این عبارت زمانیکه ng-container سبب وهله سازی estimateTemplate میشود، توسط شیء ویژهای به نام context مقدار دهی خواهد شد.
برای اینکه عبارت estimate در قالب، قابل استخراج از شیء context باشد، باین دقیقا خاصیتی به همین نام در این شیء تعریف شده باشد (و برای سایر متغیرها نیز به همین ترتیب). به همین جهت است که خاصیت عمومی ctx در کلاس AppComponent به صورت یک شیء دارای خاصیت estimate تعریف شدهاست تا بتوان نگاشتی را بین این مقدار و عبارت estimate برقرار کرد.
نکته 1:اگر در اینجا متغیری تعریف شود، اما محل تامین آن مشخص نگردد، به دنبال خاصیتی به نام implicit$ خواهد گشت. برای مثال در قالب ذیل، متغیر default تعریف شدهاست؛ اما عبارت تامین کنندهی آن مشخص نیست:
<ng-container *ngTemplateOutlet="templateRef; context: exampleContext"></ng-container><ng-template #templateRef let-default><div> '{{default}}'</div></ng-template>
export class AppComponent { exampleContext = { $implicit: 'default context property when none specified' }; }
نکته 2:نحوهی تعریف شیء context را به صورت ذیل نیز میتوان مشخص کرد:
[ngOutletContext]="exampleContext"
دسترسی به قالبها در کدهای کامپوننتها
در اینجا قالبی را مشاهده میکنید که توسط یک template reference variable به نام defaultTabButtons مشخص شدهاست:
<ng-template #defaultTabButtons></ng-template>
export class AppComponent implements OnInit { @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>; ngOnInit() { console.log(this.defaultTabButtonsTpl); } }
یکی از کاربردهای این قابلیت، امکان تعویض پویای قالبهای یک دربرگیرندهاست:
<ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons"></ng-container>
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی میشود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر میخواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم میتوان یک خاصیت عمومی مزین شدهی با Input از نوع TemplateRef را مشخص کرد:
@Input() headerTemplate: TemplateRef<any>;
<tab-container [headerTemplate]="defaultTabButtons"></tab-container>