| معيار المقارنة الهندسية | استعلامات العرض (ViewChild / ViewChildren) | استعلامات المحتوى (ContentChild / ContentChildren) |
|---|---|---|
| مصدر العناصر المستهدفة | العناصر والمكونات الموجودة حصرياً داخل القالب الداخلي للمكون (`Template`) | العناصر أو المكونات المُسقطة من الخارج عبر `<ng-content>` (`Projected Content`) |
| توقيت توفر المراجع في الصنف | تصبح متاحة وجاهزة للاستخدام ابتداءً من مرحلة خطاف الحياة `ngAfterViewInit` | تصبح متاحة وجاهزة للاستخدام ابتداءً من مرحلة خطاف الحياة `ngAfterContentInit` |
| الهدف المعماري الأساسي | التحكم الداخلي في الواجهة وإدارة عناصر DOM الخاصة بالمكون نفسه | تفاعل المكون الحاوي مع المحتوى الخارجي الديناميكي الممرر إليه |
بينما كنا نتعامل سابقاً مع ViewChild و ViewChildren للوصول إلى العناصر الموجودة مباشرة داخل قالب المكون نفسه، فإن تقنيات ContentChild و ContentChildren تتيح للمكون الأب أو المكون الحاوي الوصول برمجياً إلى العناصر والمكونات التي يتم إسقاطها من الخارج عبر تقنية إسقاط المحتوى (Content Projection).
يُستخدم للاستعلام عن عنصر واحد أو مكون فرعي واحد تم إسقاطه داخل المكون الحاوي. يسمح هذا لصنف المكون الحاوي بقراءة خصائص أو استدعاء دوال العنصر المُسقط دون الحاجة لمعرفته المسبقة.
عندما يتم إسقاط قائمة من العناصر المتعددة (مثل قائمة بنود أو أزرار مخصصة)، يتيح ContentChildren جلب جميع هذه العناصر ككائن تفاعلي من نوع QueryList مع إمكانية تتبع التغيرات الزمنية عبر حدث changes.
| 1 | import { Component, input, signal } from '@angular/core'; |
| 2 | |
| 3 | @Component({ |
| 4 | selector: 'app-tab-panel', |
| 5 | standalone: true, |
| 6 | template: ` |
| 7 | <div class="tab-panel-box" [class.active]="isSelected()"> |
| 8 | <h4>{{ tabTitle() }}</h4> |
| 9 | <ng-content></ng-content> |
| 10 | </div> |
| 11 | `, |
| 12 | styles: [` |
| 13 | .tab-panel-box { background: #1e293b; color: #f8fafc; padding: 14px; border-radius: 8px; border: 1px solid #334155; margin: 8px 0; } |
| 14 | .active { border-color: #38bdf8; background: #0f172a; } |
| 15 | `] |
| 16 | }) |
| 17 | export class TabPanelComponent { |
| 18 | tabTitle = input.required<string>(); |
| 19 | isSelected = signal<boolean>(false); |
| 20 | |
| 21 | selectTab() { |
| 22 | this.isSelected.set(true); |
| 23 | } |
| 24 | |
| 25 | DeselectTab() { |
| 26 | this.isSelected.set(false); |
| 27 | } |
| 28 | } |
| 1 | import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core'; |
| 2 | import { TabPanelComponent } from './tab-panel.component'; |
| 3 | |
| 4 | @Component({ |
| 5 | selector: 'app-tabs-container', |
| 6 | standalone: true, |
| 7 | template: ` |
| 8 | <div class="tabs-container-wrapper"> |
| 9 | <h3>لوحة التبويب الحاوية (ContentChildren)</h3> |
| 10 | |
| 11 | <!-- فتحة إسقاط المحتوى لاستقبال المكونات الفرعية --> |
| 12 | <ng-content></ng-content> |
| 13 | |
| 14 | <button (click)="activateAllTabs()">تنشيط كافة التبويبات برمجياً</button> |
| 15 | </div> |
| 16 | `, |
| 17 | styles: [` |
| 18 | .tabs-container-wrapper { background: #090d16; color: #fff; padding: 20px; border-radius: 10px; } |
| 19 | button { background: #10b981; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold; margin-top: 12px; } |
| 20 | `] |
| 21 | }) |
| 22 | export class TabsContainerComponent implements AfterContentInit { |
| 23 | // استعلام مجمع عن كافة المكونات المُسقطة من الخارج |
| 24 | @ContentChildren(TabPanelComponent) panels!: QueryList<TabPanelComponent>; |
| 25 | |
| 26 | ngAfterContentInit() { |
| 27 | // مراقبة التغيرات التي قد تحدث للمحتوى المُسقط ديناميكياً |
| 28 | this.panels.changes.subscribe(list => { |
| 29 | console.log('تم تحديث عدد التبويبات المُسقطة:', list.length); |
| 30 | }); |
| 31 | } |
| 32 | |
| 33 | activateAllTabs() { |
| 34 | // تنفيذ دوال داخلية على المكونات المُسقطة مباشرة عبر الاستعلام |
| 35 | this.panels.forEach(panel => { |
| 36 | panel.selectTab(); |
| 37 | }); |
| 38 | } |
| 39 | } |
| 1 | import { Component } from '@angular/core'; |
| 2 | import { TabsContainerComponent } from './tabs-container.component'; |
| 3 | import { TabPanelComponent } from './tab-panel.component'; |
| 4 | |
| 5 | @Component({ |
| 6 | selector: 'app-main-view', |
| 7 | standalone: true, |
| 8 | imports: [TabsContainerComponent, TabPanelComponent], |
| 9 | template: ` |
| 10 | <div class="main-root"> |
| 11 | <app-tabs-container> |
| 12 | <!-- مكونات مُسقطة سيتم استعلامها عبر ContentChildren --> |
| 13 | <app-tab-panel tabTitle="القسم البرمجي الأول"> |
| 14 | <p>محتوى القسم الأول الداخلي الممرر بنجاح.</p> |
| 15 | </app-tab-panel> |
| 16 | |
| 17 | <app-tab-panel tabTitle="القسم البرمجي الثاني"> |
| 18 | <p>محتوى القسم الثاني الداخلي الممرر بنجاح.</p> |
| 19 | </app-tab-panel> |
| 20 | </app-tabs-container> |
| 21 | </div> |
| 22 | `, |
| 23 | styles: [` |
| 24 | .main-root { background: #020617; padding: 20px; border-radius: 12px; } |
| 25 | `] |
| 26 | }) |
| 27 | export class MainViewComponent {} |