امکان تعریف قالب‌ها در Angular با دایرکتیو ng-template
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: چهار دقیقه

معرفی دایرکتیو ng-template

همانطور که از اسم آن نیز مشخص است، 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/else است. در این مثال اگر آرایه‌ی فرضی دروس دارای عضوی باشد، div مرتبط نمایش داده می‌شود؛ در غیراینصورت، قالبی که توسط یک template reference variable به نام loading مشخص شده‌است، نمایش داده خواهد شد (loading‌# در اینجا).
هرچند در پشت صحنه برای حالت ngIf نیز یک ng-template ضمنی محصور کننده‌ی div اصلی تشکیل می‌شود که از دید ما پنهان است.

استفاده از ngIf برای نمایش یک قالب، یکی از روش‌های کار با آن‌ها است. روش دیگر، استفاده از ng-container است:
<ng-container *ngTemplateOutlet="loading"></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};
}
در اینجا قالب تعریف شده، توسط پیشوند -let دارای یک متغیر ورودی به نام lessonsCounter شده‌است (می‌تواند چندین متغیر ورودی داشته باشد). شکل کلی آن به صورت "let-{{templateVariableName}}=”contextProperty است.
 این متغیر 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>
در این حالت مقدار default از خاصیت implicit$ شیء منتسب به context دریافت می‌شود:
export class AppComponent {
  exampleContext = {
    $implicit: 'default context property when none specified'
  };
}

نکته 2:
نحوه‌ی تعریف شیء context را به صورت ذیل نیز می‌توان مشخص کرد:
 [ngOutletContext]="exampleContext"


دسترسی به قالب‌ها در کدهای کامپوننت‌ها

در اینجا قالبی را مشاهده می‌کنید که توسط یک template reference variable به نام defaultTabButtons مشخص شده‌است:
<ng-template #defaultTabButtons>
</ng-template>
برای دسترسی به آن در کدهای کامپوننت مرتبط، می‌توان از طریق تعریف یک ViewChild هم نام با این متغیر استفاده کرد:
export class AppComponent implements OnInit {
   @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>;
   ngOnInit() {
      console.log(this.defaultTabButtonsTpl);
   }
}
در اینجا متغیر defaultTabButtonsTpl با ویژگی ViewChild مزین شده‌است. البته این یک روش عمومی برای دسترسی به تمام عناصر DOM در کدهای یک کامپوننت می‌باشد.

یکی از کاربردهای این قابلیت، امکان تعویض پویای قالب‌های یک دربرگیرنده‌است:
<ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons">
</ng-container>
توسط دایرکتیو ساختاری ngTemplateOutlet می‌توان در زمان اجرا، قالب‌های مختلفی را توسط کدهای کامپوننت مشخص کرد.
در اینجا headerTemplate خاصیتی است عمومی از نوع TemplateRef که در کدهای کامپوننت متناظر با این قالب مقدار دهی می‌شود. اگر این مقدار دهی صورت نگیرد، از قالب از پیش موجود defaultTabButtons استفاده خواهد کرد.
همچنین اگر می‌خواهیم به selector یک کامپوننت قابلیت انتخاب قالبی را بدهیم می‌توان یک خاصیت عمومی مزین شده‌ی با Input از نوع TemplateRef را مشخص کرد:
 @Input() headerTemplate: TemplateRef<any>;
در این حالت این کامپوننت ویژه می‌تواند به صورت ذیل، قالب خودش را با انتساب به این خاصیت عمومی دریافت کند:
 <tab-container [headerTemplate]="defaultTabButtons"></tab-container>
  • #
    ‫۶ سال و ۱۰ ماه قبل، دوشنبه ۱۵ آبان ۱۳۹۶، ساعت ۱۵:۵۳
    به روز رسانی
    در Angular 5، خاصیت ngOutletContext به ngTemplateOutletContext تغییر نام یافته‌است.  
  • #
    ‫۶ سال و ۵ ماه قبل، چهارشنبه ۲۲ فروردین ۱۳۹۷، ساعت ۱۹:۲۰
    یک نکته‌ی تکمیلی: ترکیب ngIf و ngFor بر روی یک المان

    فرض کنید می‌خواهید در همان حالیکه عنصری را در طی یک حلقه نمایش می‌دهید،  از همان آیتم جاری برای تشخیص یکی از خاصیت‌های آن نیز استفاده کنید:
    <td *ngFor="let item of headerItems" *ngIf="item.visible">{{ item?.name }}</td>
    یک چنین ترکیبی در Angular مجاز نیست و راه حل پیشنهاد شده‌ی آن استفاده از ng-container است:
    <ng-container *ngFor="let item of headerItems">
     <td *ngIf="item.visible">{{ item?.name }}</td>
    </ng-container>
    مزیت مهم آن عدم درج ng-container در DOM است. برای مثال قصد نداریم یک div اضافی را داخل تعاریف یک جدول قرار دهیم و آن‌را از شکل استاندارد خارج کنیم.