نکات و ترفندهای کامپوننت های انگولار | به زبان ساده

۲۲۲ بازدید
آخرین به‌روزرسانی: ۱۱ شهریور ۱۴۰۲
زمان مطالعه: ۳ دقیقه
نکات و ترفندهای کامپوننت های انگولار | به زبان ساده

در این مقاله برخی نکات و ترفندهای کامپوننت های انگولار را معرفی می‌کنیم که کمک می‌کنند تا بتوانید این کامپوننت‌ها را به روش بهینه‌تر و ساده‌تری توسعه دهید. در این راهنما یک کامپوننت بازشدنی (drop-down) سفارشی به عنوان مثال توسعه می‌دهیم که از هر دو روش تک‌انتخابی و چند انتخابی پشتیبانی می‌کند.

کار خود را با آماده‌سازی دو کامپوننت کلیدی آغاز می‌کنیم. در ابتدا به دو کامپوننت dato-select و dato-option نیاز داریم.

فایل dato-option.component.ts

1@Component({
2  selector: 'dato-option',
3  template: `
4    <div class="dato-option" [ngClass]="...">
5     <ng-content></ng-content>
6    </div>
7  `,
8})
9export class DatoOptionComponent implements OnInit {
10}

فایل dato-select.component.ts

1@Component({
2  selector: 'dato-select',
3  template: `<ng-content></ng-content>`
4})
5export class DatoSelectComponent {
6  @ContentChildren(DatoOptionComponent) options: QueryList<DatoOptionComponent>;
7    
8  ngAfterContentInit() {
9  }
10}

نکته اول

از آنجا که کامپوننت بازشدنی ما از روش چند انتخابی نیز پشتیبانی می‌کند، باید یک چکمارک اضافه کنیم که نشان دهد آیا یک گزینه انتخاب شده است یا نه.

یک روش برای انجام این کار از طریق افزودن یک متد از نوع input()‎ و نمایش یا پنهان کردن mrkup بر اساس مقدار آن است:

1@Component({
2  selector: 'dato-option',
3  template: `
4    <div class="dato-option" [ngClass]="...">
5     <input type="checkbox" *ngIf="isMulti">
6     <ng-content></ng-content>
7    </div>
8  `,
9})
10export class DatoOptionComponent {
11  @Input() isMulti = false;
12}

البته کد فوق کار می‌کند، اما عیب این رویکرد آن است که برای هر گزینه یک بررسی بیشتر به انگولار اضافه می‌کنیم و از این رو زمانی که یک کامپوننت بازشدنی دارای 10،000 گزینه باشد، باید 10،000 بررسی اضافه کنیم و این تنها یک ngIf است، در حالی که این کار معمولاً توسط ngClass ،ngStyle و غیره انجام می‌یابد.

در این مورد خاص عملکرد کامپوننت برای ما حائز اهمیت بالایی است و از این رو تصمیم می‌گیریم که multi-option را به کامپوننت دیگری ببریم که از single-option ارث‌بری می‌کند تا از بررسی‌های تکراری جلوگیری کنیم.

ضمناً هنوز بخش‌هایی از markup وجود دارند که در هر دو بخش تک‌انتخابی و چند انتخابی مشترک هستند و از این رو از تنظیمات تمپلیت برای حفظ اصل DRY در کد بهره می‌گیریم.

کامپوننت تک‌گزینه‌ای

1export function getOptionTemplate(isMulti = false) {
2  return `
3     <div class="dato-option">
4       ${isMulti ? '<input type="checkbox">' : ''}
5       <ng-content></ng-content>
6     </div>
7  `
8}
9
10@Component({
11  selector: 'dato-option',
12  template: getOptionTemplate(),
13})
14export class DatoOptionComponent {
15}

کامپوننت چندگزینه‌ای

1@Component({
2  selector: 'dato-option-multi',
3  template: getOptionTemplate(true)
4})
5export class DatoOptionMultiComponent extends DatoOptionComponent {
6 }

نکته دوم

تغییر قبلی که در کد ایجاد کردیم، مشکل جدید را ایجاد کرد. فرض کنید می‌خواهیم از کامپوننت بازشدنی multi-select استفاده کنیم:

1@Component({
2  selector: 'dato-select',
3  template: `<ng-content></ng-content>`
4})
5export class DatoSelectComponent {
6  @ContentChildren(DatoOptionComponent) options: QueryList<DatoOptionComponent>;
7}
1<dato-select>
2  <dato-option-multi *ngFor="let option of options">{{option}}</dato-option-multi>
3</dato-select>

این کد کار نخواهد کرد، زیرا دکوارتور ContentChildren به دنبال DatoOptionComponent می‌گردد. اگر بخواهیم یک ارجاع به کامپوننت‌های option-multi داشته باشیم، باید یک کوئری ContentChildren اضافه کنیم:

1@ContentChildren(DatoOptionComponent) options: QueryList<DatoOptionComponent>;
2@ContentChildren(DatoOptionMultiComponent) optionsMulti: QueryList<DatoOptionMultiComponent>;

اما اکنون باید هر دو را حفظ کنیم. خوشبختانه می‌توانیم این مشکل را به روشی بهتر با کمک Angular DI حل کنیم:

1@Component({
2  selector: 'dato-option-multi',
3  template: getOptionTemplate(true),
4  providers: [{provide: DatoOptionComponent, useExisting: DatoOptionMultiComponent}]
5})
6export class DatoOptionMultiComponent extends DatoOptionComponent {
7}

بدین ترتیب به انگولار اعلام می‌کنیم که وقتی به DatoOptionComponent نیاز دارد از DatoOptionMultiComponent استفاده کند.

نکات و ترفندهای کامپوننت های انگولار

نکته سوم

ما همچنان راضی نشده‌ایم و می‌خواهیم از همان سلکتور dato-option برای هر دو کامپوننت‌های بازشدنی تک‌گزینه‌ای و چند‌گزینه‌ای استفاده کنیم. بنابراین سلکتور multi-option را طوری تغییر می‌دهیم که یکسان باشند:

1@Component({
2  selector: 'dato-option',
3  ...
4})
5export class DatoOptionMultiComponent extends DatoOptionComponent {
6}

اما اکنون یک مشکل داریم. انگولار به ما امکان نمی‌دهد که سلکتورها را تکرار کنیم و خطایی به صورت زیر صادر می‌کند:

More than one component matched on this element.
Make sure that only one component’s selector can match a given element.

ترجمه باکس بالا:

«بیش از یک کامپوننت با این عنصر تطبیق یافته‌اند.»

«مطمئن شوید که تنها سلکتور یک کامپوننت می‌تواند با عنصر مفروض تطبیق یابد.»

روش ما برای حل این مشکل از طریق بهره‌گیری از این واقعیت است که انگولار از سلکتور CSS به نام:not پشتیبانی می‌کند.

فایل کامپوننت تک‌گزینه‌ای

1@Component({
2  selector: 'dato-option:not([multi])',
3  ...
4})
5export class DatoOptionComponent implements OnInit {
6}

فایل کامپوننت چندگزینه‌ای

1@Component({
2  selector: 'dato-option[multi]',
3  ...
4})
5export class DatoOptionMultiComponent extends DatoOptionComponent {
6}

اکنون می‌توانیم از همان سلکتور استفاده کرده و زمانی که به multi-option نیاز باشد، یک خصوصیت اضافه کنیم. این روش بسیار مناسب‌تر از تعریف یک سلکتور کاملاً جدید است.

نکته چهارم

کامپوننت بازشدنی یک کادر جستجو نیز دارد که به صورت پیش‌فرض جستجوی داخلی انجام می‌دهد و ما به یک گزینه برای اجرای جستجوی سمت سرور هم نیاز داریم. یک روش برای نیل به این مقصود این است که یک متد input()‎ و یک متد output()‎ ایجاد کنیم:

1@Component({
2  selector: 'dato-select',
3  template: `<ng-content></ng-content>`
4})
5export class DatoSelectComponent {
6  @ContentChildren(DatoOptionComponent) options: QueryList<DatoOptionComponent>;
7  @Input() internalSearch = true;
8  @Output() search = new EventEmitter();
9}

اما به این ترتیب حس می‌شود که ورودی تکراری است و می‌توانستیم از آن اجتناب کنیم. به این منظور باید بدانیم که آیا فردی منتظر رویداد search است یا نه و بر اساس آن تصمیم بگیریم که جستجوی داخلی را فعال کنیم یا نه.

1@Component({
2  selector: 'dato-select',
3  template: `<ng-content></ng-content>`
4})
5export class DatoSelectComponent {
6  @ContentChildren(DatoOptionComponent) options: QueryList<DatoOptionComponent>;
7  @Output() search = new EventEmitter();
8  
9  private internalSearch;
10  
11  ngOnInit() {
12    this.internalSearch = this.search.observers.length === 0;
13  }
14}

به این ترتیب هر event emitter یک لیست از observer-های خود نگهداری می‌کند. اگر دست کم بک observer داشته باشیم، به این معنی است که فردی مشترک شده است.

به این ترتیب به پایان این مقاله می‌رسیم. امیدواریم با مطالعه این راهنما نکات و ترفندهای جدیدی را آموخته باشید.

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
netbasal
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *