ایجاد یک کامپوننت به صورت پویا و برنامه نویسی در Angular

در Angular 13، ComponentFactoryResolver منسوخ شد. برخی از کتابخانههایی که به آن وابسته بودند باید با استفاده از تابع جدید معرفیشده createComponent بازنویسی میشدند، که بیشتر با مانترای مستقل هماهنگ است. بیایید آن را بررسی کنیم و از آن به خوبی استفاده کنیم.
نمونه ای از استفاده از یک مؤلفه پویا و برنامه نویسی شده در Angular، Dialog بدنام است. امروز بیایید به مستندات آن بپردازیم createComponent
و حداقل را تنظیم کنید تا بعداً از آن استفاده کنید.
من را در StackBlitz دنبال کنید
حداقل
بیایید ابتدا کامپوننتی را ایجاد کنیم که باید به صورت برنامهنویسی درج شود، که دارای یک ویژگی ساده است آره، و نه دکمه ها. طرح زیر به شرح زیر است:
- کامپوننت را به صورت برنامه نویسی از قسمت دیگری از برنامه ایجاد کنید. بیا بهش زنگ بزنیم گل سرخ.
- در یک تگ HTML شناخته شده درج کنید
- در بدنه HTML درج کنید
- داده ها را به رز منتقل کنید
- با رز تعامل کنید
هنگامی که این موارد را پوشش دادیم، میتوانیم آرزوی بیشتری داشته باشیم: ما یک جزء جدید را در Rose وارد میکنیم، بیایید آن را بنامیم هلو.
گل سرخ
اینجا گل رز است:
// src/components/rose.partial
@Component({
template: `
<button (click)="ok()">Yes</button>
<button (click)="no()">Cancel</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class RosePartialComponent {
constructor() {
//
console.log('created');
}
ok(): void {
console.log('ok');
}
no(): void {
console.log('no');
}
}
در مؤلفه برنامه ما، موارد زیر می تواند در هر جایی باشد، اما برای سادگی، اجازه دهید این کار را در مؤلفه ریشه برنامه انجام دهیم.
// main.ts, or app.component
@Component({
selector: 'my-app',
standalone: true,
imports: [CommonModule],
template: `
<button (click)="insertElement()" class="btn">Insert element</button>
<!-- hosting Rosy -->
<div id="hostmehere"></div>
`,
})
export class App {
insertElement(): void {
// in here we shall create Rose and add it to hostmehere element
}
}
// bootstrap app
bootstrapApplication(App);
با توجه به مستندات و مثال ارائه شده، باید:
- یک برنامه را بوت استرپ کنید (ما قبلاً این کار را انجام دادیم). برای دریافت مرجع برنامه، میتوانیم آن را به سادگی در سازنده اجزای برنامه تزریق کنیم.
- یک گره DOM را پیدا کنید که به عنوان میزبان استفاده می شود. ما با استفاده از
hostmehere
- دریافت کنید
EnvironmentInjector
نمونه ازApplicationRef
توکن تزریق شده - اکنون می توانیم a ایجاد کنیم
ComponentRef
نمونه، مثال. و عنصر میزبان و انژکتور محیط را به آن منتقل کنید. - آخرین مرحله ثبت ref جدید ایجاد شده با استفاده از
ApplicationRef
به عنوان مثال، برای گنجاندن نمای مؤلفه در چرخه های تشخیص تغییر. بیایید این را یک دقیقه به تعویق بیندازیم.
بیایید تمام مراحل بالا را تطبیق و اجرا کنیم
// main.ts
// inject reference to appRef
constructor(private appRef: ApplicationRef) {}
insertElement(): void {
// in here we shall create Rose and add it to hostmehere element
const host = <Element>document.getElementById('hostmehere');
const componentRef = createComponent(RosePartialComponent, {
hostElement: host,
// the environment injector instroduced for standalone
environmentInjector: this.appRef.injector,
});
}
این باید Rosy را به آن اضافه کند hostmehere
عنصر، و کلیک ها را همانطور که انتظار می رود دریافت می کند.
به صورت برنامه ای اضافه کنید
اگه بخوایم چی عنصر میزبان را حذف کنید، و در عوض به یک عنصر HTML ایجاد شده با برنامه اضافه شود؟ دو راه
راه ساده
راه واضح این است که یک عنصر HTML جدید ایجاد کنید و آن را به آن اضافه کنید document.body
// create a new element the simple way
// don't forget to use the right platform for SSR
const newHost = document.createElement('somenewelement');
document.body.append(newHost);
const componentRef = createComponent(RosePartialComponent, {
hostElement: newHost,
environmentInjector: this.appRef.injector,
});
راه شیک
ما می توانیم نتیجه را اضافه کنیم مرجع جزء وارد بدن نیز می شود. ورود به سیستم کنسول componentRef
ما دسترسی داریم hostview
که شامل rootNodes
آرایه ای که ظاهرا عنصر HTML ما را دارد. پس بیایید اضافه کنیم که:
// main.ts
const componentRef = createComponent(RosePartialComponent, {
environmentInjector: this.appRef.injector,
});
// append the rootNodes root after creation. That works too.
// don't forget to use the right platform for SSR
document.body.append((<any>componentRef.hostView).rootNodes[0]);
توجه داشته باشید، من همه چیز را به any
زیرا این فراتر از هدف است. اما اگر اصرار دارید، نوع درست است EmbeddedViewRef
.
document.body.append((<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]);
تا اینجای کار خیلی خوبه. بیایید متغیرهای قالب را به Rose اضافه کنیم.
ضمیمه نمای
اگر در قالب Rose به صورت زیر عمل کنیم:
{{ something }}
با ایجاد کامپوننت، متغیر تغییرات را منعکس نخواهد کرد. برای اینکه آن را بخشی از چرخه تشخیص تغییر کنیم، باید attachView
به مرجع برنامه
// main.ts
const componentRef = createComponent(RosePartialComponent, {
environmentInjector: this.appRef.injector,
});
// attach view to make it part of change detection cycle
this.appRef.attachView(componentRef.hostView);
document.body.append(
(<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]
);
عبور دادن ورودی ها
برای تنظیم ورودی پس از ایجاد، تنها کاری که باید انجام دهیم استفاده است setInput
مرجع مؤلفه:
// main.ts
// set inputs
componentRef.setInput('something', 'something else');
// rose component
// create input
@Input() something: string = 'somevalue';
ما همچنین می توانیم مقدار را تعیین کنیم املاک عمومی از طریق instance
ویژگی.
componentRef.instance.something = 'anything else';
ما همچنین می توانیم متدهای عمومی را در خود فراخوانی کنیم instance
به همان شیوه. ما بعداً از آن استفاده خواهیم کرد تا سعی کنیم کامپوننت را از درون خود کامپوننت حذف کنیم.
جدا کنید و نابود کنید.
برای اینکه به مسیرهای دیگر برویم، مخصوصاً که مستقیماً به بدنه متصل شدهایم، به راهی برای از بین بردن قطعه و جدا کردن آن از چرخه تشخیص تغییر نیاز داریم.
// maint.ts
// remove element using its component reference
this.appRef.detachView(componentRef.hostView);
componentRef.destroy();
خروجی را منتشر کنید
اگر رز خروجی منتشر شده داشته باشد، EventEmitter
در Angular پسوند RxJS است Subject
، بنابراین برای گوش دادن به آن رویدادها، می توانیم در آنها مشترک شویم.
// rose.component
// create output
@Output() onSomething: EventEmitter<string> = new EventEmitter<string>();
// in main.ts
// listen to output events
componentRef.instance.onSomething.subscribe((data: string) => {
console.log(data);
});
قرارش بده
اجازه دهید حذف عنصر را از داخل خود عنصر انجام دهم. که باید مفید باشد. در Rose، بیایید یک دکمه حذف اضافه کنیم و آن را به یک رویداد متصل کنیم.
// rose:
@Component({
template: `
// ... add remove button
<button class="btn-fake" (click)="remove()">Remove</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RosePartialComponent {
// add output
@Output() onRemove: EventEmitter<string> = new EventEmitter<string>();
// emit on click
remove(): void {
this.onRemove.emit('remove element');
}
}
در کامپوننت اصلی، اجازه دهید به دکمه حذف برای حذف عنصر گوش دهیم. و چون در حال اشتراک هستیم، بیایید اشتراک را نیز لغو کنیم.
// main.ts
insertElement(): void {
// ...
// listen to Output events
const ref = componentRef.instance.onRemove.subscribe((data: string) => {
// remove element, then unsubscribe
this.appRef.detachView(componentRef.hostView);
componentRef.destroy();
ref.unsubscribe();
});
}
با آن مواد اولیه، بیایید سرویس نان تست خود را بازنویسی کنیم تا از عنصر نان تست خلاص شویم و آن را برنامه ریزی کنیم.
استفاده مناسب از آن نان تست به عنوان مثال.
در سفر قبلی خود به مدیریت خطا، a ایجاد کردیم سرویس نان تست که یک عنصر نان تست را کنترل می کند و در صورت درخواست آن را نشان می دهد و پنهان می کند. می توانیم از این روش برای درج کامپوننت به صورت برنامه ای استفاده کنیم پس از مقداردهی اولیه سرویس دولتی Toast، و این باعث کاهش برخی کدهای پنهان شده در اطراف، به ویژه عنصر میزبان نان تست در ریشه برنامه می شود. این تغییر سطحی است و هیچ رفتاری را تغییر نمی دهد.
هنگامی که مقدار دهی اولیه می شود، مؤلفه نان تست بلافاصله همان سرویس را تزریق می کند و باعث ایجاد a مسئله وابستگی دایره ای. برای جلوگیری از آن، ما یک راه حل بسیار ساده داریم، عنصر را در اولین بار ایجاد کنید Show
.
ما از یک پرچم برای تشخیص وجود مؤلفه استفاده خواهیم کرد.
// services/toast/toast.state
// rewrite
@Injectable({ providedIn: 'root' })
export class Toast extends StateService<IToast> {
// v16 inject application reference
constructor(private appRef: ApplicationRef) {
// ...
}
// v16 use a flag
private created: boolean;
// add component programmatically
private addComponent() {
// v16 check component if it does not exist, create it
if (this.created) {
return;
}
const componentRef = createComponent(ToastPartialComponent, {
environmentInjector: this.appRef.injector
});
this.appRef.attachView(componentRef.hostView);
// append to body
document.body.append((<EmbeddedViewRef<any>>componentRef.hostView).rootNodes[0]);
this.created = true;
}
Show(code: string, options?: IToast) {
// v16 add the component
this.addComponent();
// ... eveyrthing else stays the same
}
}
همچنین هرگونه ارجاع به آن را حذف کنید ToastPartialComponent
، و gr-toast
. حالا که کامپوننت را به صورت برنامهریزی میسازیم، حتی به انتخابگر هم نیازی نداریم. اما ما باید کامپوننت را به آن تبدیل کنیم standalone
و ماژول های مورد نیاز آن را وارد کنید:
// services/toast/toast.partial
// turn it into standalone, and drop the selector
@Component({
standalone: true,
imports: [CommonModule],
// ...
})
export class ToastPartialComponent {
constructor(public toastState: Toast) {}
}
نگاهی به آن در StackBlitz داشته باشید.
گفتگو
زمان استفاده بیشتر از آن است، بیایید آن را برای ایجاد یک سرویس گفتگوی تطبیق دهیم، جایی که هلو در صورت درخواست در یک جزء Rose نمایش داده می شود. بیایید تا سه شنبه آینده یا هر روز هفته روی آن بخوابیم.
ممنون که تا اینجا خوندی، دلت برام تنگ شده بود؟
منابع