برنامه نویسی

با استفاده از viewContainerRef کامپوننت های پویا را در Angular رندر کنید

معرفی

در انگولار، ngComponentOutlet راه ساده ای برای رندر کردن اجزا به صورت پویا است. گزینه دیگر این است که مولفه های پویا را با استفاده از رندر کنید ViewContainerRef کلاس ViewContainerRef کلاس دارد createComponent روشی که جزء را نمونه سازی می کند و آن را در یک ظرف قرار می دهد. این یک تکنیک قدرتمند برای اضافه کردن کامپوننت ها در زمان اجرا است. آنها برای کوچک نگه داشتن اندازه بسته در بسته اصلی بارگذاری نمی شوند. هنگامی که مؤلفه های زیادی وجود دارد که به صورت مشروط ارائه می شوند، ViewContainerRef محلول تمیزتر از چندین است ng-if عباراتی که به راحتی قالب درون خطی را باد می کنند.

در این پست وبلاگ، یک جزء جدید ایجاد کردم، PokemonTabComponent، که از دکمه های رادیویی و الف تشکیل شده است ng-container عنصر هنگام کلیک کردن روی یک دکمه رادیویی، کامپوننت PokemonStatsComponent، PokemonAbilitiesComponent یا هر دو را به صورت پویا نمایش می دهد. رندر کردن اجزای پویا با استفاده از ViewContainerRef پیچیده تر از آن است ngComponentOutlet و استفاده از آن را در ادامه پست خواهیم دید.

کد اسکلت جزء تب پوکمون

// pokemon-tab.component.ts

