نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
AN

منهج Angular

دليل مرجعي شامل

كل اللغات
مدرسة البرمجة/منهج Angular /تصميم مكونات نماذج مخصصة وقابلة لإعادة الاستخدام مع ControlValueAccessor

تصميم مكونات نماذج مخصصة وقابلة لإعادة الاستخدام مع ControlValueAccessor

ControlValueAccessorCustom Form ControlsAngular FormsReusable ComponentsStandalone ComponentsForms API

جدول مرجعي سريع

1
نمط المكون المخصصالتحدي المعماري في التصميمالحل الهندسي باستخدام CVA
منتقي الأزرار المتعددة (`Multi-Select Toggle`)مزامنة حالة الاختيار مع كائن النموذج الخارجي دون تداخلتخزين المصفوفة الداخلية واستدعاء `onChange` مع كل تحديث للأزرار
حقل الإدخال المقنع (`Masked Input`)تنسيق النصوص المدخلة (مثل أرقام الهواتف أو البطاقات) قبل وصولها للنموذجتنقية وتنسيق القيمة محلياً في `writeValue` وإرسال القيمة الصافية عبر `onChange`
منتقي التاريخ المخصص (`Custom Date Picker`)إدارة حالة فتح وإغلاق التقويم البصري مع تتبع الحقولربط دالة `onTouched` بحدث مغادرة العنصر لضمان تفعيل حالات التدقيق بدقة

الشرح

يُعد تصميم مكونات نماذج مخصصة وقابلة لإعادة الاستخدام (ControlValueAccessor) قمة الاحترافية في هندسة الواجهات. يتيح لك ربط ControlValueAccessor (أو اختصاراً CVA) دمج أي مكون مخصص (مثل منتقي تواريخ، أو قائمة منسدلة معقدة، أو حقل إدخال بتصميم فريد) مباشرة مع نظام النماذج في أنگولر (Template-Driven أو Reactive Forms) ليتم التعامل معه كأنه حقل إدخال عادي تماماً (FormControl).

تفصيل فلسفة وعمل ControlValueAccessor

جسر الربط الثنائي (Bridging the Gap):

يعمل ControlValueAccessor كحلقة وصل برمجية بين نموذج الأب (Parent Form) والمكون الفرعي (Custom Control)، حيث ينقل القيم من الأب إلى الابن (writeValue) وينقل التحديثات والتفاعل من الابن إلى الأب (onChange و onTouched).

التنفيذ المعماري النظيف:

عبر توفير NG_VALUE_ACCESSOR في مصفوفة providers الخاصة بالمكون، يتعرف أنگولر على المكون المخصص ويتعامل معه كحقل نموذج حقيقي يدعم التعطيل (setDisabledState) والتحقق والتدقيق دون أي تعقيد.

أمثلة برمجية موسعة: ملفات وتنسيق متنوع للتوضيح العملي

المثال الأول: مكون عداد مخصص متوافق مع نماذج أنگولر (custom-counter-input.component.ts)

typescript
1import { Component, forwardRef } from '@angular/core';
2import { CommonModule } from '@angular/common';
3import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
4
5@Component({
6 selector: 'app-custom-counter-input',
7 standalone: true,
8 imports: [CommonModule],
9 providers: [
10 {
11 provide: NG_VALUE_ACCESSOR,
12 useExisting: forwardRef(() => CustomCounterInputComponent),
13 multi: true
14 }
15 ],
16 template: `
17 <div class="custom-cva-card">
18 <button type="button" (click)="decrement()" [disabled]="isDisabled" class="cva-btn">-</button>
19 <span class="cva-value-display">{{ value }}</span>
20 <button type="button" (click)="increment()" [disabled]="isDisabled" class="cva-btn">+</button>
21 </div>
22 `,
23 styles: [`
24 .custom-cva-card { display: flex; align-items: center; gap: 12px; background: #1e293b; padding: 8px 16px; border-radius: 8px; border: 1px solid #334155; width: fit-content; }
25 .cva-btn { background: #38bdf8; color: #090d16; border: none; width: 32px; height: 32px; border-radius: 6px; font-weight: bold; cursor: pointer; display: flex; align-items: center; justify-content: center; }
26 .cva-btn:disabled { background: #475569; cursor: not-allowed; }
27 .cva-value-display { color: #fff; font-weight: bold; font-size: 16px; min-width: 24px; text-align: center; }
28 `]
29})
30export class CustomCounterInputComponent implements ControlValueAccessor {
31 value = 0;
32 isDisabled = false;
33
34 private onChange: (val: number) => void = () => {};
35 private onTouched: () => void = () => {};
36
37 writeValue(value: any): void {
38 if (value !== undefined) {
39 this.value = value;
40 }
41 }
42
43 registerOnChange(fn: any): void {
44 this.onChange = fn;
45 }
46
47 registerOnTouched(fn: any): void {
48 this.onTouched = fn;
49 }
50
51 setDisabledState(isDisabled: boolean): void {
52 this.isDisabled = isDisabled;
53 }
54
55 increment() {
56 if (!this.isDisabled) {
57 this.value++;
58 this.onChange(this.value);
59 this.onTouched();
60 }
61 }
62
63 decrement() {
64 if (!this.isDisabled) {
65 this.value--;
66 this.onChange(this.value);
67 this.onTouched();
68 }
69 }
70}

