احتمالاً قبلاً با دستورالعمل ng-template از Angular core مواجه شده اید، مثلاً هنگام استفاده از ngIf
/else یا ngSwitch
.
دستورالعمل ng-template و دستورالعمل ngTemplateOutlet ویژگی های بسیار قدرتمند Angular هستند که کاربرد های زیادی دارند.
این دستورالعملها اغلب با ng-container استفاده میشوند، و از آنجایی که این دستورالعملها برای استفاده با هم طراحی شدهاند، اگر همه آنها را یکجا یاد بگیریم، کمک بیشتری خواهند کرد.
توجه: تمام کدهای این پست را می توانید در این مخزن Github پیدا کنید .
در این پست به موضوعات زیر می پردازیم:
@Inputs
@Input
همانطور که از نام آن مشخص می شود، دستورالعمل ng-template یک template انگولار را نشان می دهد: این بدان معنی است که محتوای این تگ بخشی از یک template خواهد بود، که می توان آن را با سایر template ها ترکیب کرد تا قالب نهایی کامپوننت را تشکیل دهد.
انگولار در حال حاضر از ng-template در لایه های زیرین بسیاری از دستورالعملهای ساختاری استفاده میکند که ما همیشه از آنها استفاده میکنیم مثلngIf
، ngFor
و ngSwitch
.
بیایید با یک مثال شروع به یادگیری ng-template کنیم. در اینجا ما دو دکمه تب در یک کامپوننت را تعریف می کنیم :
@Component({ selector: 'app-root', template: ` <ng-template> <button class="tab-button" (click)="login()">{{loginText}}</button> <button class="tab-button" (click)="signUp()">{{signUpText}}</button> </ng-template> `}) export class AppComponent { loginText = 'Login'; signUpText = 'Sign Up'; lessons = ['Lesson 1', 'Lessons 2']; login() { console.log('Login'); } signUp() { console.log('Sign Up'); } }
اگر مثال بالا را امتحان کنید، ممکن است تعجب کنید و متوجه شوید این مثال چیزی را روی صفحه نمایش نمی دهد !
این طبیعی است و رفتار مورد انتظار ماست. این به این دلیل است که با تگ ng-template ما به سادگی یک الگو را تعریف می کنیم، اما هنوز از آن استفاده نمی کنیم.
ng-template
وngIf
احتمالاً برای اولین بار هنگام اجرای یک سناریوی if/else مانند این یکی، با ng-template مواجه شده اید:
<div class="lessons-list" *ngIf="lessons else loading"> ... </div> <ng-template #loading> <div>Loading...</div> </ng-template>
این یک استفاده بسیار متداول از عملکرد ngIf/else است: ما یک الگوی جایگزینloading
را در حالی که منتظر دریافت داده ها از backend هستیم نمایش می دهیم.
همانطور که می بینیم، عبارت else به یک الگو اشاره می کند که loading
نام دارد . این نام از طریق یک template reference و با استفاده از loading# به آن اختصاص داده شد .
اما در لایه های زیرین یک ng-template ضمنی دوم نیز ایجاد می شود! بیایید نگاهی بیندازیم :
<ng-template [ngIf]="lessons" [ngIfElse]="loading"> <div class="lessons-list"> ... </div> </ng-template> <ng-template #loading> <div>Loading...</div> </ng-template>
این همان چیزی است که در داخل اتفاق میافتد، زیرا Angular دستور ساختاری مختصرتر ngIf*
را ارائه میکند.
[ngIf]
و[ngIfElse]
به دو دستورالعمل جداگانه تقسیم شده و اعمال شده است.و این فقط یک نمونه از یک مورد خاص با ngIf است. اما با ngFor و ngSwitch یک فرآیند مشابه نیز رخ می دهد.
همه این دستورالعمل ها بسیار رایج هستند، بنابراین به این معنی است که این الگوها در همه جا در Angular وجود دارند، چه به طور ضمنی یا صریح.
اما با توجه به این مثال، ممکن است یک سوال به ذهن خطور کند:
اگر دستورالعمل های ساختاری متعددی برای یک عنصر اعمال شود، چگونه کار می کند؟
بیایید ببینیم چه اتفاقی میافتد اگر برای مثال بخواهیم از ngIf
و ngFor
در همان عنصر استفاده کنیم:
<div class="lesson" *ngIf="lessons" *ngFor="let lesson of lessons"> <div class="lesson-detail"> {{lesson | json}} </div> </div>
این کار نمی کند! در عوض، پیام خطای زیر را دریافت می کنیم:
Uncaught Error: Template parse errors: Can't have multiple template bindings on one element. Use only one attribute named 'template' or prefixed with *
این بدان معنی است که نمی توان دو دستورالعمل ساختاری را برای یک عنصر اعمال کرد. برای انجام این کار، باید کاری شبیه به این انجام دهیم:
<div *ngIf="lessons"> <div class="lesson" *ngFor="let lesson of lessons"> <div class="lesson-detail"> {{lesson | json}} </div> </div> </div>
در این مثال، دستور ngIf را به یک div بیرونی منتقل کرده ایم، اما برای اینکه این کار عمل کند، باید آن عنصر div اضافی را ایجاد کنیم.
این راه حل کار می کند، اما آیا راهی برای انجام این کار بدون نیاز به ایجاد یک عنصر (div) اضافی وجود دارد؟
بله و این دقیقا همان چیزی است که دستورالعمل ساختاریng-container
به ما اجازه می دهد!
ng-container
برای جلوگیری از ایجاد آن div اضافی، میتوانیم به جای آن از دستورالعمل ng-container استفاده کنیم:
<ng-container *ngIf="lessons"> <div class="lesson" *ngFor="let lesson of lessons"> <div class="lesson-detail"> {{lesson | json}} </div> </div> </ng-container>
همانطور که می بینیم، دستورالعمل ng-container عنصری را در اختیار ما قرار می دهد که می توانیم یک دستورالعمل ساختاری را به بخشی از صفحه متصل کنیم، بدون اینکه نیازی به ایجاد یک عنصر اضافی فقط برای آن باشد.
یک مورد استفاده دیگر برای دستورالعمل ng-container وجود دارد و آن میتواند یک مکان نگهدار برای تزریق یک template به صورت پویا به صفحه ارائه دهد.
ngTemplateOutlet
توانایی ایجاد template references و ارجاع آنها به دستورالعمل های دیگر مانند ngIf تنها آغاز کار است.
همچنین میتوانیم خود template را برداریم و با استفاده از دستورالعملngTemplateOutlet
، آن را در هر نقطه از صفحه نمونهسازی کنیم :
<ng-container *ngTemplateOutlet="loading"></ng-container>
ما در اینجا میتوانیم ببینیم که ng-container چگونه به این مورد استفاده کمک میکند: ما از آن برای نمونهسازی قالب loading
که در بالا تعریف کردیم استفاده میکنیم.
ما از طریق loading
template referencesبه قالب loading اشاره می کنیم و از دستورالعمل ساختاریngTemplateOutlet
برای نمونه سازی الگو استفاده می کنیم.
ما میتوانیم به تعداد دلخواه تگngTemplateOutlet
به صفحه اضافه کنیم و تعدادی قالب مختلف را نمونهسازی کنیم. مقدار ارسال شده به این دستورالعمل می تواند هر عبارتی باشد که در یک template reference ارزیابی می شود، در ادامه در این مورد بیشتر توضیح خواهیم داد.
اکنون که میدانیم چگونه الگوها را نمونهسازی کنیم، بیایید در مورد آنچه که درون قالب در دسترس است صحبت کنیم.
یک سوال کلیدی در مورد قالب ها این است که چه چیزی در داخل آنها قابل مشاهده است؟
در داخل بدنه تگ ng-template، ما به همان context variables که در قالب بیرونی قابل مشاهده هستند، مانند متغیرlessons
دسترسی داریم.
و این به این دلیل است که تمام نمونه های ng-template به همان زمینه ای که در آن تعبیه شده اند نیز دسترسی دارند.
اما هر قالب می تواند مجموعه ای از متغیرهای ورودی خود را نیز تعریف کند! در واقع، هر الگو یک شی زمینه حاوی تمام متغیرهای ورودی خاص الگو را مرتبط کرده است.
بیایید به یک مثال نگاه کنیم:
@Component({ selector: 'app-root', 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
نام دارد و از طریق یک ویژگی ng-template با استفاده از پیشوندlet-
تعریف می شود.ngTemplateOutlet
همراه با الگو برای نمونه سازی به آن ارسال می شودestimate
باشد تا هر مقداری در قالب نمایش داده شود.ngTemplateOutlet
ارسال می شود، که می تواند هر عبارتی را که برای یک شی ارزیابی می کند دریافت کند.با توجه به مثال بالا، این چیزی است که به صفحه نمایش داده می شود:
Approximately 10 lessons ...
این به ما دید کلی خوبی از نحوه تعریف و نمونه سازی قالب های خودمان می دهد.
کار دیگری که می توانیم انجام دهیم این است که با یک الگو در سطح خود کامپوننت تعامل داشته باشیم: بیایید ببینیم چگونه می توانیم این کار را انجام دهیم.
همانطور که میتوانیم با استفاده از یک template refrence به قالب loading مراجعه کنیم، میتوانیم قالبی را نیز با استفاده از دکوراتورViewChild مستقیماً به کامپوننت خود تزریق کنیم :
@Component({ selector: 'app-root', template: ` <ng-template #defaultTabButtons> <button class="tab-button" (click)="login()"> {{loginText}} </button> <button class="tab-button" (click)="signUp()"> {{signUpText}} </button> </ng-template> `}) export class AppComponent implements OnInit { @ViewChild('defaultTabButtons') private defaultTabButtonsTpl: TemplateRef<any>; ngOnInit() { console.log(this.defaultTabButtonsTpl); } }
همانطور که می بینیم، الگو را می توان مانند هر عنصر DOM یا کامپوننتی تزریق شود، با ارائه نام مرجع الگو defaultTabButtons
به دکوراتورViewChild
تزریق کرد.
این بدان معناست که قالب ها در سطح کلاس کامپوننت نیز قابل دسترسی هستند و ما می توانیم کارهایی مانند انتقال آنها به اجزای فرزند انجام دهیم!
مثالی از اینکه چرا میخواهیم این کار را انجام دهیم، ایجاد یک کامپوننت قابل تنظیمتر است، جایی که میتوان نه تنها یک پارامتر پیکربندی یا شی پیکربندی را به آن ارسال کرد: ما همچنین میتوانیم یک الگو را به عنوان پارامتر ورودی ارسال کنیم .
Inputs@
اجازه دهید به عنوان مثال یک tab container را در نظر بگیریم، جایی که میخواهیم به کاربر کامپوننت امکان پیکربندی ظاهر و احساس دکمههای تب را بدهیم.
در اینجا به نظر می رسد، ما با تعریف الگوی سفارشی برای دکمه های کامپوننت والد شروع می کنیم:
@Component({ selector: 'app-root', template: ` <ng-template #customTabButtons> <div class="custom-class"> <button class="tab-button" (click)="login()"> {{loginText}} </button> <button class="tab-button" (click)="signUp()"> {{signUpText}} </button> </div> </ng-template> <tab-container [headerTemplate]="customTabButtons"></tab-container> `}) export class AppComponent implements OnInit { }
و سپس در کامپوننت tab container، میتوانیم یک ویژگی ورودی تعریف کنیم که آن نیز یک الگو به نام headerTemplate
:
@Component({ selector: 'tab-container', template: ` <ng-template #defaultTabButtons> <div class="default-tab-buttons"> ... </div> </ng-template> <ng-container *ngTemplateOutlet="headerTemplate ? headerTemplate: defaultTabButtons"> </ng-container> ... rest of tab container component ... `}) export class TabContainerComponent { @Input() headerTemplate: TemplateRef<any>; }
در این مثال ترکیبی نهایی، چند چیز در اینجا در حال انجام است. بیایید این را تجزیه کنیم:
defaultTabButtons
نامیده می شود.headerTemplate
تعریف نشده ( undefined ) باقی بماند.headerTemplate
برای نمایش دکمه ها استفاده می شود.ngTemplateOutlet
در داخل یک ng-container نمونه سازی می شود.نتیجه نهایی این طراحی این است که در صورت عدم ارائه الگوی سفارشی، کانتینر تب ظاهر و حالت پیش فرض را برای دکمه های تب نمایش می دهد، اما در صورت موجود بودن از قالب سفارشی استفاده می کند.
دستورالعمل های اصلی ng-container، ng-template و ngTemplateOutlet همگی با هم ترکیب می شوند تا به ما امکان ایجاد کامپوننت های بسیار پویا و قابل تنظیم را می دهند.
ما حتی میتوانیم ظاهر یک کامپوننت را بر اساس الگوهای ورودی کاملاً تغییر دهیم ، و میتوانیم یک الگو تعریف کنیم و در مکانهای مختلف برنامه نمونهسازی کنیم.
و این تنها یکی از راه های ممکن برای ترکیب این ویژگی ها است!
این مقاله برگردان شده یک مقاله معتبر درباره این موضوع است.برای دسترسی به مقاله منبع اینجا کلیک کنید.
برای مشاهده پست های بیشتر و ارتباط با من از طریق لینکدین اینجا کلیک کنید.
امیدوارم براتون مفید واقع شده باشه.