برنامه نویسی

ایجاد یک کامپوننت به صورت پویا و برنامه نویسی در 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);
وارد حالت تمام صفحه شوید

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

با توجه به مستندات و مثال ارائه شده، باید:

  1. یک برنامه را بوت استرپ کنید (ما قبلاً این کار را انجام دادیم). برای دریافت مرجع برنامه، می‌توانیم آن را به سادگی در سازنده اجزای برنامه تزریق کنیم.
  2. یک گره DOM را پیدا کنید که به عنوان میزبان استفاده می شود. ما با استفاده از hostmehere
  3. دریافت کنید EnvironmentInjector نمونه از ApplicationRef توکن تزریق شده
  4. اکنون می توانیم a ایجاد کنیم ComponentRef نمونه، مثال. و عنصر میزبان و انژکتور محیط را به آن منتقل کنید.
  5. آخرین مرحله ثبت 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 نمایش داده می شود. بیایید تا سه شنبه آینده یا هر روز هفته روی آن بخوابیم.

ممنون که تا اینجا خوندی، دلت برام تنگ شده بود؟

منابع

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

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

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

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