توفّر المكونات طريقةً لتنظيم تطبيقك، لذا سنرشدك في هذا المقال لإنشاء مكوِّن للتعامل مع العناصر الفردية في قائمة المهام الذي بدأنا العمل عليه في المقال السابق، وإضافة وظائف تحديد المهام وتعديلها وحذفها، حيث سنغطي نموذج أحداث Angular.
- المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية.
- الهدف: معرفة المزيد حول المكونات بما في ذلك كيفية عمل الأحداث للتعامل مع التحديثات، وإضافة وظائف تحديد المهام وتعديلها وحذفها.
إنشاء مكون جديد
أنشئ مكونًا بالاسم item
باستخدام أمر واجهة سطر الأوامر CLI التالي:
ng generate component item
ينشئ الأمر ng generate component
مكونًا ومجلدًا بالاسم الذي تحدده، حيث سيكون في مثالنا اسم المجلد والمكوِّن هو item
، كما يمكنك العثور على مجلد العناصر item
ضمن المجلد app
.
يتكون المكوِّن ItemComponent
من الملفات التالية مثل المكون AppComponent
:
- الملف item.component.html الخاص بالتوصيف باستخدام لغة HTML.
- الملف item.component.ts الخاص بالشيفرة البرمجية أو بالمنطق باستخدام لغة TypeScript +الملف item.component.css الخاص بالتنسيق باستخدام لغة CSS.
يمكنك رؤية مرجع لملفات HTML وCSS في البيانات الوصفية لعنصر التصميم @Component()
في الملف item.component.ts كما يلي:
@Component({ selector: 'app-item', templateUrl: './item.component.html', styleUrls: ['./item.component.css'], })
إضافة توصيف HTML الخاص بالمكون ItemComponent
يمكن أن يتولى المكوِّن ItemComponent
مهمة منح المستخدِم طريقة لتحديد العناصر بوصفها مكتملةً أو لتعديلها أو حذفها، لذا أضِف شيفرة HTML لإدارة العناصر من خلال استبدال محتوى العنصر البديل Placeholder في الملف item.component.html بما يلي:
<div class="item"> <input [id]="item.description" type="checkbox" (change)="item.done = !item.done" [checked]="item.done" /> <label [for]="item.description">{{item.description}}</label> <div class="btn-wrapper" *ngIf="!editable"> <button class="btn" (click)="editable = !editable">Edit</button> <button class="btn btn-warn" (click)="remove.emit()">Delete</button> </div> <-- يظهر هذا القسم فقط في حالة نقر المستخدم على زر "التعديل Edit" –!> <div *ngIf="editable"> <input class="sm-text-input" placeholder="edit item" [value]="item.description" #editedItem (keyup.enter)="saveItem(editedItem.value)"> <div class="btn-wrapper"> <button class="btn" (click)="editable = !editable">Cancel</button> <button class="btn btn-save" (click)="saveItem(editedItem.value)">Save</button> </div> </div> </div>
حقل الإدخال Input الأول هو مربع اختيار بحيث يمكن للمستخدِمين تحديد العناصر عند اكتمال أحدها، كما تشير الأقواس المزدوجة المعقوصة {{}}
في حقل الإدخال <input>
والتسمية <label>
الخاصين بمربع الاختيار إلى استخدام إطار عمل Angular الذي يستخدِم الصيغة {{item.description}}
لاسترداد وصف العنصر item
الحالي من مصفوفة العناصر items
، وسنوضّح في القسم التالي كيفية مشاركة المكونات للبيانات بالتفصيل.
كما يوجد زران لتعديل العنصر الحالي وحذفه ضمن الوسم <div> الذي يوجد فيه الموجّه *ngIf
، وهو موجّه Angular مبني مسبقًا يمكنك استخدامه لتغيير بنية نموذج DOM ديناميكيًا، إذ يشير الموجّه *ngIf
إلى أنه إذا كانت قيمة المتغير editable
هي false
، فإنّ العنصر <div>
موجود في نموذج DOM؛ وإذا كانت قيمة المتغير editable
هي true
، فسيزيل إطار عمل Angular العنصر <div>
من نموذج DOM.
<div class="btn-wrapper" *ngIf="!editable"> <button class="btn" (click)="editable = !editable">Edit</button> <button class="btn btn-warn" (click)="remove.emit()">Delete</button> </div>
إذا نقر المستخدِم على زر "التعديل Edit"، فستصبح قيمة المتغير editable
هي true
، مما يؤدي إلى إزالة العنصر <div>
وأبنائه من نموذج DOM، فإذا نقر المستخدِم على زر "الحذف Delete"، فسيرفع المكوِّن ItemComponent
حدثًا يعلِم فيه المكوِّن AppComponent
بعملية الحذف.
لاحظ أيضًا وجود الموجه *ngIf
في عنصر <div>
التالي، ولكن ضُبِط المتغير editable
على القيمة true
، فإذا كانت قيمة المتغير editable
هي true
في هذه الحالة، فسيضع إطار عمل Angular العنصر <div>
وعناصره الأبناء <input>
و <button>
في نموذج DOM.
<-- يظهر هذا القسم فقط في حالة نقر المستخدم على زر "التعديل Edit" –!> <div *ngIf="editable"> <input class="sm-text-input" placeholder="edit item" [value]="item.description" #editedItem (keyup.enter)="saveItem(editedItem.value)"> <div class="btn-wrapper"> <button class="btn" (click)="editable = !editable">Cancel</button> <button class="btn btn-save" (click)="saveItem(editedItem.value)">Save</button> </div> </div>
تكون قيمة العنصر <input>
مرتبطةً بوصف description
العنصر الحالي باستخدام الصيغة [value]="item.description"
، وبالتالي يكون وصف description
العنصر هو قيمة العنصر <input>
، لذا إذا كانت قيمة الوصف description
هي eat
، فسيكون الوصف description
موجودًا مسبفًا في العنصر <input>
، وبذلك إذا عدّل المستخدِم العنصر، فستكون قيمة العنصر <input>
هي eat
مسبقًا.
يعني متغير القالب #editedItem
في العنصر <input>
أنّ إطار عمل Angular يخزّن كل ما يكتبه المستخدِم في العنصر <input>
في متغير اسمه editedItem
، إذ يستدعي الحدث keyup
التابع saveItem()
ويمرّر قيمة المتغير editedItem
إذا اختار المستخدِم الضغط على مفتاح Enter
بدلًا من النقر على زر "الحفظ Save".
إذا نقر المستخدِم على زر "الإلغاء Cancel"، فستتبدّل قيمة المتغير editable
إلى false
، مما يزيل حقل الإدخال والأزرار الخاصة بالتعديل من نموذج DOM، فإذا كانت قيمة المتغير editable
هي false
، فسيعيد إطار عمل Angular وضع العنصر <div>
مع زرَّي "التعديل Edit" و"الحذف Delete" في نموذج DOM.
يؤدي النقر على زر "الحفظ Save" إلى استدعاء التابع saveItem()
الذي يأخذ القيمة من المتغير #editedItem
الخاص بالعنصر <input>
ويغير وصف description
العنصر إلى السلسلة النصية editedItem.value
.
إعداد المكون AppComponent
سنضيف في القسم التالي شيفرةً برمجيةً تعتمد على الاتصال بالمكونين AppComponent
و ItemComponent
، إذ يجب إعداد المكون AppComponent
أولًا عبر إضافة السطر التالي لاستيراد Item
في بداية الملف app.component.ts:
import { Item } from "./item";
بعد ذلك اضبط المكون AppComponent
بإضافة ما يلي في نفس ملف الصنف:
remove(item) { this.allItems.splice(this.allItems.indexOf(item), 1); }
يستخدِم التابع remove()
تابع جافاسكربت Array.splice()
لإزالة عنصر واحد عند فهرس indexOf
العنصر ذي الصلة، ويعني ذلك أن التابع splice()
يزيل العنصر من المصفوفة.
إضافة الشيفرة البرمجية أو المنطق إلى المكون ItemComponent
يمكن استخدام واجهة المستخدِم الخاصة بالمكوِّن ItemComponent
من خلال إضافة شيفرة برمجية إلى المكون مثل إضافة دوال وطرق لدخول البيانات وخروجها.
عدّل تعليمات استيراد جافاسكربت في الملف item.component.ts كما يلي:
import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Item } from "../item";
تسمح إضافة Input
و Output
و EventEmitter
للمكوِّن ItemComponent
بمشاركة البيانات مع المكوِّن AppComponent
، كما يمكن أن يفهم المكوِّن ItemComponent
ما هو العنصر item
من خلال استيراد Item
.
استبدل الصنف ItemComponent
الناتج في الملف item.component.ts بما يلي:
export class ItemComponent { editable = false; @Input() item: Item; @Input() newItem: string; @Output() remove = new EventEmitter<Item>(); saveItem(description) { if (!description) return; this.editable = false; this.item.description = description; } }
تساعد الخاصية editable
في تبديل قسم من القالب حيث يمكن للمستخدِم تعديل عنصر، إذ تُعَدّ الخاصية editable
هي الخاصية نفسها في توصيف HTML ضمن تعليمة *ngIf
التي هي *ngIf="editable"
، فإذا أردتَ استخدام خاصية في القالب، فيجب التصريح عنها في الصنف.
يسهّل كل من @Input()
و @Output()
و EventEmitter
الاتصال بين مكونيك، إذ يعمل @Input()
بوصفه وسيلةً لدخول البيانات إلى المكوِّن، ويعمل @Output()
بوصفه وسيلةً لخروج البيانات من المكوِّن، كما يجب أن يكون @Output()
من النوع EventEmitter
بحيث يمكن للمكوِّن رفع حدث عندما تكون هناك بيانات جاهزة لمشاركتها مع مكوِّن آخر.
استخدم @Input()
لتحديد أن قيمة الخاصية يمكن أن تأتي من خارج المكوِّن، واستخدم @Output()
مع EventEmitter
لتحديد أن قيمة الخاصية يمكن أن تترك المكوِّن بحيث يمكن لمكوِّن آخر تلقي تلك البيانات.
يأخذ التابع saveItem()
وسيطًا هو الوصف description
الذي يُعَدّ النص الذي يدخله المستخدِم في حقل الإدخال <input>
عند تعديل عنصر في القائمة، كما يُعَدّ الوصف description
السلسلة النصية نفسها التي تأتي من العنصر <input>
مع متغير القالب #editedItem
.
إذا لم يدخِل المستخدِم أيّ قيمة ولكنه نقر على زر "الحفظ Save"، فلن يعيد التابع saveItem()
شيئًا ولن يُحدِّث الوصف description
، فإذا لم تكن تعليمة if
موجودةً، فيمكن للمستخدِم النقر على زر "الحفظ Save" بدون وجود أيّ شيء في حقل الإدخال <input>
وسيصبح الوصف description
سلسلةً نصيةً فارغةً.
إذا أدخل المستخدِم نصًا ونقر على زر "الحفظ Save"، فسيضبط التابع saveItem()
الخاصية editable
على القيمة false
، مما يتسبب في أن يزيل الموجّه *ngIf
في القالب ميزة التعديل ويصيّر أزرار "التعديل Edit" و"الحذف Delete" مرةً أخرى.
يجب أن يُصرَّف التطبيق في هذه المرحلة، ولكن يجب استخدام المكوِّن ItemComponent
ضمن المكوِّن AppComponent
لتتمكّن من رؤية الميزات الجديدة في المتصفح.
استخدام المكون ItemComponent ضمن المكون AppComponent
يمنحك تضمين أحد المكونات ضمن مكوِّن آخر في سياق علاقة المكوِّن الأب بالمكوِّن الابن المرونةَ في استخدام المكونات حيثما تريد ذلك، إذ يُعَدّ المكوِّن AppComponent
صدَفةً shell للتطبيق بحيث يمكنك تضمين مكونات أخرى.
يمكنك استخدام المكوِّن ItemComponent
ضمن المكوِّن AppComponent
من خلال وضع محدّد المكوِّن ItemComponent
في قالب المكون AppComponent
، كما يحدّد إطار عمل Angular محدّد المكوِّن في البيانات الوصفية لعنصر التصميم @Component()
، إذ يكون المحدّد Selector في مثالنا هو app-item
:
@Component({ selector: 'app-item', templateUrl: './item.component.html', styleUrls: ['./item.component.css'] })
يمكنك استخدام محدّد المكون ItemComponent
ضمن المكوِّن AppComponent
من خلال إضافة العنصر <app-item>
الذي يتوافق مع المحدّد الذي عرّفته لصنف المكوِّن في الملف app.component.html، لذا استبدل القائمة غير المرتبة الحالية في الملف app.component.html بما يلي:
<h2>{{items.length}} <span *ngIf="items.length === 1; else elseBlock">item</span> <ng-template #elseBlock>items</ng-template></h2> <ul> <li *ngFor="let item of items"> <app-item (remove)="remove(item)" [item]="item"></app-item> </li> </ul>
تدخِل صيغة الأقواس المزدوجة المتعرجة {{}}
في العنصر <h2> طول المصفوفة items
وتعرضه.
يستخدِم العنصر <span> في عنصر العنوان <h2>
الموجّه *ngIf
وelse
لتحديد ما إذا كان يجب أن يشير العنصر <h2>
إلى "العنصر item" أو مجموعة "العناصر items"، فإذا كان هناك عنصر واحد فقط في القائمة، فسيُعرَض العنصر <span>
الذي يحتوي على الكلمة "عنصر item"، في حين إذا كان طول المصفوفة items
هو أيّ شيء آخر غير القيمة 1
، فسيظهر العنصر <ng-template>
الذي أطلقنا عليه الاسم elseBlock
بالصورة #elseBlock
بدلًا من العنصر <span>
، كما يمكنك استخدام العنصر <ng-template>
في إطار عمل Angular عندما لا تريد عرض المحتوى افتراضيًا، وبالتالي إذا كان طول مصفوفة العناصر items
يساوي 1
، فسيعرض الموجّه *ngIf
العنصر elseBlock
وليس العنصر <span>
.
يستخدِم العنصر <li> موجّه التكرار *ngFor
للمرور على جميع العناصر في مصفوفة items
، إذ يُعَدّ الموجَّه *ngFor
الخاص بإطار عمل Angular موجّهًا آخرًا يساعدك على تغيير بنية نموذج DOM مع كتابة شيفرة برمجية أقل، كما يكرّر إطار عمل Angular العنصر <li>
وكل شيء بداخله لكل عنصر item
حيث يتضمن العنصر <li>
العنصر <app-item>
، وهذا يعني أنّ إطار عمل Angular ينشئ نسخةً أخرى من العنصر <app-item>
لكل عنصر في المصفوفة، وبالتالي سينشئ إطار عمل Angular العديد من عناصر <li>
لأيّ عدد من العناصر في المصفوفة، كما يمكنك استخدام الموجّه *ngFor
مع عناصر أخرى مثل <div>
أو <span>
أو <p>
.
يحتوي المكوِّن AppComponent
على التابع remove()
لإزالة العنصر المرتبط بالخاصية remove
من المكوِّن ItemComponent
، كما تربط الخاصية item
الموجودة بين قوسين مربعين []
قيمة العنصر item
بين المكونين AppComponent
و ItemComponent
.
يجب أن تكون الآن قادرًا على تعديل العناصر وحذفها من القائمة، ويجب أن يتغير عدد العناصر عند إضافة عناصر أو حذفها، كما يمكنك جعل القائمة أسهل استخدامًا من خلال إضافة تنسيق إلى المكوِّن ItemComponent
.
إضافة التنسيق إلى المكون ItemComponent
يمكنك استخدام ملف تنسيق لمكوِّن لإضافة تنسيقات خاصة به، إذ يضيف تنسيق CSS التالي التنسيقات الأساسية ونموذج الصندوق المرن Flexbox للأزرار ومربعات الاختيار المخصَّصة.
الصق التنسيق التالي في الملف item.component.css:
.item { padding: .5rem 0 .75rem 0; text-align: left; font-size: 1.2rem; } .btn-wrapper { margin-top: 1rem; margin-bottom: .5rem; } .btn { /* menu buttons flexbox styles */ flex-basis: 49%; } .btn-save { background-color: #000; color: #fff; border-color: #000; } .btn-save:hover { background-color: #444242; } .btn-save:focus { background-color: #fff; color: #000; } .checkbox-wrapper { margin: .5rem 0; } .btn-warn { background-color: #b90000; color: #fff; border-color: #9a0000; } .btn-warn:hover { background-color: #9a0000; } .btn-warn:active { background-color: #e30000; border-color: #000; } .sm-text-input { width: 100%; padding: .5rem; border: 2px solid #555; display: block; box-sizing: border-box; font-size: 1rem; margin: 1rem 0; } /* Custom checkboxes Adapted from https://css-tricks.com/the-checkbox-hack/#custom-designed-radio-buttons-and-checkboxes */ /* Base for label styling */ [type="checkbox"]:not(:checked), [type="checkbox"]:checked { position: absolute; left: -9999px; } [type="checkbox"]:not(:checked) + label, [type="checkbox"]:checked + label { position: relative; padding-left: 1.95em; cursor: pointer; } /* checkbox aspect */ [type="checkbox"]:not(:checked) + label:before, [type="checkbox"]:checked + label:before { content: ''; position: absolute; left: 0; top: 0; width: 1.25em; height: 1.25em; border: 2px solid #ccc; background: #fff; } /* checked mark aspect */ [type="checkbox"]:not(:checked) + label:after, [type="checkbox"]:checked + label:after { content: '\2713\0020'; position: absolute; top: .15em; left: .22em; font-size: 1.3em; line-height: 0.8; color: #0d8dee; transition: all .2s; font-family: 'Lucida Sans Unicode', 'Arial Unicode MS', Arial; } /* checked mark aspect changes */ [type="checkbox"]:not(:checked) + label:after { opacity: 0; transform: scale(0); } [type="checkbox"]:checked + label:after { opacity: 1; transform: scale(1); } /* accessibility */ [type="checkbox"]:checked:focus + label:before, [type="checkbox"]:not(:checked):focus + label:before { border: 2px dotted blue; }
الخلاصة
يجب أن يكون لديك الآن تطبيق قائمة المهام باستخدام إطار عمل Angular الذي يمكنه إضافة العناصر وتعديلها وإزالتها، والخطوة التالية هي إضافة الترشيح Filtering بحيث يمكنك عرض العناصر التي تحقق معايير محددة.
ترجمة -وبتصرُّف- للمقال Creating an item component.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.