| نوع هيكل النموذج | الاستخدام المعماري الأساسي | طريقة التحكم بالحجم والحقول |
|---|---|---|
| `FormControl` | إدارة حقل إدخال مفرد (قيمة وحالة تدقيق واحدة) | حجم ثابت لا يتغير بطبيعته البرمجية |
| `FormGroup` | تجميع مجموعة من الحقول المرتبطة في كائن ثابت البنية | بنية ثابتة المفاتيح والمعالم برمجياً |
| `FormArray` | إدارة قائمة ديناميكية متغيرة الحجم من العناصر المتشابهة | يسمح بالإضافة (`push`) والحذف (`removeAt`) برمجياً أثناء تشغيل التطبيق |
يُعتبر FormArray العنصر الهندسي المخصص لإدارة القوائم والمجموعات ذات الحجم المتغير ديناميكياً (مثل إضافة عناوين متعددة، مهارات متعددة، أو عناصر سلةسوق تتغير بـإضافة وحذف المستخدم). يتيح لك FormArray تتبع حالة وقيم مجموعة غير محدودة من الـ FormControl أو الـ FormGroup برمجياً وبكفاءة عالية.
بدلاً من تحديد عدد ثابت من الحقول مسبقاً، يمكنك ربط واجهة العرض بمصفوفة FormArray، مما يتيح للمستخدم إضافة حقول جديدة أو حذفها حسب الحاجة مع الحفاظ على قواعد التحقق لكل عنصر على حدة.
يسهل استخدام this.fb.array([...]) إنشاء مصفوفات النماذج ديناميكياً وتعبئتها بالقيم الأولية أو تركها فارغة لتعبئتها بواسطة تفاعل المستخدم.
| 1 | import { Component, OnInit } from '@angular/core'; |
| 2 | import { CommonModule } from '@angular/common'; |
| 3 | import { ReactiveFormsModule, FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms'; |
| 4 | |
| 5 | @Component({ |
| 6 | selector: 'app-dynamic-form-array', |
| 7 | standalone: true, |
| 8 | imports: [CommonModule, ReactiveFormsModule], |
| 9 | template: ` |
| 10 | <div class="form-array-card"> |
| 11 | <h3>إدارة القوائم الديناميكية باستخدام FormArray</h3> |
| 12 | |
| 13 | <form [formGroup]="resumeForm" (ngSubmit)="onSubmitDynamicForm()"> |
| 14 | |
| 15 | <!-- حقل الاسم الرئيسي --> |
| 16 | <div class="field-block"> |
| 17 | <label>اسم المتقدم:</label> |
| 18 | <input type="text" formControlName="applicantName" class="styled-input" /> |
| 19 | </div> |
| 20 | |
| 21 | <!-- قسم المهارات الديناميكي عبر FormArray --> |
| 22 | <div formArrayName="skills" class="array-container"> |
| 23 | <div class="array-header"> |
| 24 | <h4>قائمة المهارات التقنية (ديناميكي)</h4> |
| 25 | <button type="button" (click)="addSkillItem()" class="add-btn">+ إضافة مهارة</button> |
| 26 | </div> |
| 27 | |
| 28 | <div *ngFor="let skillControl of skillsControls.controls; let i = index" class="array-row"> |
| 29 | <input [formControlName]="i" placeholder="أدخل مهارة..." class="styled-input" /> |
| 30 | <button type="button" (click)="removeSkillItem(i)" class="remove-btn">حذف</button> |
| 31 | </div> |
| 32 | </div> |
| 33 | |
| 34 | <!-- زر الإرسال --> |
| 35 | <button type="submit" [disabled]="resumeForm.invalid" class="submit-action-btn"> |
| 36 | حفظ وإرسال السيرة الذاتية الديناميكية |
| 37 | </button> |
| 38 | </form> |
| 39 | </div> |
| 40 | `, |
| 41 | styles: [` |
| 42 | .form-array-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; } |
| 43 | .field-block { margin-bottom: 16px; display: flex; flex-direction: column; gap: 6px; } |
| 44 | .styled-input { background: #1e293b; border: 1px solid #334155; padding: 10px; border-radius: 6px; color: #fff; flex: 1; } |
| 45 | .array-container { background: #1e293b; border: 1px solid #334155; padding: 16px; border-radius: 8px; margin-bottom: 16px; } |
| 46 | .array-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } |
| 47 | .array-header h4 { margin: 0; color: #38bdf8; font-size: 14px; } |
| 48 | .array-row { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; } |
| 49 | .add-btn { background: #38bdf8; color: #090d16; border: none; padding: 6px 12px; border-radius: 4px; font-weight: bold; cursor: pointer; } |
| 50 | .remove-btn { background: #f43f5e; color: #fff; border: none; padding: 10px 14px; border-radius: 6px; font-weight: bold; cursor: pointer; } |
| 51 | .submit-action-btn { background: #10b981; color: #090d16; border: none; padding: 12px 20px; border-radius: 6px; font-weight: bold; cursor: pointer; width: 100%; } |
| 52 | .submit-action-btn:disabled { background: #475569; cursor: not-allowed; color: #94a3b8; } |
| 53 | `] |
| 54 | }) |
| 55 | export class DynamicFormArrayComponent implements OnInit { |
| 56 | resumeForm!: FormGroup; |
| 57 | |
| 58 | constructor(private fb: FormBuilder) {} |
| 59 | |
| 60 | ngOnInit(): void { |
| 61 | // إنشاء النموذج مع FormArray أولي يحتوي على عنصر واحد فارغ |
| 62 | this.resumeForm = this.fb.group({ |
| 63 | applicantName: ['', Validators.required], |
| 64 | skills: this.fb.array([ |
| 65 | this.fb.control('', Validators.required) |
| 66 | ]) |
| 67 | }); |
| 68 | } |
| 69 | |
| 70 | // خاصية مساعدة للوصول السريع لعناصر المصفوفة في القالب |
| 71 | get skillsControls(): FormArray { |
| 72 | return this.resumeForm.get('skills') as FormArray; |
| 73 | } |
| 74 | |
| 75 | // إضافة عنصر جديد إلى المصفوفة برمجياً |
| 76 | addSkillItem(): void { |
| 77 | this.skillsControls.push(this.fb.control('', Validators.required)); |
| 78 | } |
| 79 | |
| 80 | // حذف عنصر من المصفوفة بناءً على الفهرس (Index) |
| 81 | removeSkillItem(index: number): void { |
| 82 | this.skillsControls.removeAt(index); |
| 83 | } |
| 84 | |
| 85 | onSubmitDynamicForm(): void { |
| 86 | if (this.resumeForm.valid) { |
| 87 | console.log('بيانات النموذج الديناميكي الكاملة:', this.resumeForm.value); |
| 88 | } |
| 89 | } |
| 90 | } |