همانطور که در فصل گذشته مشاهده کردید مطالب مربوط به Routing یا مسیردهی را در انگولار آغاز کردیم و شما را با انواع روشهای مسیردهی به لینکها و دکمهها آشنا کردیم. در این بخش قصد داریم در ادامه به توضیح مسیردهی بپردازیم. با ما همراه باشید.
مثال فصل گذشته را در نظر بگیرید که شامل یک مسیر برای home، مسیری برای users و در نهایت آدرسی برای servers بود. حال فرض کنید یک کاربر با id=1 داریم و میخواهیم صفحهی مربوط به آن را نمایش دهیم در این صورت باید چه کاری انجام دهیم؟ به عبارت دیگر میخواهیم مسیری مشابه زیر ایجاد کرده و اجازه بدهیم جزئیات هر حساب کاربری مشخص شود:
http://localhost:4200/users/1
برای دستیابی به مسیری مشابه فوق باید ابتدا فایل app.module.ts را باز کرده و سپس به ویژگی appRoutes یک مسیر جدید مشابه زیر اضافه کنیم:
const appRoutes: Routes = [ {path: '', component: HomeComponent}, {path: 'users', component: UsersComponent}, {path: 'users/:id/:name', component: UserComponent}, {path: 'servers', component: ServersComponent} ];
همانطور که ملاحظه کردید برای کامپوننت user که مربوط به یک کاربر با شناسه (id) مشخص است، یک مسیر تعریف کردیم توجه داشته باشید که روبهروی مسیر users یک / قرار داده و سپس از علامت دو نقطه (:) استفاده کردهایم. این علامت به انگولار میگوید که این مسیر یک مسیر داینامیک و پویا میباشد.
در نهایت نام یک متغییر به نام id را روبهروی علامت : قرار دادهایم که بیانگر شناسه یا id کاربر است. این متغییر میتواند name یا هر چیز دیگری باشد. تنها موضوعی که مهم است علامت : میباشد.
سپس در ادامه مجددا نام کاربر را میخواهیم در آدرس در اختیار داشته باشیم بنابراین یک پارامتر دیگر به نام name به مسیر موردنظر اضافه میکنیم.
پس از اضافه کردن یک مسیر پویا به appRoutes باید بتوانیم به صورت خودکار اطلاعات را از آدرس موردنظر بازیابی کرده و مورد استفاده قرار دهیم. به فرض مثال میخواهیم وقتی آدرس http://localhost:4200/users/1 وارد شد، در صفحه کاربران نام کاربر به همراه شناسه id آن نمایش داده شود.
بنابراین درون فایل user.component.ts در ابتدا باید یک ویژگی از نوع ActivatedRoute تعریف کنیم که بیانگر مسیریست که فعال است.
به عبارت دیگر این کلاس برای دستیابی به مسیر جاری (Current Route) میباشد. بنابراین داریم:
constructor(private route: ActivatedRoute) { }
همانطور که ملاحظه کردید داخل سازنده از کلاس ActivatedRoute برای دستیابی به id موجود در URL استفاده کردیم.
حال در هوک ngOnInit به عنوان پیشفرض و در ابتدای استفاده از کامپوننت مقادیر id و name را از url با استفاده از متد snapshot و پارامتر params دریافت میکنیم:
import {Component, OnInit} from '@angular/core'; import {ActivatedRoute} from "@angular/router"; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { user: { id: number, name: string }; constructor(private route: ActivatedRoute) { } ngOnInit() { this.user = { id: this.route.snapshot.params['id'], name: this.route.snapshot.params['name'] } } }
همانطور که ملاحظه کردید در هوک ngOnInit ویژگی user را که یک شیء جاوا اسکریپت است مقداردهی کردیم.
در صورتیکه با مفاهیم مربوط به هوک ها در انگولار آشنایی ندارید لطفا زیر را مطالعه بفرمایید:
اما نکتهی حائز اهمیت این بخش در این است که همواره باید مقدار داخل براکت params با مقداری که در appRoutes تعریف کردهاید برابر باشد. حال به فایل user.component.html مراجعه کرده و سپس تغییرات زیر را جهت نمایش نام و شناسه کاربری اعمال میکنیم:
<p>شناسه کاربر: {{ user.id }}</p> <p>نام کاربری: {{ user.name }}</p>
در این حالت با وارد کردن آدرس زیر
http://localhost:4200/users/1/roxo
متنی که در صفحه مشاهده خواهید کرد به صورت : شناسه کاربری: ۱، نام کاربری: روکسو است.
حال فرض کنید میخواهیم یک لینک درون صفحه users قرار دهیم که با کلیک روی آن پروفایل یا آدرس کاربر با شناسه ۴ و نام «انگولار» نمایش داده شود. خب در ابتدا فایل user.component.html را به صورت زیر ویرایش میکنیم:
<p>شناسه کاربر: {{ user.id }}</p> <p>نام کاربری: {{ user.name }}</p> <a [routerLink]="['/users', 4, 'angular']">بارگذاری اطلاعات کاربر angular</a>
همانطور که ملاحظه میکنید یک تگ <a> درست کرده و سپس درون آن یک ویژگی به نام routerLink قرار دادهایم. این ویژگی سه پارامتر به نامهای: مسیر، شناسه id و نام کاربری را دریافت میکند و با کلیک روی آن به مسیری مشابه زیر وارد میشود:
http://localhost:4200/users/4/angular
شاید برای شما یک چیز غیر طبیعی باشد. دقیقا درست حدس زدید، اگرچه صفحه به آدرس فوق انتقال پیدا کرده است ولی عبارت: شناسه کاربری: ۱ و نام کاربری برابر «روکسو» هنوز در صفحه باقی مانده و تغییری نکرده است! بنابراین نسبت به یک مسیری که از طرف کاربر ارسال میشود پاسخی دریافت نمیکنیم.
برای حل این مشکل فایل user.component.ts را باز کرده و سپس درون هوک ngOnInit به جای استفاده از متد snapshot از ویژگیای به نام params استفاده میکنیم. این ویژگی به عنوان یک مشاهدهگر (Observable) است.
مشاهدهگرها یا Observable مانند دریچهها و کانالهایی هستند که همواره باز بوده و برای هر لحظه که یک درخواست ارسال میشود پاسخی ارسال میکنند تا بروزرسانی دادهها انجام شود. بنابراین درون این فایل خواهیم داشت:
import {Component, OnInit} from '@angular/core'; import {ActivatedRoute, Params} from "@angular/router"; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit { user: { id: number, name: string }; constructor(private route: ActivatedRoute) { } ngOnInit() { this.user = { id: this.route.snapshot.params['id'], name: this.route.snapshot.params['name'] } this.route.params.subscribe( (params: Params) =>{ this.user.id = params['id'], this.user.name = params['name'] }); } }
حال اگر صفحه را مجددا بارگذاری کنید مشاهده خواهید کرد که اطلاعات موجود در صفحه نیز بروزرسانی شدهاند.
لازم دانستیم در این بخش به توضیح Observable بپردازیم تا مقدمهای برای ورود به مبحث Observable ایجاد کرده باشیم.
Observable در زبان فارسی به معنی مشاهده کننده (نگهبان) میباشد و امکاناتیست که برای کار با دادههای غیرهمزمان توسط rx.js تولید شده است. دادههای غیرهمزمان به دادههایی گفته میشود که در زمانهای مختلف به سیستم ارسال میشوند.
معمولا در نرمافزارهایی که در اختیار ما قرار میگیرد امکان ارسال چندین داده به صورت موازی وجود دارد. یعنی مثلا فرض کنید به صورت همزمان نرمافزارهای آفیس و مدیا پلیر و ... را باز کردهاید لحظهای که سیستم تمام این نرم افزارها را باز میکند ابتدا یک ناظر قرار میدهد که وظیفهی کنترل اجرای هر برنامه را به عهده داشته باشد. بنابراین ناظر یا Observer تصمیم میگیرد که اطلاعات دریافتی را پس از اعمال یک تبدیل مناسب توسط subscribe منتشر کند.
از مزایای استفاده از مشاهده کننده یا Observable این است که اجرای موازی یا Parallel برنامهها را با ترتیب دلخواه یا به صورت همزمان به عهده میگیرد. یعنی شما میتوانید با استفاده از آن به صورت همزمان چندین کار را انجام دهید و منتظر نباشید که ابتدا یک دستور انجام شود و سپس دستور دیگر پیادهسازی گردد.
برای نظارت بر اطلاعات دریافتی و ارسال آنها از یک عملگر به نام subscribe استفاده میشود. به عبارت دیگر عملگر subscribe تنظیم میکند که کدام عملیات یا داده هم اکنون باید مورد استفاده قرار بگیرد و بگونهای وظیفهی نظارت بر دادههای پردازش شده را به عهده دارد.
حال در نظر بگیرید که نرمافزار شما یک نگهبان یا مشاهدهگر یا ناظر با متد subscribe در اختیار دارد و هرآنچه درون یک مجموعه داده از نوع Observable باشد را بررسی و متناسب با زمان خود انتشار میدهد. اما این کانال همیشه نمیتواند باز باشد زیرا حافظهای را به خود اختصاص داده و اشغال میکند بنابراین برای بستن این دریچه باید از یک متد دیگر به نام unsubscribe استفاده کرد که این متد در زمان بسته شدن یک کامپوننت اتفاق میافتد. بنابراین در هوک ngOnDestroy موجود در فایل user.component.ts خواهیم داشت:
import {Component, OnDestroy, OnInit} from '@angular/core'; import {ActivatedRoute, Params} from "@angular/router"; import {Subscription} from "rxjs"; @Component({ selector: 'app-user', templateUrl: './user.component.html', styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit, OnDestroy { user: { id: number, name: string }; paramsSubscribtion: Subscription constructor(private route: ActivatedRoute) { } ngOnInit() { this.user = { id: this.route.snapshot.params['id'], name: this.route.snapshot.params['name'] } this.paramsSubscribtion = this.route.params.subscribe( (params: Params) =>{ this.user.id = params['id'], this.user.name = params['name'] }); } ngOnDestroy(){ this.paramsSubscribtion.unsubscribe(); } }
همانطور که ملاحظه کردید ابتدا یک ویژگی با نام paramsSubscribtion از نوع کلاس Subscription ایجاد کرده و سپس مقدار بدست آمده توسط route را برابر آن قرار دادیم. در نهایت در هوک ngOnDestroy دروازهی نظارت برای paramsSubscribtion را با متد unsubscribe بستیم.
به عنوان مثال فرض کنید میخواهید اطلاعاتی را از طریق دستورهای کوئری به آدرس ارسال کنید. مثلا قصد داریم مسیر زیر را در آدرس بار مرورگر خود وارد کرده و از آن استفاده کنیم:
http://localhost:4200/servers/1/roxo?model=editing&
http://localhost:4200/servers/1/roxo?model=editing#loading
برای ایجاد مسیرهایی به صورت فوق باید از دو ویژگی به نامهای queryParams و fragment استفاده کرد. برای انجام اینکار ابتدا فایل app.module.ts را باز کرده و سپس یک مسیر دیگر برای ویرایش سرورها به صورت زیر ایجاد میکنیم:
const appRoutes: Routes = [ {path: '', component: HomeComponent}, {path: 'users', component: UsersComponent}, {path: 'users/:id/:name', component: UserComponent}, {path: 'servers', component: ServersComponent}, {path: 'servers/:id/edit', component: EditServerComponent} ];
سپس فایل server.component.html را باز کرده و درون تگ a ویژگیهای پارامتر کوئری و فراگمنت را به آن ارسال میکنیم:
<div class="row"> <button class="btn btn-primary" (click)="onReloadServer()">بارگذاری مجدد سرور</button> <div class="col-xs-12 col-sm-4"> <div class="list-group"> <a [routerLink]="['/servers', 5, 'edit']" [queryParams] = "{allowEdit: 1}" [fragment] = "'loading'" href="#" class="list-group-item" *ngFor="let server of servers"> {{ server.name }} </a> </div> </div> <div class="col-xs-12 col-sm-4"> <app-edit-server></app-edit-server> <hr> <app-server></app-server> </div> </div>
حال اگر روی یکی از اسامی سرور ایجاد شده در صفحه سرورها کلیک کنید به صفحهی کامپوننت EditServerComponent منتقل میشود و مسیر آدرس شما به صورت زیر خواهد بود:
http://localhost:4200/servers/5/edit?allowEdit=1#loading
همچنین درصورتیکه بخواهید یک کاربر با کلیک کردن روی یک دکمه به صفحهای با آدرس فوق منتقل شود کافیست درون متد navigate پارامترها را به صورت زیر تنظیم کنید:
this.route.navigate(['/servers, id, 'edit'],{queryParams:{allowEdit: 1}, fragment:'loading'})
حال برای دسترسی به پارامترهای کوئری و فراگمنتها باید مشابه بخش بازیابی اطلاعات از آدرس، اقدام به ایجاد یک route کنیم. بنابراین فایل edit-server.component.ts را باز کرده و تغییرات زیر را لحاظ میکنیم:
import {Component, OnInit} from '@angular/core'; import {ServersService} from '../servers.service'; import {ActivatedRoute} from "@angular/router"; @Component({ selector: 'app-edit-server', templateUrl: './edit-server.component.html', styleUrls: ['./edit-server.component.css'] }) export class EditServerComponent implements OnInit { server: { id: number, name: string, status: string }; serverName = ''; serverStatus = ''; constructor(private serversService: ServersService, private route: ActivatedRoute) { } ngOnInit() { this.route.queryParams.subscribe(); this.route.fragment.subscribe(); this.server = this.serversService.getServer(1); this.serverName = this.server.name; this.serverStatus = this.server.status; } onUpdateServer() { this.serversService.updateServer(this.server.id, {name: this.serverName, status: this.serverStatus}); } }
توجه داشته باشید که در این قسمت مشاهدهگر و ناظر با استفاده از متد subscribe مورد استفاده قرار گرفته است ولی دیگر نیازی به unsubscribe کردن یا به عبارت دیگر بستن دریچه نیست زیرا پس از انجام این فرمان دریچه به صورت خودکار بسته میشود.
برای درک بهتر مفاهیم ارائه شده اقدام به تکمیل مثال قبلی میکنیم. بنابراین فایل users.component.html را باز کرده و سپس درون تگ a مسیردهی داینامیک یا پویا به ازای هر لینک ارائه میدهیم. بنابراین داریم:
<div class="row"> <div class="col-xs-12 col-sm-4"> <div class="list-group"> <a [routerLink] = "['/users', user.id, user.name]" href="#" class="list-group-item" *ngFor="let user of users" > {{ user.name }} </a> </div> </div> <div class="col-xs-12 col-sm-4"> <app-user></app-user> </div> </div>
همانطور که ملاحظه کردید درون این لینک هر کاربر به صورت داینامیک به مسیر موردنظر ارسال میشود حال فایل servers.component.html را نیز باز کرده و قسمت id موجود در مسیر را با خط زیر جایگزین و به حالت داینامیک و پویا تبدیل میکنیم:
[routerLink]="['/servers', server.id]"
سپس برای استفاده از کامپوننت server که برای نمایش جزئیات هر سرور بکار گرفته میشود یک مسیر جدید درون فایل app.module.ts به صورت زیر ایجاد میکنیم:
const appRoutes: Routes = [ {path: '', component: HomeComponent}, {path: 'users', component: UsersComponent}, {path: 'users/:id/:name', component: UserComponent}, {path: 'servers', component: ServersComponent}, {path: 'servers/:id', component: ServerComponent}, {path: 'servers/:id/edit', component: EditServerComponent} ];
حال میخواهیم هنگامیکه روی آدرس یک سرور مشخص کلیک شد مشابه قبل اطلاعات آن سرور بازیابی شده و در صفحه موردنظر آن سرور نمایش داده شود بنابراین مشابه دو مثال قبل در این قسمت نیز فایل server.component.ts را باز کرده و تغییرات زیر را در آن لحاظ میکنیم:
import {Component, OnDestroy, OnInit} from '@angular/core'; import {ServersService} from '../servers.service'; import {ActivatedRoute, Params} from "@angular/router"; import {Subscription} from "rxjs"; @Component({ selector: 'app-server', templateUrl: './server.component.html', styleUrls: ['./server.component.css'] }) export class ServerComponent implements OnInit, OnDestroy { server: { id: number, name: string, status: string }; paramsSubscribtion: Subscription; constructor(private serversService: ServersService, private route: ActivatedRoute) { } ngOnInit() { const id = +this.route.snapshot.params['id']; this.paramsSubscribtion = this.route.params.subscribe( (params: Params) => { this.server = this.serversService.getServer(+params['id']) } ); this.server = this.serversService.getServer(1); } ngOnDestroy(){ this.paramsSubscribtion.unsubscribe(); } }
حال اگر برنامهی خود را اجرا کنید در کنسول خود خطا دریافت میکنید و علت آن وجود تگ app-server درون قالب کامپوننت servers است. زیرا همواره با این تگ یک کامپوننت single از یک مقدار خاص بارگذاری میشود که فعلا این تگ را غیرفعال کنید تا در ادامه به شما مبحث مسیردهی تو در تو را ارائه کنیم.
بسیار عالی. هم اکنون اگر روی نام هر سرور در صفحه سرورها کلیک کنید، مشخصات آن سرور به صورت کامل برای شما نمایش داده میشود!
همانطور که مسیرهای موجود در فایل app.module.ts را مشاهده میکنید برخی از عبارتها مثل servers و users تکرار شدهاند بنابراین برای جلوگیری از این تکرار و ایجاد ساختار استانداردتر از یک مسیردهی تودرتو با استفاده از ویژگی children استفاده کرده و در نهایت فایلهایی که جزئی از مسیر اصلی میباشند را به عنوان زیرمجموعه به این مسیر اضافه میکنیم. به نمونهی زیر توجه بفرمایید:
const appRoutes: Routes = [ {path: '', component: HomeComponent}, {path: 'users', component: UsersComponent, children:[ {path: ':id/:name', component: UserComponent}, ]}, {path: 'servers', component: ServersComponent, children:[ {path: ':id', component: ServerComponent}, {path: ':id/edit', component: EditServerComponent} ]} ];
حال نوبت به ویرایش فایل servers.component.html میرسد زیرا دیگر به تگهای app-server و app-edit-server نیازی نیست و کامپوننتها با استفاده از تگ router-outlet مسیردهی میشوند. بنابراین تغییرات را در این فایل به صورت زیر اعمال میکنیم:
<div class="row"> <button class="btn btn-primary" (click)="onReloadServer()">بارگذاری مجدد سرور</button> <div class="col-xs-12 col-sm-4"> <div class="list-group"> <a [routerLink]="['/servers', server.id]" [queryParams]="{allowEdit: 1}" [fragment]="'loading'" href="#" class="list-group-item" *ngFor="let server of servers"> {{ server.name }} </a> </div> </div> <div class="col-xs-12 col-sm-4"> <router-outlet></router-outlet> </div> </div>
همچنین برای فایل users.component.html خواهیم داشت:
<div class="row"> <div class="col-xs-12 col-sm-4"> <div class="list-group"> <a [routerLink] = "['/users', user.id, user.name]" href="#" class="list-group-item" *ngFor="let user of users" > {{ user.name }} </a> </div> </div> <div class="col-xs-12 col-sm-4"> <router-outlet></router-outlet> </div> </div>
با اعمال این تغییرات و بارگذاری مجدد صفحه متوجه خواهید شد که به راحتی میتوانید بین مسیرهای ایجاد شده حرکت کنید و مشکلی از نظر ساختاری برای برنامهی شما بوجود نیاید.
بسیار عالی! به شما دوستان عزیز تبریک میگوییم در این بخش توانستید قابلیتها و تواناییهای موردنیاز را برای کار با مسیرها و انواع تکنیکهای مسیردهی تقویت کنید. در بخش بعدی به توضیح پیشرفتهی کنترل کوئری پارمترها و مباحث امنیتی مسیرها میپردازیم. با ما همراه باشید.
توجه: دوستان عزیز آموزش ویدیویی انگولار 6 از مقدماتی تا پیشرفته به زبان فارسی را میتوانید با کلیک روی اینجا یاد بگیرید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.