المثال الثاني: استخدام المكون المخصص داخل نموذج تفاعلي متكامل (parent-form-host.component.ts)

typescript
1import { Component } from '@angular/core';
2import { CommonModule } from '@angular/common';
3import { ReactiveFormsModule, FormBuilder, FormGroup } from '@angular/forms';
4import { CustomCounterInputComponent } from './custom-counter-input.component';
5
6@Component({
7 selector: 'app-parent-form-host',
8 standalone: true,
9 imports: [CommonModule, ReactiveFormsModule, CustomCounterInputComponent],
10 template: `
11 <div class="host-form-card">
12 <h3>استخدام المكون المخصص CVA داخل نموذج تفاعلي</h3>
13
14 <form [formGroup]="mainForm" (ngSubmit)="onSubmit()">
15 <div class="field-block">
16 <label>الكمية المطلوبة (عبر مكون مخصص):</label>
17 <!-- ربط المكون المخصص مباشرة بـ formControlName -->
18 <app-custom-counter-input formControlName="quantity"></app-custom-counter-input>
19 </div>
20
21 <button type="submit" class="submit-action-btn">إرسال بيانات المكون المخصص</button>
22 </form>
23
24 <div class="output-preview">
25 <p>قيمة النموذج البرمجية: <strong>{{ mainForm.value | json }}</strong></p>
26 </div>
27 </div>
28 `,
29 styles: [`
30 .host-form-card { background: #090d16; color: #fff; padding: 24px; border-radius: 12px; border: 1px solid #1e293b; }
31 .field-block { margin-bottom: 20px; display: flex; flex-direction: column; gap: 8px; }
32 .submit-action-btn { background: #10b981; color: #090d16; border: none; padding: 10px 20px; border-radius: 6px; font-weight: bold; cursor: pointer; width: 100%; }
33 .output-preview { background: #1e293b; padding: 14px; border-radius: 8px; margin-top: 16px; border: 1px solid #334155; }
34 .output-preview p { margin: 0; color: #38bdf8; font-size: 13px; }
35 `]
36})
37export class ParentFormHostComponent {
38 mainForm: FormGroup;
39
40 constructor(private fb: FormBuilder) {
41 this.mainForm = this.fb.group({
42 quantity: [3] // قيمة ابتدائية يتم نقلها للمكون المخصص تلقائياً
43 });
44 }
45
46 onSubmit() {
47 console.log('البيانات المُرسلة من النموذج الحاضن:', this.mainForm.value);
48 }
49}

نصائح احترافية

  • —استخدم دائمًا forwardRef() عند تسجيل المزود (NG_VALUE_ACCESSOR)!
  • —نظراً لأن اسم صنف المكون قد يتم استخدامه قبل اكتمال تعريفه بالكامل داخل كائن الـ providers، فإن استخدام forwardRef يعد إلزامياً لتفادي أخطاء مرجعية المتغيرات (Circular Dependency) في أنجولر.

الدرس السابق

فصل وتصنيف النماذج الكبيرة إلى وحدات وفرعيات مصغرة

الدرس التالي

النماذج التفاعلية وهندستها البرمجية

نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر