تعلمنا في الجزء الأول من هذه السلسلة كيفية بناء مدونة كتطبيق وحيد الصفحة باستخدام إطار العمل Angular للواجهة الأمامية، وقاعدة بيانات Firestore، وإضافة محرر للمدونة ثم إضافة تدوينات جديدة وعرضها على الصفحة الرئيسية، أما في هذا الجزء فسنتعلم كيفية التعديل على التدوينات وإضافة الترقيم في نهاية الصفحة الرئيسية لعرض عدد محدد من التدوينات فيها والتنقل بين صفحات التدوينات الأخرى.
هذا المقال جزء من سلسلة عن بناء مدونة باستخدام إطار العمل Angular وقاعدة بيانات Firestore
- مقدمة في بناء تطبيقات الويب باستخدام إطار العمل Angular وقاعدة بيانات Firestore
- بناء مدونة باستخدام إطار العمل Angular وقاعدة بيانات Firestore - إضافة التدوينات وعرضها
- بناء مدونة باستخدام إطار العمل Angular وقاعدة بيانات Firestore - تعديل التدوينات
- بناء مدونة باستخدام إطار العمل Angular وقاعدة بيانات Firestore - إضافة الاستثيثاق
- نشر مدونة مبنية عبر Angular على Firebase
حذف تدوينة
سننشئ الآن خدمة Snackbar، وهي خدمة تُظهر شريطًا يعرض رسائل للمستخدم نعرض عبرها رسالة بعد حذف التدوينة، وننشئها بكتابة الأمر التالي:
ng g s services/Snackbar
افتح الملف snackbar.service.ts وضع الشيفرة التالية فيه:
import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; @Injectable({ providedIn: 'root' }) export class SnackbarService { constructor(private snackBar: MatSnackBar) { } showSnackBar(message: string) { this.snackBar.open(message, 'Close', { duration: 2000, panelClass: 'snackbar-ribon', verticalPosition: 'top', horizontalPosition: 'center' }); } }
لقد أنشأنا الآن التابع showSnackBar
الذي يقبل معامِلًا يكون هو الرسالة التي نريد عرضها، والحد الأقصى لعرض هذه الرسالة هو 2000 مللي ثانية، وقد عرَّفنا موضع الشريط لينتصف أعلى الصفحة.
أضف الآن التنسيق التالي لشريط Snackbar في الملف styles.scss:
.snackbar-ribon { color: #FFFFFF; background: #17a2b8; }
سنضيف الآن مزية حذف إحدى التدوينات الموجودة، وسنحذف التدوينة من تجميعة blogs
وفقًا لمعرِّف التدوينة postID
.
أضف الشيفرة التالية في الملف blog.service.ts:
deletePost(postId: string) { return this.db.doc('blogs/' + postId).delete(); }
افتح الملف blog-card.comonent.ts واستورد SnackbarService
كما يلي:
deletePost(postId: string) { return this.db.doc('blogs/' + postId).delete();}
ثم احقن خدمة SnackbarService
في المنشئ كما يلي:
constructor( // other service injection private snackBarService: SnackbarService ) { }
كما سنحدّث تابع الحذف في الملف blog-card.comonent.ts، بحيث يكون تعريفه كما يلي:
delete(postId: string) { if (confirm('Are you sure')) { this.blogService.deletePost(postId).then( () => { this.snackBarService.showSnackBar('Blog post deleted successfully'); } ); } }
سيقبل تابع الحذف postID
كمعامِل وسيعرض رسالة تحذير تطلب تأكيد الحذف، وعند ضغط المستخدم على OK، يُستدعَى تابع deletePost
الخاص بالخدمة BlogService
، ثم نعرض رسالة تظهر نجاح عملية الحذف باستخدام شريط snackbar.
افتح المتصفح واضغط على زر Delete في بطاقة التدوينة، سيظهر صندوق جافاسكربت يطلب تأكيد عملية حذف التدوينة:
إذا ضغط المستخدم على OK ستُحذف التدوينة ونحصل على رسالة توكيد في شريط Snackbar:
التعديل على تدوينة موجودة
سننفذ الآن خاصية التعديل على تدوينة قائمة وموجودة، من خلال إضافة التعريف التالي إلى الملف blog.service.ts:
updatePost(postId: string, post: Post) { const putData = JSON.parse(JSON.stringify(post)); return this.db.doc('blogs/' + postId).update(putData); }
سيقبل التابع updatePost
كلا من postID
وكائنًا من النوع Post كمعاملات، وسنحلل كائن Post إلى كائن JSON ثم نحدّث الكائن في تجميعة blogs
.
أضف توجيه وظيفة التعديل في الملف app.module.ts كما يلي:
RouterModule.forRoot([ ... { path: 'editpost/:id', component: BlogEditorComponent }, ... ])
ثم أضف تعريف الاستيراد التالي في الملف blog-editor.component.ts:
import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators';
صرِّح الآن عن متغير Subject
جديد:
private unsubscribe$ = new Subject<void>();
افتح الملف blog-editor.component.ts وأضف الشيفرة التالية في المنشئ:
if (this.route.snapshot.params['id']) { this.postId = this.route.snapshot.paramMap.get('id'); }
في الشيفرة أعلاه، نستخدم الصنف ActivatedRoute
لجلب معرِّف التدوينة من الرابط.
سنضيف الآن تابعًا لإعداد استمارة التعديل عند النقر على زر Edit في بطاقة التدوينة في الصفحة الرئيسية، ويكون تعريف التابع كما يلي:
setPostFormData(postFormData) { this.postData.title = postFormData.title; this.postData.content = postFormData.content; }
حدِّث الآن التابع ngOnInit
داخل الصنف BlogEditorComponent
كما يلي:
ngOnInit() { this.setEditorConfig(); if (this.postId) { this.formTitle = 'Edit'; this.blogService.getPostbyId(this.postId) .pipe(takeUntil(this.unsubscribe$)) .subscribe( result => { this.setPostFormData(result); } ); } }
إذا كان postID
معيَّنًا set فهذا يعني أن هذا طلب تحرير Edit، وسنجعل عنوان الاستمارة Edit، ونستدعي التابع getPostbyID
من BlogService
لجلب تفاصيل التدوينة المتوافقة مع postID
.
عند النقر على Save، نحتاج إلى معالجة كلا من حالة إنشاء تدوينة جديدة وتحرير تدوينة موجودة من قبل، وعليه سنحدِّث saveBlogPost
كما يلي:
saveBlogPost() { if (this.postId) { this.blogService.updatePost(this.postId, this.postData).then( () => { this.router.navigate(['/']); } ); } else { this.postData.createdDate = this.datePipe.transform(Date.now(), 'MMdd-yyyy HH:mm'); this.blogService.createPost(this.postData).then( () => { this.router.navigate(['/']); } ); } }
سننفذ الواجهة OnDestroy
على الصنف BlogEditorComponent
، وسنكمل اشتراك unsubscribe$
داخل التابع ngOnDestroy
، انظر الشيفرة التالية:
ngOnDestroy() { this.unsubscribe$.next(); this.unsubscribe$.complete(); }
افتح المتصفح وانقر على زر Edit في بطاقة التدوينة في الصفحة الرئيسية، ستنتقل الصفحة إلى صفحة Edit Post. تستطيع الآن أن ترى محرر التدوينة وفيه محتوى التدوينة جاهزًا بداخله، وإذا نظرت إلى الرابط ستجد أنه يحتوي على postID الخاص بهذه التدوينة. انظر الصورة التالية:
يمكن الآن تعديل محتوى التدوينة داخل المحرر، وبعد تمام التعديل ننقر على زر الحفظ لتحديث التدوينة بالمحتوى الجديد والعودة إلى الصفحة الرئيسية لنرى المحتوى الجديد في ملخص التدوينة:
إضافة شريط تنقل بين صفحات التدوينات
سنضيف خاصية الترقيم pagination في الصفحة الرئيسية وذلك للتنقل بين صفحات بطاقات التدوينات، مستخدمين ngx-pagination لهذا الغرض، وهو مكون مفتوح المصدر يوفر خاصية ترقيم بسيطة وسهلة الاستخدام لتطبيقات Angular.
نفِّذ الأمر التالي لتثبيت المكون ngx-pagination
:
npm i ngx-pagination --save
استورد NgxPaginationModule
في الملف app.module.ts كما يلي:
import { NgxPaginationModule } from 'ngx-pagination'; @NgModule( { imports: [ ... NgxPaginationModule, ], })
إنشاء مكون المرقم PaginatorComponent
نفِّذ الأمر التالي في الطرفية الأصلية لتوليد مكون المرقِّم PaginatorComponent:
ng g c components/paginator
افتح الملف paginator.component.ts وأضف تعريفات الاستيراد التالية:
import { Input } from '@angular/core'; import { Router } from '@angular/router';
سنضيف خاصيتي إدخال لهذا المكون، كما يلي:
@Input() pageSizeOptions: []; @Input() config: any;
احقن صنف الموجِّه router داخل المنشئ، كما يلي:
constructor(private router: Router) { }
سنضيف تابعًا يعالج حدث pageChange
للمرقِّم الخاص بنا، وسيكون تعريف ذلك التابع كما يلي:
pageChange(newPage: number) { this.router.navigate(['/page/', newPage]); }
كذلك، نضيف التابع changePageItemCount
في الصنف PaginatorComponent
، ويُستخدم هذا التابع لإعداد ترقيم ديناميكي للمرقِّم، حيث يعين عدد العناصر التي يظهرها على كل صفحة وفقًا لاختيار من قائمة منسدلة:
changePageItemCount(selectedItem) { localStorage.setItem('pageSize', selectedItem.value); this.config.itemsPerPage = selectedItem.value; }
سنخزِّن القيمة التي يختارها المستخدم في الذاكرة المحلية، وذلك لضمان أن القيمة لا تُفقد عند تحديث الصفحة، وكذلك لضمان جودة تجربة المستخدم.
افتح الملف paginator.component.html واستبدل الشيفرة التالية بالموجود فيه:
<div class="paginator-controls"> <div> <pagination-controls (pageChange)="pageChange($event)" class="mypagination"></pagination-controls> </div> <div> <mat-form-field> <mat-label>Items per page: </mat-label> <mat-select [(ngModel)]="config.itemsPerPage" (selectionChange)="changePageItemCount($event)"> <mat-option *ngFor="let page of pageSizeOptions" [value]="page"> {{ page }} </mat-option> </mat-select> </mat-form-field> </div> </div>
أخيرًا، سنضيف التنسيق الخاص بالمكون PaginatorComponent
، فافتح الملف paginator.component.scss واستبدل الشيفرة أدناه بالموجود فيه:
.my-pagination ::ng-deep .ngx-pagination { margin: 10px 0px 10px 0px; padding-inline-start: 0px; } .paginator-controls { display: flex; justify-content: space-between; padding-top: 10px; } @media screen and (min-width: 320px) and (max-width: 420px) { .paginator-controls { flex-direction: column-reverse; } }
أما الآن، فسنضيف رابط موجِّهٍ في الملف app.module.ts
لدعم الترقيم كما يلي:
{ path: 'page/:pagenum', component: HomeComponent },
إضافة المكون PaginatorComponent إلى قائمة التدوينات
يجب أن نضيف المكون PaginatorComponent
إلى مكون قائمة التدويناتBlogCardComponent
من أجل تفعيل الترقيم فيها، لنصرح عن خاصيتين، كما يلي:
config: any; pageSizeOptions = [];
أضف الاستيراد للصنف ActivatedRoute
في المكون، كما يلي:
import { ActivatedRoute } from '@angular/router';
احقن الصنف ActivatedRoute
في المنشئ، كما يلي:
constructor( // other services private route: ActivatedRoute) { }
أضف الشيفرة التالية داخل منشئ الصنف BlogCardComponent
لبدء الخصائص المصرَّح عنها للتو:
this.pageSizeOptions = [2, 4, 6]; const pageSize = localStorage.getItem('pageSize'); this.config = { currentPage: 1, itemsPerPage: pageSize ? +pageSize : this.pageSizeOptions[0] };
والآن، حدِّث التابع ngOnInit
كما يلي:
ngOnInit() { this.route.params.subscribe( params => { this.config.currentPage = +params['pagenum']; this.getBlogPosts(); } ); }
تحديث قالب BlogCardComponent
افتح الملف blog-card.component.html وأضف مكون المرقِّم كما يلي:
<app-paginator [pageSizeOptions]="pageSizeOptions" [config]="config"></app-paginator>
سنضيف أنبوب ترقيم أيضًا في التوجيه ngFor
أثناء التكرار على قائمة التدوينات، كما يلي:
<div *ngFor="let post of blogPost | paginate: config">
افتح المتصفح الآن، يجب أن ترى المرقِّم في الصفحة الرئيسية، كما تستطيع التنقل بين الصفحات كما تشاء، ويتغير الرابط برقم الصفحة التي انتقلت إليها، كما يمكن رؤية القائمة المنسدلة بجانب المرقِّم، والتي تسمح باختيار عدد العناصر التي ينبغي إظهارها في كل صفحة، انظر الصورة التالية:
خاتمة
تعلمنا في هذا الجزء من سلسلة بناء مدونة باستخدام إطار العمل Angular وقاعدة بيانات Firestore كيفية حذف التدوينات والتعديل عليها وإضافة الترقيم للصفحات، وسنتعلم في المقال التالي كيفية إضافة استيثاق جوجل للوصول إلى التطبيق وإضافة التدوينات والتعديل عليها.
ترجمة -وبتصرف- لفصول من كتاب Build a full stack web application using angular and firebase لصاحبه Ankit Sharma.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.