@Component({
  selector: 'app-pokemon-tab',
  standalone: true,
  imports: [PokemonStatsComponent, PokemonAbilitiesComponent],
  template: `
    <div style="padding: 0.5rem;" class="container">
      <div>
        <div>
          <input type="radio" id="all" name="selection" value="all" checked">
          <label for="all">All</label>
        </div>
        <div>
          <input type="radio" id="stats" name="selection" value="stats">
          <label for="stats">Stats</label>
        </div>
        <div>
          <input type="radio" id="abilities" name="selection" value="abilities">
          <label for="abilities">Abilities</label>
        </div>
      </div>
      <ng-container #vcr></ng-container>
    </div>
  `,
  styles: [`...omitted due to brevity...`],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PokemonTabComponent {
  @Input()
  pokemon: FlattenPokemon;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

که در PokemonTabComponent جزء مستقل، هنگام کلیک کردن روی دکمه‌های رادیویی هیچ اتفاقی نیفتاد. با این حال، وقتی شروع به اضافه کردن کدهای جدید برای رندر کردن مؤلفه‌های پویا با استفاده از آن می‌کردم، رفتار تغییر می‌کرد ViewContainerRef. نتیجه نهایی نمایش اجزا در ng-container به نام vcr.

کد اسکلت نمونه

نقشه انتخاب دکمه رادیویی به انواع مؤلفه

// pokemon-tab.enum.ts

export enum POKEMON_TAB {
    ALL = 'all',
    STATISTICS = 'statistics',
    ABILITIES = 'abilities'
}
First, I defined enum to represent different radio button selections.

// pokemon-tab.component.ts

componentTypeMap = {
    [POKEMON_TAB.ALL]: [PokemonStatsComponent, PokemonAbilitiesComponent],
    [POKEMON_TAB.STATISTICS]: [PokemonStatsComponent],
    [POKEMON_TAB.ABILITIES]: [PokemonAbilitiesComponent],
  };
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

سپس، من یک نقشه شی برای نگاشت اعضای enum به لیست های نوع مؤلفه تعریف کردم.

@ViewChild('vcr', { static: true, read: ViewContainerRef })
vcr!: ViewContainerRef;
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در مرحله بعد، من از دکوراتور ViewChild() برای به دست آوردن ارجاع به عنصر ng-container و فراخوانی کلاس ViewContainerRef برای افزودن مؤلفه های پویا به ظرف استفاده کردم.

کنترل رویداد کلیک را به دکمه‌های رادیویی اضافه کنید

وقتی روی هر دکمه رادیویی کلیک می‌کنم، می‌خواهم انواع مؤلفه‌ها را در componentTypeMap جستجو کنم، فهرست را برای ایجاد ارجاعات مؤلفه جدید و اضافه کردن آنها به vcr تکرار کنم. در قالب درون خطی، من کنترل کننده رویداد کلیک را به دکمه های رادیویی اضافه می کنم که متد renderDynamicComponents را برای رندر مؤلفه های پویا اجرا می کند.

// pokemon-tab.component.ts

template:`  
...
<div>
   <input type="radio" id="all" name="selection" value="all"
   checked (click)="selection = 'ALL'; renderDynamicComponents();">
   <label for="all">All</label>
</div>
<div>
    <input type="radio" id="stats" name="selection" value="stats"
       (click)="selection = 'STATISTICS'; renderDynamicComponents();">
    <label for="stats">Stats</label>
</div>
<div>
    <input type="radio" id="abilities" name="selection" value="abilities"
       (click)="selection = 'ABILITIES'; renderDynamicComponents();">
    <label for="abilities">Abilities</label>
</div>
...
`

selection: 'ALL' | 'STATISTICS' | 'ABILITIES' = 'ALL';
componentRefs: ComponentRef<PokemonStatsComponent | PokemonAbilitiesComponent>[] = [];
cdr = inject(ChangeDetectorRef);

async renderDynamicComponents(currentPokemon?: FlattenPokemon) {
    const enumValue = POKEMON_TAB[this.selection as keyof typeof POKEMON_TAB];
    const componentTypes = this.componentTypeMap[enumValue];

    // clear dynamic components shown in the container previously    
    this.vcr.clear();
    for (const componentType of componentTypes) {
      const newComponentRef = this.vcr.createComponent(componentType);
      newComponentRef.instance.pokemon = currentPokemon ? currentPokemon : this.pokemon;
      // store component refs created
      this.componentRefs.push(newComponentRef);
      // run change detection in the component and child components
      this.cdr.detectChanges();
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

this.selection دکمه رادیویی انتخاب شده فعلی را ردیابی می کند و مقدار مؤلفه/مولفه هایی را که در ظرف رندر می شوند، تعیین می کند.

this.vcr.clear(); تمام اجزا را از ظرف حذف می کند و اجزای جدید را به صورت پویا وارد می کند

const newComponentRef = this.vcr.createComponent(componentType); کامپوننت جدید را نمونه سازی می کند و به ظرف اضافه می کند و یک ComponentRef را برمی گرداند.

newComponentRef.instance.pokemon = currentPokemon ? currentPokemon : this.pokemon;

PokemonStatsComponent و PokemonAbilitiesComponent انتظار ورودی پوکمون را دارند. بنابراین، من یک شی Pokemon را به newComponentRef.instance.pokemon اختصاص می دهم که در آن newComponentRef.instance یک نمونه جزء است.

this.componentRefs.push(newComponentRef); تمام نمونه های ComponentRef را ذخیره می کند و بعداً آنها را در ngOnDestroy نابود می کنم تا از نشت حافظه جلوگیری شود.

this.cdr.detectChanges(); تشخیص تغییر را برای به‌روزرسانی مؤلفه و مؤلفه‌های فرزند آن آغاز می‌کند.

اجزای پویا را در قلاب چرخه حیات OnDestroy نابود کنید

با ارائه یک پیاده سازی مشخص از ngOnDestroy رابط OnDestroy را پیاده سازی کنید.

export class PokemonTabComponent implements OnDestroy {
   ...other logic...

   ngOnDestroy(): void {
    // release component refs to avoid memory leak
    for (const componentRef of this.componentRefs) {
      if (componentRef) {
        componentRef.destroy();
      }
    }
  }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این روش آرایه componentRefs را تکرار می کند و حافظه هر ComponentRef را برای جلوگیری از نشت حافظه آزاد می کند.

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

هنگامی که برنامه در ابتدا بارگیری می شود، صفحه خالی است زیرا تماسی نداشته است renderDynamicComponents هنوز. حل آن با پیاده سازی رابط OnInit و فراخوانی متد در بدنه آسان است ngOnInit.

export class PokemonTabComponent implements OnDestroy, OnInit {
   ...
   ngOnInit(): void {
     this.renderDynamicComponents();
   }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

وقتی Angular اجرا می شود ngOnInit، مقدار اولیه this.selectionis ‘ALL’ andrenderDynamicComponents در ابتدا هر دو مؤلفه را نمایش می دهد.

حالا بار اولیه هر دو کامپوننت را رندر می کند اما من مشکل دیگری دارم. کلیک روی دکمه و تغییر ورودی فرم، ورودی پوکمون اجزای پویا را به روز نمی کند. با پیاده سازی قابل حل است OnChanges رابط و فراخوانی مجدد renderDynamicComponents در ngOnChanges.

مولفه های پویا را در ngOnChanges دوباره رندر کنید

با OnChanges

changes['pokemon'].currentValue ورودی جدید پوکمون است. this.renderDynamicComponents(changes['pokemon'].currentValue) Pokemon جدید را برای تخصیص به اجزای جدید و نمایش آنها در کانتینر به صورت پویا ارسال می کند.

مخزن زیر Stackblitz نتایج نهایی را نشان می دهد:

این پایان پست وبلاگ است و امیدوارم از مطالب خوشتان بیاید و همچنان تجربه یادگیری من در Angular و سایر فناوری ها را دنبال کنید.

منابع:

نوشته های مشابه

دیدگاهتان را بنویسید

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

دکمه بازگشت به بالا