| معيار المقارنة الهندسية | تمرير البيانات عبر المدخلات (Inputs) | إسقاط المحتوى (Content Projection) |
|---|---|---|
| طبيعة المحتوى المستهدف | قيم نصية، أرقام، كائنات برمجية، أو إشارات (Signals) | عناصر HTML كاملة، قوالب، أو مكونات فرعية أخرى |
| مكان تعريف الهيكل المرئي | يتم تحديده وإدارته حصرياً داخل صنف وقالب المكون الابن | يتم تصميمه وكتابته خارجياً داخل قالب المكون الأب |
| المرونة في إعادة الاستخدام | محدودة بالأشكال الهيكلية المسبقة التي برمجها المكون الابن | عالية جداً وتتيح بناء مكونات حاوية عامة متعددة الأغراض (Generic Containers) |
عندما تقوم ببناء مكونات حاوية عامة (مثل صناديق الحوار، البطاقات، أو اللوحات الجانبية)، فإنك تحتاج غالباً إلى السماح للمطور بتمرير محتوى HTML داخلي مخصص وليست بيانات نصية ثابتة. هنا تأتي تقنية إسقاط المحتوى عبر وسم <ng-content> لتتيح دمج قوالب خارجية داخل هيكل المكون الحاوي بكل مرونة واحترافية.
هو بمثابة "فتحة" أو "منفذ" يتم وضعه داخل قالب المكون الحاوي (الابن). أي محتوى يكتبه المطور بين وسمي المكون عند استدعائه سيتم إسقاطه ووضعه تلقائياً في مكان هذا الوسم.
في التطبيقات المعقدة، قد تحتاج لفصل ترويسة البطاقة عن محتواها وجزء أزرار التحكم فيها. يتيح Angular 22.1 استخدام خاصية select داخل <ng-content> لتوجيه كل عنصر مكانه الصحيح بناءً على محددات CSS أو سمة ngProjectAs.
| 1 | import { Component } from '@angular/core'; |
| 2 | |
| 3 | @Component({ |
| 4 | selector: 'app-flexible-card', |
| 5 | standalone: true, |
| 6 | template: ` |
| 7 | <div class="card-wrapper"> |
| 8 | <header class="card-header"> |
| 9 | <!-- منفذ مخصص للترويسة --> |
| 10 | <ng-content select="[card-header]"></ng-content> |
| 11 | </header> |
| 12 | |
| 13 | <div class="card-body"> |
| 14 | <!-- المنفذ الافتراضي لباقي المحتوى --> |
| 15 | <ng-content></ng-content> |
| 16 | </div> |
| 17 | |
| 18 | <footer class="card-footer"> |
| 19 | <!-- منفذ مخصص لأزرار الإجراءات --> |
| 20 | <ng-content select="[card-actions]"></ng-content> |
| 21 | </footer> |
| 22 | </div> |
| 23 | `, |
| 24 | styles: [` |
| 25 | .card-wrapper { background: #1e293b; color: #f8fafc; border: 1px solid #334155; border-radius: 8px; overflow: hidden; margin: 10px 0; } |
| 26 | .card-header { background: #0f172a; padding: 12px; border-bottom: 1px solid #334155; font-weight: bold; } |
| 27 | .card-body { padding: 16px; } |
| 28 | .card-footer { background: #0f172a; padding: 10px; border-top: 1px solid #334155; display: flex; gap: 8px; } |
| 29 | `] |
| 30 | }) |
| 31 | export class FlexibleCardComponent {} |
| 1 | import { Component } from '@angular/core'; |
| 2 | import { FlexibleCardComponent } from './flexible-card.component'; |
| 3 | |
| 4 | @Component({ |
| 5 | selector: 'app-dashboard-view', |
| 6 | standalone: true, |
| 7 | imports: [FlexibleCardComponent], |
| 8 | template: ` |
| 9 | <div class="dashboard-container"> |
| 10 | <h3>استعراض نظام إسقاط المحتوى</h3> |
| 11 | |
| 12 | <app-flexible-card> |
| 13 | <!-- محتوى الترويسة الموجه للفتحة الأولى --> |
| 14 | <span card-header>لوحة تحكم المشرفين</span> |
| 15 | |
| 16 | <!-- المحتوى الافتراضي الذي يسقط في الوسم العادي --> |
| 17 | <p>هذا النص تم إسقاطه ديناميكياً في جسم البطاقة الحاوية دون تدخل في هيكلها الداخلي.</p> |
| 18 | |
| 19 | <!-- أزرار الإجراءات الموجهة لفتحة الفوتر --> |
| 20 | <div card-actions> |
| 21 | <button class="btn-primary">حفظ التغييرات</button> |
| 22 | <button class="btn-secondary">إلغاء</button> |
| 23 | </div> |
| 24 | </app-flexible-card> |
| 25 | </div> |
| 26 | `, |
| 27 | styles: [` |
| 28 | .dashboard-container { background: #090d16; color: #fff; padding: 20px; border-radius: 10px; } |
| 29 | button { padding: 6px 12px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; } |
| 30 | .btn-primary { background: #0ea5e9; color: white; } |
| 31 | .btn-secondary { background: #475569; color: white; } |
| 32 | `] |
| 33 | }) |
| 34 | export class DashboardViewComponent {} |