با استفاده از 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’ and
renderDynamicComponents در ابتدا هر دو مؤلفه را نمایش می دهد.
حالا بار اولیه هر دو کامپوننت را رندر می کند اما من مشکل دیگری دارم. کلیک روی دکمه و تغییر ورودی فرم، ورودی پوکمون اجزای پویا را به روز نمی کند. با پیاده سازی قابل حل است OnChanges
رابط و فراخوانی مجدد renderDynamicComponents در ngOnChanges
.
مولفه های پویا را در ngOnChanges دوباره رندر کنید
changes['pokemon'].currentValue
ورودی جدید پوکمون است. this.renderDynamicComponents(changes['pokemon'].currentValue)
Pokemon جدید را برای تخصیص به اجزای جدید و نمایش آنها در کانتینر به صورت پویا ارسال می کند.
مخزن زیر Stackblitz نتایج نهایی را نشان می دهد:
این پایان پست وبلاگ است و امیدوارم از مطالب خوشتان بیاید و همچنان تجربه یادگیری من در Angular و سایر فناوری ها را دنبال کنید.
منابع: