استخدام Angular Router للتنقل بين الصفحات
في عالم تطوير الواجهات الحديثة، لم يعد التنقل بين الصفحات مجرد انتقال من شاشة إلى أخرى، بل أصبح جزءًا أساسيًا من تجربة المستخدم نفسها. عندما يفتح المستخدم تطبيقًا مبنيًا بـ Angular، فهو لا يريد فقط أن يرى المحتوى، بل يريد أن ينتقل بين الأقسام بسلاسة، وأن يشعر أن التطبيق سريع ومنظم، وأن كل شيء يصل إلى مكانه الصحيح دون ارتباك أو إعادة تحميل مزعجة. هنا يظهر دور Angular Router باعتباره القلب النابض لعملية التنقل داخل تطبيقات Angular، فهو المسؤول عن ربط المسارات بالصفحات والمكوّنات، وتنظيم حركة المستخدم، وإدارة المعاملات في الرابط، والتحكم في الحماية والوصول، وحتى تحسين تجربة التصفح داخل تطبيق غني بالتفاعلات.
ما يميّز Angular Router أنه لا يكتفي بتغيير العرض فقط، بل يمنحك نظامًا كاملًا لإدارة المسارات داخل التطبيق. تستطيع من خلاله تعريف صفحة رئيسية، وصفحات فرعية، وصفحات ديناميكية تعتمد على معرّفات في الرابط، ومسارات محمية لا يراها إلا المستخدم المسجل دخوله، ومسارات إعادة توجيه، ومسارات احتياطية لصفحة الخطأ، وغير ذلك كثير. وهذا التنظيم مهم جدًا، لأن التطبيقات الحديثة لم تعد مجرد صفحات ثابتة، بل أصبحت أنظمة متكاملة تحتوي على لوحات تحكم، نماذج، ملفات شخصية، إعدادات، تقارير، وسلاسل عمل مترابطة، وكل ذلك يحتاج إلى Router قوي وواضح وسهل الصيانة.
في هذا المقال سنغوص بعمق في Angular Router، لكن بأسلوب عملي وبسيط في الوقت نفسه. سنفهم الفكرة أولًا، ثم ننتقل إلى إعداد المسارات، واستخدام الروابط داخل القوالب، والانتقال برمجيًا من TypeScript، واستخراج المعاملات من الرابط، وبناء مسارات متداخلة، وحماية الصفحات، وتحسين تجربة المستخدم، والتعامل مع الأخطاء الشائعة. وسنضع أمثلة حقيقية بكود Angular حتى تكون الصورة مكتملة، لأن الفهم الحقيقي يأتي من التطبيق لا من النظرية وحدها.
ما هو Angular Router ولماذا هو مهم؟
Angular Router هو النظام الذي يسمح لتطبيق Angular بالتنقل بين الصفحات أو العرضات المختلفة دون الحاجة إلى إعادة تحميل الصفحة بالكامل. هذا النوع من التنقل يسمى غالبًا Single Page Application Navigation، أي أن التطبيق يبقى في صفحة واحدة من الناحية التقنية، لكن المحتوى يتغير بحسب المسار الحالي في الرابط. هذا يعطي إحساسًا بالسرعة والانسيابية، ويجعل تجربة المستخدم أقرب إلى تطبيقات سطح المكتب من حيث السلاسة.
تخيل مثلًا متجرًا إلكترونيًا يحتوي على الصفحة الرئيسية، صفحة المنتجات، صفحة تفاصيل المنتج، صفحة سلة المشتريات، وصفحة الحساب الشخصي. بدون Router، سيكون من الصعب إدارة هذا التنقل بشكل أنيق. أما مع Angular Router، فيمكنك تعريف كل مسار وربطه بمكوّن مناسب، ثم بناء روابط داخلية واضحة، مع القدرة على تمرير البيانات في الرابط أو عبر query parameters أو حتى عبر state أثناء التنقل.
الأمر لا يتوقف هنا. Angular Router يساعدك أيضًا في تنظيم بنية المشروع، لأن كل مسار يمكن أن يشير إلى مكوّن محدد أو مكوّن فرعي ضمن Layout معين. وهذا مهم في المشاريع الكبيرة؛ فبدل أن يصبح كل شيء متداخلًا ومبعثرًا، تصبح صفحاتك مرتبة مثل خريطة واضحة: هذا المسار للوحة التحكم، هذا المسار للإعدادات، هذا المسار للتقارير، وهذا مسار لتفاصيل عنصر معين.
بداية سريعة: إنشاء مشروع Angular وتجهيز Router
عند إنشاء مشروع Angular جديد، غالبًا يمكنك تفعيل Router من البداية، أو إضافته لاحقًا. إذا كنت تبدأ مشروعًا جديدًا، فاختيار router أثناء الإنشاء يوفر عليك بعض الخطوات. وبعدها ستلاحظ أن Angular ينشئ لك ملفًا خاصًا بالمسارات مثل app-routing.module.ts في الإصدارات التقليدية، أو يعتمد على provideRouter في البنية الحديثة القائمة على الـ standalone components.
في البنية التقليدية، يكون الملف الأساسي للمسارات قريبًا من الشكل التالي:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { NotFoundComponent } from './not-found/not-found.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '**', component: NotFoundComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}
هذا المثال يوضح الفكرة الأساسية ببساطة شديدة. المسار الفارغ '' يعرض الصفحة الرئيسية، والمسار about يعرض صفحة من نحن، والمسار ** يعمل كمسار عام افتراضي إذا لم يجد Angular أي تطابق، وغالبًا يُستخدم لصفحة 404. ما يبدو بسيطًا هنا هو في الحقيقة الأساس الذي سيبنى عليه كل شيء لاحقًا.
فهم الطريق بين المسار والمكوّن
لكي تفهم Angular Router بشكل صحيح، يجب أن تتخيل أن الرابط في المتصفح ليس مجرد نص يظهر في الأعلى، بل هو المفتاح الذي يخبر التطبيق: ما الذي يجب أن أُظهره الآن؟ عندما يكتب المستخدم /about، يقرأ Router هذا المسار، يطابقه مع قائمة المسارات التي عرّفتها، ثم يحمّل المكوّن المناسب داخل <router-outlet>.
وهنا يأتي عنصر مهم جدًا وهو router-outlet. هذا العنصر هو المكان الذي تُعرض فيه الصفحة أو المكوّن المرتبط بالمسار الحالي. بدونه، يعرف Router المسار، لكن لن يجد مكانًا ليعرض فيه المحتوى.
في ملف القالب الرئيسي غالبًا ستجد:
<nav>
<a routerLink="/">الرئيسية</a>
<a routerLink="/about">من نحن</a>
<a routerLink="/products">المنتجات</a>
</nav>
<router-outlet></router-outlet>
هذه السطور الثلاثة تلخص فكرة التنقل كله تقريبًا. الروابط تغيّر المسار، وrouter-outlet يعرض النتيجة. لكن خلف هذا الشكل البسيط توجد قدرات كثيرة جدًا يمكن أن تستخدمها لبناء تجربة احترافية.
استخدام routerLink داخل القوالب
أكثر طريقة شائعة للتنقل داخل Angular هي routerLink. وهي أفضل من استخدام href التقليدي داخل تطبيق SPA، لأنها تمنع إعادة تحميل الصفحة كاملة وتُبقي التنقل داخل إطار Angular نفسه.
مثلًا:
<a routerLink="/dashboard">لوحة التحكم</a>
<a routerLink="/users">المستخدمون</a>
<a routerLink="/settings">الإعدادات</a>
يمكن أيضًا استخدامه مع مصفوفة مسارات، وهذا يكون مفيدًا جدًا عندما تريد بناء المسار بطريقة ديناميكية:
<a [routerLink]="['/products', product.id]">عرض المنتج</a>
في هذا المثال، إذا كان product.id = 25 فإن الرابط يصبح /products/25. هذا النوع من التنقل شائع جدًا في صفحات التفاصيل، حيث يفتح المستخدم صفحة منتج معين، أو ملف مستخدم محدد، أو تقريرًا مرقمًا.
كما يمكنك التعامل مع query parameters بسهولة:
<a [routerLink]="['/search']" [queryParams]="{ q: 'angular router' }">بحث</a>
سيصبح الرابط مثل:
/search?q=angular%20router
وهذا مفيد عندما تريد تمرير معلومات إضافية دون إدخالها ضمن بنية المسار نفسها.
التنقل برمجيًا باستخدام Router
أحيانًا لا يكفيك التنقل من داخل HTML. ربما تريد التوجيه بعد حفظ نموذج، أو بعد تسجيل الدخول، أو بعد تنفيذ عملية معينة. هنا نستخدم Router من داخل TypeScript.
مثال:
import { Component } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-login',
template: `
<button (click)="login()">تسجيل الدخول</button>
`
})
export class LoginComponent {
constructor(private router: Router) {}
login() {
// عملية تسجيل الدخول الناجحة
this.router.navigate(['/dashboard']);
}
}
في هذا المثال، بعد نجاح العملية، يتم توجيه المستخدم إلى لوحة التحكم. هذه الطريقة عملية جدًا عندما تكون لديك شروط قبل التنقل، مثل التحقق من البيانات أو انتظار استجابة API.
وإذا كنت تريد الانتقال مع query parameters:
this.router.navigate(['/products'], {
queryParams: { category: 'laptops', page: 2 }
});
أما إذا كنت تريد استخدام رابط نصي كامل، فيمكنك:
this.router.navigateByUrl('/dashboard');
غالبًا navigate تمنحك مرونة أكبر في بناء المسارات بشكل منظّم، بينما navigateByUrl تكون مناسبة عندما لديك رابط جاهز بالكامل.
قراءة المعاملات من الرابط
واحدة من أكثر ميزات Angular Router فائدة هي قدرته على استخراج المعلومات من الرابط نفسه. وهذا يسمح لك ببناء صفحات ديناميكية تعتمد على البيانات الموجودة في المسار أو في query string.
قراءة Params من المسار
إذا كان لديك مسار مثل:
/products/42
فأنت غالبًا تريد معرفة أن 42 هو معرف المنتج. في هذه الحالة، نعرّف المسار هكذا:
const routes: Routes = [
{ path: 'products/:id', component: ProductDetailsComponent }
];
ثم نقرأ id داخل المكوّن:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-details',
template: `
<h2>تفاصيل المنتج</h2>
<p>معرف المنتج: {{ productId }}</p>
`
})
export class ProductDetailsComponent implements OnInit {
productId: string | null = null;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.productId = this.route.snapshot.paramMap.get('id');
}
}
هذا المثال بسيط، لكنه أساسي للغاية. وفي التطبيقات الواقعية قد تستخدم هذا المعرف لاستدعاء API وجلب بيانات المنتج الفعلية.
الاستماع إلى تغيّر params
إذا كنت داخل الصفحة نفسها والتنقل يحدث إلى نفس المكوّن لكن بمعرف مختلف، من الأفضل الاشتراك في التغيّر بدل الاعتماد على snapshot فقط:
ngOnInit() {
this.route.paramMap.subscribe(params => {
this.productId = params.get('id');
});
}
هذه الطريقة مهمة لأن Angular قد لا يعيد إنشاء المكوّن نفسه إذا تغيّر فقط جزء من المسار، وإنما يعيد استخدامه، لذا الاشتراك يضمن أن تتعامل مع التغييرات الجديدة.
قراءة Query Parameters
الـ query parameters مفيدة عندما تريد تمرير معلومات إضافية مثل ترتيب النتائج أو صفحة البحث أو الفلتر المستخدم.
ngOnInit() {
this.route.queryParamMap.subscribe(params => {
const category = params.get('category');
const page = params.get('page');
console.log(category, page);
});
}
وهكذا، يمكنك بناء صفحة بحث مثل:
/search?q=angular&sort=latest&page=3
هذا النوع من الروابط واضح، قابل للمشاركة، وسهل الفهم.
إنشاء مسارات متداخلة
في المشاريع الحقيقية، من النادر أن تكون الصفحات كلها منفصلة تمامًا. غالبًا لديك Layout رئيسي يحتوي على شريط جانبي أو رأس ثابت، ثم بداخله صفحات فرعية. هنا تأتي المسارات المتداخلة أو Child Routes.
مثلًا، يمكن أن يكون لديك قسم الإدارة:
/admin/admin/users/admin/products/admin/orders
بدل تكرار layout في كل صفحة، يمكنك تعريف مسار أب يحتوي على children.
const routes: Routes = [
{
path: 'admin',
component: AdminLayoutComponent,
children: [
{ path: '', component: AdminHomeComponent },
{ path: 'users', component: AdminUsersComponent },
{ path: 'products', component: AdminProductsComponent },
{ path: 'orders', component: AdminOrdersComponent }
]
}
];
وفي AdminLayoutComponent تضع router-outlet آخر لعرض الصفحات الفرعية:
<div class="admin-layout">
<aside>القائمة الجانبية</aside>
<main>
<router-outlet></router-outlet>
</main>
</div>
هذه البنية تجعل التطبيق أنظف بكثير، وتقلل التكرار، وتسمح بإدارة أفضل للواجهات المشتركة.
إعادة التوجيه Redirects
في بعض الأحيان لا تريد أن يزور المستخدم مسارًا معينًا بشكل مباشر، أو تريد أن تجعل المسار الافتراضي يذهب إلى صفحة محددة. هنا نستخدم redirectTo.
const routes: Routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent }
];
وجود pathMatch: 'full' مهم هنا، لأنك تريد أن يكون التطابق كاملًا مع المسار الفارغ فقط. لو لم تحدد ذلك قد تحصل على سلوك غير متوقع.
كما يمكنك إعادة توجيه مسار قديم إلى مسار جديد:
{ path: 'old-dashboard', redirectTo: 'dashboard', pathMatch: 'full' }
هذه الخاصية مفيدة جدًا عند إعادة هيكلة التطبيق أو تغيير أسماء الصفحات دون كسر الروابط القديمة.
صفحة 404 والمسارات الاحتياطية
لا يوجد تطبيق كامل بدون معالجة حالة المسارات المجهولة. قد يكتب المستخدم رابطًا خاطئًا، أو يزور صفحة حذفت لاحقًا، أو يفتح رابطًا قديمًا. لهذا السبب نخصص مسارًا احتياطيًا.
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'about', component: AboutComponent },
{ path: '**', component: NotFoundComponent }
];
ومكوّن NotFoundComponent يمكن أن يكون بسيطًا وواضحًا:
<h2>الصفحة غير موجودة</h2>
<p>عذرًا، الرابط الذي تحاول الوصول إليه غير متاح.</p>
<a routerLink="/">العودة إلى الصفحة الرئيسية</a>
هذه اللمسة الصغيرة تحسن تجربة المستخدم بشكل ملحوظ، وتمنع الضياع داخل التطبيق.
حماية المسارات باستخدام Guards
ليس كل ما في التطبيق يجب أن يكون مفتوحًا للجميع. بعض الصفحات تحتاج إلى تسجيل دخول، وبعضها يحتاج إلى صلاحيات محددة. Angular Router يوفّر فكرة الـ Guards لتحقيق ذلك. الأكثر شيوعًا هو CanActivate.
مثال بسيط:
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const isLoggedIn = !!localStorage.getItem('token');
if (!isLoggedIn) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
ثم تستخدمه في المسارات:
const routes: Routes = [
{ path: 'login', component: LoginComponent },
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
}
];
الفكرة هنا أن المسار نفسه لا يُفتح إلا إذا سمح الحارس بذلك. هذا النمط يضيف طبقة مهمة من التحكم، خصوصًا في لوحات الإدارة والتطبيقات الحساسة.
كما توجد أنواع أخرى من الحراس مثل CanDeactivate لمنع خروج المستخدم من صفحة فيها تغييرات غير محفوظة، وResolve لجلب البيانات قبل فتح الصفحة، وCanLoad أو CanMatch للتحكم في تحميل الموديولات أو مطابقة المسارات. ومع ذلك، يبقى CanActivate هو المدخل الأكثر شهرة لفهم فكرة الحماية.
تحميل البيانات قبل فتح الصفحة باستخدام Resolve
أحيانًا تكون لديك صفحة لا معنى لفتحها قبل أن تصل البيانات الأساسية. مثل صفحة تفاصيل مستخدم، أو صفحة تقرير، أو صفحة حساب. في هذه الحالة يمكنك استخدام Resolve لجلب البيانات قبل الانتقال.
@Injectable({
providedIn: 'root'
})
export class ProductResolver implements Resolve<any> {
constructor(private http: HttpClient) {}
resolve(route: ActivatedRouteSnapshot): Observable<any> {
const id = route.paramMap.get('id');
return this.http.get(`https://api.example.com/products/${id}`);
}
}
ثم في المسار:
{
path: 'products/:id',
component: ProductDetailsComponent,
resolve: {
product: ProductResolver
}
}
وفي المكوّن:
ngOnInit() {
this.route.data.subscribe(data => {
this.product = data['product'];
});
}
هذا الأسلوب رائع عندما تريد أن تظهر الصفحة فقط بعد توفر بياناتها الأساسية، بدل أن تعرض واجهة فارغة ثم تملأها لاحقًا.
إضافة ألوان ومؤثرات للمسار الحالي
Angular Router لا يساعدك فقط في التنقل، بل يمكنك أيضًا استخدامه لإظهار المسار النشط في الواجهة، مثل تمييز الرابط الحالي في القائمة.
<a routerLink="/home" routerLinkActive="active">الرئيسية</a>
<a routerLink="/about" routerLinkActive="active">من نحن</a>
<a routerLink="/contact" routerLinkActive="active">اتصل بنا</a>
وفي CSS:
.active {
font-weight: bold;
color: #1976d2;
}
هذا التفصيل البسيط يبدو صغيرًا، لكنه يضيف كثيرًا إلى وضوح الواجهة. المستخدم يحب أن يعرف أين هو الآن، وما الصفحة التي يزورها حاليًا.
التعامل مع التنقل النسبي Relative Navigation
في التطبيقات الكبيرة، ليس من العملي دائمًا كتابة المسارات بشكل مطلق. أحيانًا تحتاج إلى التنقل النسبي من داخل مكوّن فرعي.
constructor(private router: Router, private route: ActivatedRoute) {}
goToDetails() {
this.router.navigate(['details'], { relativeTo: this.route });
}
هنا إذا كنت داخل /admin/users، فإن الانتقال إلى details قد يصبح /admin/users/details بحسب مكان المكوّن الحالي. هذا مهم جدًا عندما تبني واجهات متداخلة أو صفحات فرعية كثيرة.
استخدام fragment والانتقال إلى جزء معين من الصفحة
في بعض الأحيان لا تريد تغيير الصفحة كاملة، بل تريد فقط الانتقال إلى جزء داخلها، مثل قسم "التعليقات" أو "الأسئلة الشائعة". عندها يمكنك استخدام fragment.
<a [routerLink]="['/help']" fragment="faq">الأسئلة الشائعة</a>
ثم في الصفحة الهدف قد يكون الرابط مثل:
/help#faq
وهذا مفيد عندما تجمع بين التنقل داخل التطبيق والتنقل داخل الصفحة نفسها.
تمثيل أفضل للمسارات في المشاريع الحقيقية
عندما يكبر المشروع، لا يجب أن تبقى كل المسارات في ملف واحد بشكل عشوائي. من الأفضل تقسيمها بذكاء بحسب المجال الوظيفي. فمثلًا يمكنك جعل هناك مسارات للمصادقة، ومسارات للمستخدم، ومسارات الإدارة، ومسارات المتجر، وكل مجموعة في ملف مستقل إذا كانت البنية تسمح بذلك. هذا الأسلوب يحافظ على نظافة الكود، ويسهّل الصيانة، ويجعل الإضافة والتعديل أكثر أمانًا.
مثال على بنية معقولة:
const routes: Routes = [
{
path: '',
loadChildren: () => import('./features/home/home.routes').then(m => m.HOME_ROUTES)
},
{
path: 'auth',
loadChildren: () => import('./features/auth/auth.routes').then(m => m.AUTH_ROUTES)
},
{
path: 'dashboard',
loadChildren: () => import('./features/dashboard/dashboard.routes').then(m => m.DASHBOARD_ROUTES)
}
];
هذه الفكرة تتماشى مع lazy loading أو التحميل الكسول، حيث لا يتم تحميل كل شيء دفعة واحدة، بل يتم تحميل الجزء المطلوب فقط عند الحاجة. وهذا يحسن الأداء خاصة في التطبيقات الكبيرة.
التحميل الكسول Lazy Loading
التحميل الكسول من أهم الميزات التي ترفع أداء التطبيقات. بدل أن يحمل Angular كل الموديولات والصفحات منذ البداية، يمكنه تحميل بعضها عند الانتقال إليها فقط. هذا يقلل زمن التحميل الأولي، ويجعل التطبيق أخف وأسرع في البداية.
في البنية الحديثة يمكن استخدام loadChildren مع ملفات routes مباشرة:
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes)
}
];
أما إذا كنت تستخدم موديولات تقليدية:
const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule)
}
];
هذا يجعل التطبيق يحمّل قسم الإدارة فقط عندما يزور المستخدم /admin. في التطبيقات الكبيرة مثل الأنظمة التجارية أو لوحات التحكم الإدارية، هذا الفارق مهم جدًا.
أخطاء شائعة عند استخدام Angular Router
هناك بعض الأخطاء المتكررة التي يقع فيها المطورون في البداية، ومعرفتها مبكرًا يوفر وقتًا وجهدًا كبيرين. من أكثر هذه الأخطاء استخدام href بدل routerLink داخل التطبيق، فينتج عن ذلك إعادة تحميل غير ضرورية. كذلك من الأخطاء نسيان إضافة router-outlet في القالب الرئيسي أو في القوالب الفرعية عند الحاجة. وأحيانًا ينسى المطور تحديد pathMatch: 'full' مع إعادة التوجيه، مما يؤدي إلى سلوك غير صحيح. ومن الأخطاء أيضًا الاعتماد على snapshot في حالات تحتاج إلى التفاعل مع تغيّر البيانات أثناء البقاء داخل نفس المكوّن.
خطأ آخر مهم هو عدم تنظيم المسارات مع كبر المشروع. عندما تتراكم المسارات في ملف واحد بلا تقسيم، يصبح التعديل صعبًا ويزيد احتمال التعارض. لذلك من الأفضل أن تتعامل مع Router كجزء من بنية المشروع وليس كإضافة جانبية فقط.
مثال عملي متكامل
لنجمع الآن كل شيء في مثال عملي بسيط، لكنه قريب من ما يحدث في المشاريع الواقعية. لنفترض أن لدينا تطبيقًا فيه صفحة رئيسية، صفحة منتجات، صفحة تفاصيل المنتج، صفحة تسجيل الدخول، ولوحة تحكم محمية.
تعريف المسارات
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { ProductsComponent } from './products/products.component';
import { ProductDetailsComponent } from './product-details/product-details.component';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { AuthGuard } from './auth.guard';
export const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'products', component: ProductsComponent },
{ path: 'products/:id', component: ProductDetailsComponent },
{ path: 'login', component: LoginComponent },
{
path: 'dashboard',
component: DashboardComponent,
canActivate: [AuthGuard]
},
{ path: '**', component: NotFoundComponent }
];
الروابط داخل الصفحة الرئيسية
<nav>
<a routerLink="/">الرئيسية</a>
<a routerLink="/products">المنتجات</a>
<a routerLink="/dashboard">لوحة التحكم</a>
<a routerLink="/login">تسجيل الدخول</a>
</nav>
<router-outlet></router-outlet>
عرض قائمة منتجات
import { Component } from '@angular/core';
@Component({
selector: 'app-products',
template: `
<h2>المنتجات</h2>
<ul>
<li *ngFor="let product of products">
<a [routerLink]="['/products', product.id]">
{{ product.name }}
</a>
</li>
</ul>
`
})
export class ProductsComponent {
products = [
{ id: 1, name: 'لابتوب' },
{ id: 2, name: 'هاتف ذكي' },
{ id: 3, name: 'سماعات' }
];
}
تفاصيل المنتج
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product-details',
template: `
<h2>تفاصيل المنتج</h2>
<p>رقم المنتج: {{ id }}</p>
`
})
export class ProductDetailsComponent implements OnInit {
id: string | null = null;
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.paramMap.subscribe(params => {
this.id = params.get('id');
});
}
}
حماية لوحة التحكم
import { Injectable } from '@angular/core';
import { CanActivate, Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router) {}
canActivate(): boolean {
const loggedIn = !!localStorage.getItem('token');
if (!loggedIn) {
this.router.navigate(['/login']);
return false;
}
return true;
}
}
هذا المثال المتكامل يبيّن كيف تتعاون أجزاء Router معًا لبناء تجربة تنقل كاملة: روابط داخلية، عرض صفحات، صفحات ديناميكية، وحماية مسارات.
Angular Router وتجربة المستخدم
قد يبدو Router في البداية كأنه أداة تقنية بحتة، لكنه في الحقيقة جزء من تجربة المستخدم بشكل مباشر جدًا. المستخدم لا يهتم بالتفاصيل الداخلية بقدر ما يهتم بأن يكون التنقل سريعًا وواضحًا ومريحًا. عندما تعمل الروابط بسلاسة، وتبقى العناوين واضحة، وتظهر الصفحة المناسبة في الوقت المناسب، فإن ذلك يعطي انطباعًا احترافيًا عن التطبيق كله.
بل إن الترتيب الجيد للمسارات يؤثر حتى على فريق التطوير نفسه. فكلما كانت بنية Router واضحة، كان من الأسهل على المطورين إضافة ميزات جديدة أو إصلاح الأخطاء أو فهم أين توجد الصفحة المطلوبة. ولهذا السبب، فإن الاهتمام بـ Router ليس مجرد خطوة تقنية، بل هو استثمار في جودة المشروع على المدى الطويل.
نصائح عملية لاستخدام Router بشكل احترافي
عند العمل على مشروع حقيقي، حاول أن تجعل كل مسار يخدم هدفًا واضحًا. لا تضع مسارات كثيرة بلا حاجة، ولا تكرر نفس المنطق في أكثر من مكان. استخدم child routes عندما يكون لديك layout مشترك. استخدم lazy loading عندما يبدأ التطبيق في التضخم. استخدم guards في الصفحات الحساسة. واجعل الروابط في الواجهة واضحة وسهلة القراءة. وإذا كانت هناك صفحات كثيرة مرتبطة ببيانات ديناميكية، ففكر مبكرًا في طريقة منظمة لقراءة params وquery params حتى لا يتكرر الكود بلا داعٍ.
ومن المهم أيضًا أن تختبر التنقل في أوضاع مختلفة، مثل الضغط المباشر على الرابط، أو تحديث الصفحة أثناء وجودك في مسار فرعي، أو العودة للخلف، أو فتح الرابط في تبويب جديد. هذه التفاصيل الصغيرة تكشف لك إن كانت بنية Router عندك مستقرة أم لا.
خاتمة
Angular Router ليس مجرد وسيلة للانتقال بين الصفحات، بل هو البنية التي تمنح تطبيق Angular شكله المنظم وروحه العملية. من خلاله تستطيع أن تبني تجربة تنقل سلسة، وأن تربط المسارات بالمكوّنات، وأن تدير المعاملات، وتحمي الصفحات، وتتحكم في إعادة التوجيه، وتعرض صفحات الخطأ، وتحمّل أجزاء التطبيق عند الحاجة فقط. ومع مرور الوقت ستلاحظ أن فهمك الجيد لـ Router ينعكس مباشرة على جودة مشاريعك، لأنك ستفكر في التطبيق كمنظومة متكاملة لا كصفحات متفرقة.
الجميل في Angular Router أنه يجمع بين البساطة في الاستخدام والعمق في الإمكانات. يمكنك أن تبدأ بروابط بسيطة وصفحات قليلة، ثم تتطور تدريجيًا إلى تطبيق كبير يحتوي على موديولات متداخلة، صفحات محمية، مسارات ديناميكية، وتحميل كسول، وكل ذلك بنفس الأدوات الأساسية التي تعلمتها من البداية. وهذه هي قوة Angular فعلًا: أن تمنحك قاعدة واضحة، ثم تفتح أمامك طريقًا واسعًا للبناء بطريقة احترافية ومنظمة.