سه ویژگی ng-template، ng-container و ngTemplateOutlet - راهنمای کامل الگوهای Angular

احتمالاً قبلاً با دستورالعمل ng-template از Angular core مواجه شده اید، مثلاً هنگام استفاده از ngIf/else یا ngSwitch.دستورالعمل ng-template و دستورالعمل ngTemplateOutlet ویژگی های بسیار قدرتمند Angular هستند که کاربرد های زیادی دارند.این دستورالعمل‌ها اغلب با ng-container استفاده می‌شوند، و از آنجایی که این دستورالعمل‌ها برای استفاده با هم طراحی شده‌اند، اگر همه آنها را یکجا یاد بگیریم، کمک بیشتری خواهند کرد.توجه: تمام کدهای این پست را می توانید در این مخزن Github پیدا کنید .فهرست مطالبدر این پست به موضوعات زیر می پردازیم:مقدمه ای بر دستورالعمل ng-templateمتغیرهای Template Inputاستفاده از دستورالعمل ng-template با ngIfاگر دستور و ng-template را حذف کنید.دستور template references ng-template و TemplateRef قابل تزریقکامپوننت های قابل تنظیم با Template Partial @Inputsدستورالعمل ng-container ، چه زمانی از آن استفاده کنیم؟داینامیک کردن Template با دستورالعمل سفارشی ngTemplateOutletویژگی های Template outlet @Inputمثال ترکیبی نهاییخلاصه و نتیجه گیریمقدمه ای بر دستورالعمل ng-templateهمانطور که از نام آن مشخص می شود، دستورالعمل ng-template یک template انگولار را نشان می دهد: این بدان معنی است که محتوای این تگ بخشی از یک template خواهد بود، که می توان آن را با سایر template ها ترکیب کرد تا قالب نهایی کامپوننت را تشکیل دهد.انگولار در حال حاضر از ng-template در لایه های زیرین بسیاری از دستورالعمل‌های ساختاری استفاده می‌کند که ما همیشه از آن‌ها استفاده می‌کنیم مثلngIf، ngForو ngSwitch.بیایید با یک مثال شروع به یادگیری ng-template کنیم. در اینجا ما دو دکمه تب در یک کامپوننت را تعریف می کنیم :@Component({ selector: 'app-root', template: ` <ng-template> <button class=&quottab-button&quot (click)=&quotlogin()&quot>{{loginText}}</button> <button class=&quottab-button&quot (click)=&quotsignUp()&quot>{{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=&quotlessons-list&quot *ngIf=&quotlessons else loading&quot> ... </div> <ng-template #loading> <div>Loading...</div> </ng-template>این یک استفاده بسیار متداول از عملکرد ngIf/else است: ما یک الگوی جایگزینloadingرا در حالی که منتظر دریافت داده ها از backend هستیم نمایش می دهیم.همانطور که می بینیم، عبارت else به یک الگو اشاره می کند که loadingنام دارد . این نام از طریق یک template reference و با استفاده از loading# به آن اختصاص داده شد .اما در لایه های زیرین یک ng-template ضمنی دوم نیز ایجاد می شود! بیایید نگاهی بیندازیم :<ng-template [ngIf]=&quotlessons&quot [ngIfElse]=&quotloading&quot> <div class=&quotlessons-list&quot> ... </div> </ng-template> <ng-template #loading> <div>Loading...</div> </ng-template>این همان چیزی است که در داخل اتفاق می‌افتد، زیرا Angular دستور ساختاری مختصرتر ngIf*را ارائه می‌کند. عنصری که دستورالعمل ساختاری ngIf بر روی آن اعمال شد به یک ng-template منتقل شده است.عبارت ngIf* با استفاده از [ngIf]و[ngIfElse] به دو دستورالعمل جداگانه تقسیم شده و اعمال شده است.و این فقط یک نمونه از یک مورد خاص با ngIf است. اما با ngFor و ngSwitch یک فرآیند مشابه نیز رخ می دهد.همه این دستورالعمل ها بسیار رایج هستند، بنابراین به این معنی است که این الگوها در همه جا در Angular وجود دارند، چه به طور ضمنی یا صریح.اما با توجه به این مثال، ممکن است یک سوال به ذهن خطور کند:اگر دستورالعمل های ساختاری متعددی برای یک عنصر اعمال شود، چگونه کار می کند؟دستورالعمل های ساختاری متعددبیایید ببینیم چه اتفاقی می‌افتد اگر برای مثال بخواهیم از ngIfو ngForدر همان عنصر استفاده کنیم:<div class=&quotlesson&quot *ngIf=&quotlessons&quot *ngFor=&quotlet lesson of lessons&quot> <div class=&quotlesson-detail&quot> {{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=&quotlessons&quot> <div class=&quotlesson&quot *ngFor=&quotlet lesson of lessons&quot> <div class=&quotlesson-detail&quot> {{lesson | json}} </div> </div> </div>در این مثال، دستور ngIf را به یک div بیرونی منتقل کرده ایم، اما برای اینکه این کار عمل کند، باید آن عنصر div اضافی را ایجاد کنیم.این راه حل کار می کند، اما آیا راهی برای انجام این کار بدون نیاز به ایجاد یک عنصر (div) اضافی وجود دارد؟بله و این دقیقا همان چیزی است که دستورالعمل ساختاریng-containerبه ما اجازه می دهد!دستورالعمل ng-containerبرای جلوگیری از ایجاد آن div اضافی، می‌توانیم به جای آن از دستورالعمل ng-container استفاده کنیم:<ng-container *ngIf=&quotlessons&quot> <div class=&quotlesson&quot *ngFor=&quotlet lesson of lessons&quot> <div class=&quotlesson-detail&quot> {{lesson | json}} </div> </div> </ng-container>همانطور که می بینیم، دستورالعمل ng-container عنصری را در اختیار ما قرار می دهد که می توانیم یک دستورالعمل ساختاری را به بخشی از صفحه متصل کنیم، بدون اینکه نیازی به ایجاد یک عنصر اضافی فقط برای آن باشد.یک مورد استفاده دیگر برای دستورالعمل ng-container وجود دارد و آن میتواند یک مکان نگهدار برای تزریق یک template به صورت پویا به صفحه ارائه دهد.ایجاد template پویا با دستورالعملngTemplateOutletتوانایی ایجاد template references و ارجاع آنها به دستورالعمل های دیگر مانند ngIf تنها آغاز کار است.همچنین می‌توانیم خود template را برداریم و با استفاده از دستورالعملngTemplateOutlet، آن را در هر نقطه از صفحه نمونه‌سازی کنیم :<ng-container *ngTemplateOutlet=&quotloading&quot></ng-container>ما در اینجا می‌توانیم ببینیم که ng-container چگونه به این مورد استفاده کمک می‌کند: ما از آن برای نمونه‌سازی قالب loadingکه در بالا تعریف کردیم استفاده می‌کنیم.ما از طریق loadingtemplate referencesبه قالب loading اشاره می کنیم و از دستورالعمل ساختاریngTemplateOutlet برای نمونه سازی الگو استفاده می کنیم.ما می‌توانیم به تعداد دلخواه تگngTemplateOutletبه صفحه اضافه کنیم و تعدادی قالب مختلف را نمونه‌سازی کنیم. مقدار ارسال شده به این دستورالعمل می تواند هر عبارتی باشد که در یک template reference ارزیابی می شود، در ادامه در این مورد بیشتر توضیح خواهیم داد.اکنون که می‌دانیم چگونه الگوها را نمونه‌سازی کنیم، بیایید در مورد آنچه که درون قالب در دسترس است صحبت کنیم.محتوای الگو ( Template Context )یک سوال کلیدی در مورد قالب ها این است که چه چیزی در داخل آنها قابل مشاهده است؟در داخل بدنه تگ ng-template، ما به همان context variables که در قالب بیرونی قابل مشاهده هستند، مانند متغیرlessonsدسترسی داریم.و این به این دلیل است که تمام نمونه های ng-template به همان زمینه ای که در آن تعبیه شده اند نیز دسترسی دارند.اما هر قالب می تواند مجموعه ای از متغیرهای ورودی خود را نیز تعریف کند! در واقع، هر الگو یک شی زمینه حاوی تمام متغیرهای ورودی خاص الگو را مرتبط کرده است.بیایید به یک مثال نگاه کنیم:@Component({ selector: 'app-root', template: ` <ng-template #estimateTemplate let-lessonsCounter=&quotestimate&quot> <div> Approximately {{lessonsCounter}} lessons ...</div> </ng-template> <ng-container *ngTemplateOutlet=&quotestimateTemplate;context:ctx&quot> </ng-container> `}) export class AppComponent { totalEstimate = 10; ctx = {estimate: this.totalEstimate}; }در اینجا به تفکیک این مثال آمده است:این الگو برخلاف قالب های قبلی دارای یک متغیر ورودی است (می تواند چندین متغیر نیز داشته باشد)متغیر ورودیlessonsCounterنام دارد و از طریق یک ویژگی ng-template با استفاده از پیشوندlet-تعریف می شود.متغیر lessonsCounter در داخل بدنه ng-template قابل مشاهده است، اما خارج از آن قابل مشاهده نیست.محتوای این متغیر با عبارتی که به ویژگی let-lessonsCounter اختصاص داده شده است تعیین می شود.این عبارت در برابر یک context object ارزیابی می شود که ngTemplateOutletهمراه با الگو برای نمونه سازی به آن ارسال می شودسپس این context object باید دارای یک ویژگی به نامestimate باشد تا هر مقداری در قالب نمایش داده شود.شی context از طریق ویژگی context به ngTemplateOutletارسال می شود، که می تواند هر عبارتی را که برای یک شی ارزیابی می کند دریافت کند.با توجه به مثال بالا، این چیزی است که به صفحه نمایش داده می شود:Approximately 10 lessons ...این به ما دید کلی خوبی از نحوه تعریف و نمونه سازی قالب های خودمان می دهد.کار دیگری که می توانیم انجام دهیم این است که با یک الگو در سطح خود کامپوننت تعامل داشته باشیم: بیایید ببینیم چگونه می توانیم این کار را انجام دهیم.مرجع الگو یا Template Referencesهمانطور که می‌توانیم با استفاده از یک template refrence به قالب loading مراجعه کنیم، می‌توانیم قالبی را نیز با استفاده از دکوراتورViewChild مستقیماً به کامپوننت خود تزریق کنیم :@Component({ selector: 'app-root', template: ` <ng-template #defaultTabButtons> <button class=&quottab-button&quot (click)=&quotlogin()&quot> {{loginText}} </button> <button class=&quottab-button&quot (click)=&quotsignUp()&quot> {{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=&quotcustom-class&quot> <button class=&quottab-button&quot (click)=&quotlogin()&quot> {{loginText}} </button> <button class=&quottab-button&quot (click)=&quotsignUp()&quot> {{signUpText}} </button> </div> </ng-template> <tab-container [headerTemplate]=&quotcustomTabButtons&quot></tab-container> `}) export class AppComponent implements OnInit { }و سپس در کامپوننت tab container، می‌توانیم یک ویژگی ورودی تعریف کنیم که آن نیز یک الگو به نام headerTemplate:@Component({ selector: 'tab-container', template: ` <ng-template #defaultTabButtons> <div class=&quotdefault-tab-buttons&quot> ... </div> </ng-template> <ng-container *ngTemplateOutlet=&quotheaderTemplate ? headerTemplate: defaultTabButtons&quot> </ng-container> ... rest of tab container component ... `}) export class TabContainerComponent { @Input() headerTemplate: TemplateRef<any>; }در این مثال ترکیبی نهایی، چند چیز در اینجا در حال انجام است. بیایید این را تجزیه کنیم:یک الگوی پیش فرض برای دکمه های برگه تعریف شده است کهdefaultTabButtons نامیده می شود.این الگو تنها در صورتی استفاده خواهد شد که ویژگی ورودی headerTemplateتعریف نشده ( undefined ) باقی بماند.اگر ویژگی تعریف شده باشد، در عوض از الگوی ورودی سفارشی ارسال شده headerTemplateبرای نمایش دکمه ها استفاده می شود.الگوی headers با استفاده از ویژگی ngTemplateOutletدر داخل یک ng-container نمونه سازی می شود.تصمیم در مورد استفاده از الگو (پیش‌فرض یا سفارشی) با استفاده از عبارت سه‌گانه گرفته می‌شود، اما اگر این منطق پیچیده بود، می‌توانیم آن را به متد controller نیز واگذار کنیم.نتیجه نهایی این طراحی این است که در صورت عدم ارائه الگوی سفارشی، کانتینر تب ظاهر و حالت پیش فرض را برای دکمه های تب نمایش می دهد، اما در صورت موجود بودن از قالب سفارشی استفاده می کند.خلاصه و نتیجه گیریدستورالعمل های اصلی ng-container، ng-template و ngTemplateOutlet همگی با هم ترکیب می شوند تا به ما امکان ایجاد کامپوننت های بسیار پویا و قابل تنظیم را می دهند.ما حتی می‌توانیم ظاهر یک کامپوننت را بر اساس الگوهای ورودی کاملاً تغییر دهیم ، و می‌توانیم یک الگو تعریف کنیم و در مکان‌های مختلف برنامه نمونه‌سازی کنیم.و این تنها یکی از راه های ممکن برای ترکیب این ویژگی ها است!این مقاله برگردان شده یک مقاله معتبر درباره این موضوع است.برای دسترسی به مقاله منبع اینجا کلیک کنید.برای مشاهده پست های بیشتر و ارتباط با من از طریق لینکدین اینجا کلیک کنید.امیدوارم براتون مفید واقع شده باشه.