در کامپوننتهای razor برای <InputText> صفت autocomplete وجود ندارد. شما چه راه حلی را پیشنهاد میدهید؟
نظرات مطالب
معرفی Bit Platform
آیا کامپوننتهای فارسی و به صورت کلی RTL رو هم پشتیبانی میکنید ؟
توی دمو من کامپوننت RTL ندیدم.
اغلب برنامههای بزرگ Angular، ویژگیهای مختلف خود را به ماژولهای مجزایی تبدیل میکنند. این ماژولها شبیه به مفهوم Area در ASP.NET MVC هستند و هدف آنها نظم بخشیدن به کامپوننتهای ویژهی یک قسمت خاص از برنامه، در ناحیهای مختص به آن میباشد. به علاوه ایجاد ماژولها، قابلیت lazy loading مسیریابیها را نیز مسیر میکند. هر برنامهی Angular حداقل به همراه یک ماژول است که بر اساس قراردادی، AppModule نام گرفتهاست و در فایل src\app\app.module.ts قرار دارد. با توسعهی برنامه و بیشتر شدن قابلیتهای آن، استفادهی از این تک ماژول پیش فرض، مشکل تداخل مسئولیتها را به همراه خواهد داشت. برای رفع این مشکل توصیه شدهاست که کامپوننتهای مرتبط به یک ویژگی خاص را درون ماژولی مختص به خود قرار دهید؛ برای مثال مانند ماژول محصولات، برای نظم دهی به کامپوننتهای لیست محصولات، جزئیات محصولات، ویرایش محصولات و غیره، ماژول کاربران برای تعریف کامپوننتهای لاگین، تغییر کلمهی عبور و امثال آن. در این قسمت قصد داریم نحوهی تنظیمات مسیریابی و هدایت کاربران را به ماژولهای مختلف برنامه، بررسی کنیم.
تنظیم مسیریابی ماژولها
در اینجا نیازی به تنظیم base path نیست و این تنظیم تنها یکبار به ازای کل برنامه انجام میشود. همانطور که در قسمت قبل نیز عنوان شد، ماژول مسیریابی Angular و یا همان RouterModule، به همراه سرویسی برای دسترسی به امکانات آن، تنظیمات مسیریابی و یک سری دایرکتیو مانند routerLink، جهت تعامل با آن است. از آنجائیکه سرویس ماژول مسیریابی در فایل src\app\app-routing.module.ts تعریف و تنظیم شدهاست، باید اطمینان حاصل کرد که این سرویس تنها یکبار در طول عمر برنامه وهله سازی میشود و از آنجائیکه هر ماژول تنظیمات مجزای مسیریابی خود را خواهد داشت، دیگر نمیتوان از متد RouterModule.forRoot سراسری استفاده کرد و در اینجا باید از متد forChild این ماژول، جهت تعریف تنظیمات مسیریابیهای ماژولهای مختلف کمک گرفت. متد forChild نیز شبیه به همان آرایهی تنظیمات مسیریابی متد forRoot را دریافت میکند.
یک مثال: در ادامهی مثالی که در قسمت قبل به کمک Angular CLI ایجاد کردیم، ماژول جدید محصولات را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
پس از اجرای این دستور، ماژول جدید محصولات در فایل src\app\product\product.module.ts و تنظیمات ابتدایی آن در فایل src\app\product\product-routing.module.ts تشکیل میشوند:
همانطور که مشاهده میکنید، در حین تشکیل تنظیمات ابتدایی مسیریابی این ماژول جدید، اینبار از متد forChild استفاده شدهاست و نه متد forRoot که مختص به ماژول اصلی و سراسری برنامهاست.
سپس ProductRoutingModule به قسمت imports ماژول محصولات به صورت خودکار اضافه شدهاست:
در ادامه کامپوننت جدید لیست محصولات را به این ماژول اضافه میکنیم:
به این ترتیب داخل پوشهای به نام produc-list، کامپوننت product-list.component.ts تشکیل خواهد شد. در حقیقت این دستور اعمال ذیل را انجام میدهد:
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول محصولات، جهت درج این کامپوننت جدید، در قسمت declarations فایل product.module.ts نیز به صورت خودکار انجام شدهاست:
اکنون که این ماژول جدید را به همراه یک کامپوننت نمونه در آن تعریف کردیم، برای افزودن مسیریابی به آن، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آرایهی Routes آنرا تکمیل میکنیم:
ابتدا کامپوننت لیست محصولات import شده و سپس آرایهی Routes مسیری را به این کامپوننت تعریف کردهایم.
در ادامه میخواهیم لینکی را به این مسیریابی جدید اضافه کنیم. در قسمت قبل منویی را به برنامه اضافه کردیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink جدیدی را به آن اضافه میکنیم:
پیشتر لینکی را به کامپوننت welcome در قسمت قبل اضافه کرده بودیم. در اینجا لینک جدیدی را به کامپوننت لیست محصولات، در ذیل آن تعریف کردهایم.
در اینجا نیز نحوهی تعریف لینکها مانند قبل است و آرایهی تنظیمات پارامترهای لینک باید به مقدار خاصیت path تعریف شده اشاره کند.
اکنون دستور ng serve -o را صادر کنید تا برنامه در حافظه ساخته شده و در مرورگر نمایش داده شود. در ادامه اگر بر روی لینک لیست محصولات کلیک کنید، صفحهی ذیل را مشاهده خواهید کرد:
به این معنا که برنامه اطلاعی از این مسیریابی جدید نداشته و صفحهی یافت نشدن مسیریابی را که در قسمت قبل تنظیم کردیم، نمایش دادهاست. برای رفع این مشکل باید به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی کنیم:
در اینجا در ابتدا ماژول محصولات import شده و سپس به قسمت لیست imports ماژول App اضافه گردیدهاست. اکنون مسیریابی به این کامپوننت جدید واقع شدهی در قسمت ماژول محصولات، کار میکند:
نکته 1: علت اینکه ProductModule را پیش از AppRoutingModule تعریف کردیم این است که AppRoutingModule دارای تعریف مسیریابی ** یا catch all است که در قسمت قبل آنرا جهت مدیریت مسیرهای یافت نشده به برنامه افزودیم. اگر ابتدا AppRoutingModule تعریف میشد و سپس ProductModule، هیچگاه فرصت به پردازش مسیریابیهای ماژول محصولات نمیرسید؛ چون مسیر ** پیشتر برنده شده بود.
نکته 2: میتوان در قسمت import متد RouterModule.forRoot را نیز مستقیما قرار داد (بجای AppRoutingModule). اگر این کار صورت گیرد، ابتدا مسیریابیهای موجود در ماژولها پردازش میشوند و در آخر مسیرهای موجود در RouterModule.forRoot صرفنظر از محل قرارگیری آن در این لیست بررسی خواهد شد (حتی اگر در ابتدای لیست قرار گیرد). هرچند جهت مدیریت بهتر برنامه، این متد به AppRoutingModule منتقل شدهاست. بنابراین اکنون «نکتهی 1» برقرار است.
انتخاب استراتژی مناسب نامگذاری مسیرها
هنگام کار کردن با تعدادی ویژگی مرتبط به هم قرار گرفتهی داخل یک ماژول، بهتر است روش نامگذاری مناسبی را برای تنظیمات مسیریابی آن درنظر گرفت تا مسیرهای تعیین شده علاوه بر زیبایی، وضوح بیشتری را نیز پیدا کنند. به علاوه این نامگذاری مناسب، گروه بندی مسیریابیها و lazy loading آنها را نیز سادهتر میکند.
استراتژی ابتدایی که به ذهن میرسد، نامگذاری هر مسیر بر اساس عملکرد آنها است مانند products برای نمایش لیست محصولات، product/:id برای نمایش جزئیات محصولی خاص که در اینجا id پارامتر مسیریابی است و productEdit/:id برای ویرایش جزئیات یک محصول مشخص. همانطور که مشاهده میکنید، هرچند این مسیرها متعلق به یک ماژول هستند، اما مسیرهای تعیین شدهی برای آنها اینگونه به نظر نمیرسد. بنابراین بهتر است تمام ویژگیهای قرار گرفتهی درون یک ماژول را با مسیر ریشهی یکسانی شروع کنیم. به این ترتیب نمایش لیست محصولات همان products باقی خواهد ماند اما برای نمایش جزئیات محصولی خاص از مسیر products/:id استفاده میکنیم (همان اسم جمع ریشهی مسیر؛ بجای اسم مفرد). اینبار مسیر ویرایش جزئیات یک محصول به صورت products/:id/edit تنظیم خواهد شد:
در اینجا نام ریشهی یکسانی را برای عناصر مختلف قرارگرفتهی درون یک ماژول انتخاب کردهایم؛ تا ارتباط بین آنها بهتر مشخص شود و همچنین در آینده بتوان مباحث گروه بندی و lazy loading را نیز بر این اساس پیاده سازی کرد.
فعالسازی یک مسیر با کدنویسی
تا اینجا نحوهی فعالسازی یک مسیر را با استفاده از دایرکتیو routerLink بررسی کردیم. اما گاهی از اوقات نیاز است تا بتوان با کدنویسی نیز کاربران را به مسیری خاص هدایت کرد. برای مثال پس از عملیات logout میخواهیم مجددا صفحهی اول سایت نمایش داده شود. برای اینکار از سرویس Router مسیریاب Angular کمک گرفته میشود. ابتدا آنرا در سازندهی یک کامپوننت تزریق کرده و سپس میتوان به قابلیتهای آن مانند استفادهی از متد navigate آن، در کدهای برنامه دسترسی یافت.
باید درنظر داشت که دایرکتیو routerLink نیز در پشت صحنه از همین متد navigate سرویس Router استفاده میکند. بنابراین تمام پارامترهای آن در متد navigate نیز قابل استفاده هستند. برای مثال زمانیکه تعداد پارامترهای routerLink یک مورد است، میتوان آرایهی آنرا به یک رشته خلاصه کرد. یک چنین قابلیتی با متد navigate نیز میسر است.
متد navigate تنها قسمتهایی از URL جاری را تغییر میدهد. اگر نیاز باشد تا کل آدرس تعویض شود، میتوان از متد دیگر سرویس Router به نام navigateByUrl استفاده کرد. این متد تمام URL segments موجود را با مسیر جدیدی جایگزین میکند. به علاوه برخلاف متد navigate، تنها یک رشته را به عنوان پارامتر میپذیرد.
در ادامه مثال جاری میخواهیم پیاده سازی ابتدایی login و logout را به برنامه اضافه کنیم. به همین منظور ابتدا ماژول جدید user را به همراه تنظیمات ابتدایی مسیریابی آن اضافه میکنیم:
به این ترتیب دو فایل src\app\user\user-routing.module.ts و src\app\user\user.module.ts به برنامه اضافه میشوند.
همانند ماژول قبلی، نیاز است UserModule را به قسمت imports فایل src\app\app.module.ts نیز معرفی کنیم:
سپس کامپوننت جدید لاگین را به ماژول user برنامه اضافه میکنیم:
که اینکار سبب به روز رسانی فایل user.module.ts جهت تکمیل قسمت declarations آن با LoginComponent نیز میشود.
در ادامه به فایل src\app\user\user-routing.module.ts مراجعه کرده و مسیریابی جدیدی را به کامپوننت لاگین تعریف میکنیم:
ابتدا این کامپوننت import شده و سپس یک path جدید را به آن انتساب میدهیم.
مرحلهی بعد، فعالسازی این مسیریابی است، با تعریف لینکی به آن. به همین جهت به فایل src\app\app.component.html مراجعه کرده و منوی برنامه را تکمیل میکنیم:
اکنون دستور ng serve -o را صادر کنید تا برنامه در حافظه ساخته شده و در مرورگر نمایش داده شود و سپس بر روی لینک login کلیک کنید تا قالب ابتدایی آن نمایش داده شود:
تکمیل کامپوننت login و افزودن لینک logout
در ادامه میخواهیم یک فرم لاگین مقدماتی را پس از کلیک بر روی لینک لاگین نمایش دهیم و هدایت به صفحهی لیست محصولات را پس از لاگین و مخفی کردن لینک لاگین و نمایش لینک خروج را در این حالت پیاده سازی کنیم. برای این منظور ابتدا اینترفیس خالی کاربر را ایجاد میکنیم:
که سبب ایجاد فایل src\app\user\user.ts خواهد شد. این اینترفیس را به صورت زیر تکمیل میکنیم:
پس از آن یک سرویس ابتدایی اعتبارسنجی کاربران را نیز اضافه خواهیم کرد:
که سبب افزوده شدن سرویس auth.service.ts و همچنین به روز رسانی خودکار قسمت providers ماژول user.module.ts نیز میشود:
اگر نام ماژول را ذکر نکنیم، سرویس مدنظر تولید خواهد شد، اما قسمت providers هیچ ماژولی به صورت خودکار تکمیل نمیشود.
پس از ایجاد قالب ابتدایی فایل auth.service.ts آنرا به نحو ذیل تکمیل کنید:
در اینجا اگر کاربر هر نوع کلمهی عبور و یا نام کاربری را وارد کند، به سیستم وارد خواهد شد. اگر نامش admin باشد، دسترسی admin پیدا میکند و همچنین متدهای logout با null کردن یوزر وارد شدهی به سیستم و isLoggedIn برای مشخص بودن نال نبودن شیء کاربر جاری، به این سرویس اضافه شدهاند.
سپس کامپوننت لاگین واقع در فایل src\app\user\login\login.component.ts را به نحو ذیل تکمیل کنید:
در این کامپوننت دو سرویس AuthService، که پیشتر ایجاد کردیم و سرویس Router، به سازندهی کلاس تزریق شدهاند.
از AuthService برای اعتبارسنجی کاربر و لاگین او به سیستم استفاده میکنیم و از سرویس مسیریاب Angular جهت فراخوانی متد navigate آن به صفحهی مشاهدهی محصولات، پس از لاگین کاربر استفاده شدهاست.
اکنون میخواهیم قالب این کامپوننت را نیز تکمیل کنیم. پیش از آن به فایل src\app\user\user.module.ts مراجعه کرده و در قسمت imports آن FormsModule را نیز اضافه کنید:
سپس فایل src\app\user\login\login.component.html را به نحو ذیل تغییر دهید:
تا اینجا صفحهی لاگین نمایش داده شده و کاربر میتواند به سیستم وارد شود. تا زمانیکه کلمهی عبور و نام کاربری وارد نشده باشند، دکمهی login این فرم غیرفعال باقی میماند. پس از آن کاربر با هر نوع ترکیبی از کلمهی عبور و نام کاربری میتواند به سیستم وارد شده و بلافاصله به صفحهی لیست محصولات هدایت میشود.
اکنون میخواهیم پس از ورود او، نام او را نمایش داده و همچنین دکمهی logout را بجای login در منوی بالای سایت نمایش دهیم. به همین جهت در قالب کامپوننت App که منوی برنامه در آن تنظیم شدهاست، نیاز است بتوانیم به سرویس Auth سفارشی دسترسی یافته و خروجی متد isLoggedIn آنرا بررسی کنیم. به همین منظور به فایل src\app\app.component.ts مراجعه کرده و آنرا به صورت ذیل تکمیل کنید:
در اینجا دو سرویس Auth و Router به سازندهی کامپوننت App تزریق شدهاند. به این ترتیب میتوان از شیء authService در قالب این کامپوننت برای دسترسی به متد isLoggedIn و سایر خواص این سرویس استفاده کرد. همچنین از سرویس مسیریاب Angular برای پیاده سازی logout و هدایت کاربر به صفحهی welcome کمک گرفتهایم. در اینجا از متد navigateByUrl استفاده شدهاست؛ از این جهت که پس از Logout دیگر حفظ URL Segments موجود بیمفهوم است و تمام آنها باید پاک شده و با آدرس جدید جایگزین شوند.
پس از این تغییرات، اکنون میتوان قالب src\app\app.component.html را به نحو ذیل تکمیل کرد:
همانطور که مشاهده میکنید، دوبار لاگین بودن کاربر جاری توسط متد authService.isLoggedIn بررسی شدهاست. اگر خروجی این متد true باشد، نام کاربری شخص به همراه لینک Logout نمایش داده میشود. اگر خروجی این متد false باشد، تنها لینک login نمایان شده و مابقی گزینهها (لینک لاگین و نمایش نام شخص) از صفحه حذف میشوند.
اکنون اگر برنامه را توسط دستور ng serve -o اجرا کنید، صفحهی لاگین و منوی بالای صفحه چنین شکلی را خواهد داشت:
پس از لاگین، لینک لاگین از منو حذف شده و سپس نام کاربری و لینک به logout نمایان میشوند.
اینبار اگر بر روی logout کلیک کنید، نام کاربری و لینک logout از صفحه حذف و مجددا لینک لاگین نمایش داده میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
تنظیم مسیریابی ماژولها
در اینجا نیازی به تنظیم base path نیست و این تنظیم تنها یکبار به ازای کل برنامه انجام میشود. همانطور که در قسمت قبل نیز عنوان شد، ماژول مسیریابی Angular و یا همان RouterModule، به همراه سرویسی برای دسترسی به امکانات آن، تنظیمات مسیریابی و یک سری دایرکتیو مانند routerLink، جهت تعامل با آن است. از آنجائیکه سرویس ماژول مسیریابی در فایل src\app\app-routing.module.ts تعریف و تنظیم شدهاست، باید اطمینان حاصل کرد که این سرویس تنها یکبار در طول عمر برنامه وهله سازی میشود و از آنجائیکه هر ماژول تنظیمات مجزای مسیریابی خود را خواهد داشت، دیگر نمیتوان از متد RouterModule.forRoot سراسری استفاده کرد و در اینجا باید از متد forChild این ماژول، جهت تعریف تنظیمات مسیریابیهای ماژولهای مختلف کمک گرفت. متد forChild نیز شبیه به همان آرایهی تنظیمات مسیریابی متد forRoot را دریافت میکند.
یک مثال: در ادامهی مثالی که در قسمت قبل به کمک Angular CLI ایجاد کردیم، ماژول جدید محصولات را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد میکنیم:
>ng g m product --routing
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class ProductRoutingModule { }
سپس ProductRoutingModule به قسمت imports ماژول محصولات به صورت خودکار اضافه شدهاست:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ProductRoutingModule } from './product-routing.module'; @NgModule({ imports: [ CommonModule, ProductRoutingModule ], declarations: [] }) export class ProductModule { }
در ادامه کامپوننت جدید لیست محصولات را به این ماژول اضافه میکنیم:
>ng g c product/ProductList
installing component create src\app\product\product-list\product-list.component.css create src\app\product\product-list\product-list.component.html create src\app\product\product-list\product-list.component.spec.ts create src\app\product\product-list\product-list.component.ts update src\app\product\product.module.ts
import { ProductListComponent } from './product-list/product-list.component'; @NgModule({ imports: [ ], declarations: [ProductListComponent] }) export class ProductModule { }
اکنون که این ماژول جدید را به همراه یک کامپوننت نمونه در آن تعریف کردیم، برای افزودن مسیریابی به آن، به فایل src\app\product\product-routing.module.ts مراجعه کرده و آرایهی Routes آنرا تکمیل میکنیم:
import { ProductListComponent } from './product-list/product-list.component'; const routes: Routes = [ { path: 'products', component: ProductListComponent } ];
در ادامه میخواهیم لینکی را به این مسیریابی جدید اضافه کنیم. در قسمت قبل منویی را به برنامه اضافه کردیم. به همین جهت به فایل src\app\app.component.html مراجعه کرده و routerLink جدیدی را به آن اضافه میکنیم:
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/home']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
در اینجا نیز نحوهی تعریف لینکها مانند قبل است و آرایهی تنظیمات پارامترهای لینک باید به مقدار خاصیت path تعریف شده اشاره کند.
اکنون دستور ng serve -o را صادر کنید تا برنامه در حافظه ساخته شده و در مرورگر نمایش داده شود. در ادامه اگر بر روی لینک لیست محصولات کلیک کنید، صفحهی ذیل را مشاهده خواهید کرد:
به این معنا که برنامه اطلاعی از این مسیریابی جدید نداشته و صفحهی یافت نشدن مسیریابی را که در قسمت قبل تنظیم کردیم، نمایش دادهاست. برای رفع این مشکل باید به فایل src\app\app.module.ts مراجعه کرده و این ماژول جدید را به آن معرفی کنیم:
import { ProductModule } from './product/product.module'; @NgModule({ declarations: [ ], imports: [ BrowserModule, FormsModule, HttpModule, ProductModule, AppRoutingModule ],
نکته 1: علت اینکه ProductModule را پیش از AppRoutingModule تعریف کردیم این است که AppRoutingModule دارای تعریف مسیریابی ** یا catch all است که در قسمت قبل آنرا جهت مدیریت مسیرهای یافت نشده به برنامه افزودیم. اگر ابتدا AppRoutingModule تعریف میشد و سپس ProductModule، هیچگاه فرصت به پردازش مسیریابیهای ماژول محصولات نمیرسید؛ چون مسیر ** پیشتر برنده شده بود.
نکته 2: میتوان در قسمت import متد RouterModule.forRoot را نیز مستقیما قرار داد (بجای AppRoutingModule). اگر این کار صورت گیرد، ابتدا مسیریابیهای موجود در ماژولها پردازش میشوند و در آخر مسیرهای موجود در RouterModule.forRoot صرفنظر از محل قرارگیری آن در این لیست بررسی خواهد شد (حتی اگر در ابتدای لیست قرار گیرد). هرچند جهت مدیریت بهتر برنامه، این متد به AppRoutingModule منتقل شدهاست. بنابراین اکنون «نکتهی 1» برقرار است.
انتخاب استراتژی مناسب نامگذاری مسیرها
هنگام کار کردن با تعدادی ویژگی مرتبط به هم قرار گرفتهی داخل یک ماژول، بهتر است روش نامگذاری مناسبی را برای تنظیمات مسیریابی آن درنظر گرفت تا مسیرهای تعیین شده علاوه بر زیبایی، وضوح بیشتری را نیز پیدا کنند. به علاوه این نامگذاری مناسب، گروه بندی مسیریابیها و lazy loading آنها را نیز سادهتر میکند.
استراتژی ابتدایی که به ذهن میرسد، نامگذاری هر مسیر بر اساس عملکرد آنها است مانند products برای نمایش لیست محصولات، product/:id برای نمایش جزئیات محصولی خاص که در اینجا id پارامتر مسیریابی است و productEdit/:id برای ویرایش جزئیات یک محصول مشخص. همانطور که مشاهده میکنید، هرچند این مسیرها متعلق به یک ماژول هستند، اما مسیرهای تعیین شدهی برای آنها اینگونه به نظر نمیرسد. بنابراین بهتر است تمام ویژگیهای قرار گرفتهی درون یک ماژول را با مسیر ریشهی یکسانی شروع کنیم. به این ترتیب نمایش لیست محصولات همان products باقی خواهد ماند اما برای نمایش جزئیات محصولی خاص از مسیر products/:id استفاده میکنیم (همان اسم جمع ریشهی مسیر؛ بجای اسم مفرد). اینبار مسیر ویرایش جزئیات یک محصول به صورت products/:id/edit تنظیم خواهد شد:
products products/:id products/:id/edit
فعالسازی یک مسیر با کدنویسی
تا اینجا نحوهی فعالسازی یک مسیر را با استفاده از دایرکتیو routerLink بررسی کردیم. اما گاهی از اوقات نیاز است تا بتوان با کدنویسی نیز کاربران را به مسیری خاص هدایت کرد. برای مثال پس از عملیات logout میخواهیم مجددا صفحهی اول سایت نمایش داده شود. برای اینکار از سرویس Router مسیریاب Angular کمک گرفته میشود. ابتدا آنرا در سازندهی یک کامپوننت تزریق کرده و سپس میتوان به قابلیتهای آن مانند استفادهی از متد navigate آن، در کدهای برنامه دسترسی یافت.
باید درنظر داشت که دایرکتیو routerLink نیز در پشت صحنه از همین متد navigate سرویس Router استفاده میکند. بنابراین تمام پارامترهای آن در متد navigate نیز قابل استفاده هستند. برای مثال زمانیکه تعداد پارامترهای routerLink یک مورد است، میتوان آرایهی آنرا به یک رشته خلاصه کرد. یک چنین قابلیتی با متد navigate نیز میسر است.
متد navigate تنها قسمتهایی از URL جاری را تغییر میدهد. اگر نیاز باشد تا کل آدرس تعویض شود، میتوان از متد دیگر سرویس Router به نام navigateByUrl استفاده کرد. این متد تمام URL segments موجود را با مسیر جدیدی جایگزین میکند. به علاوه برخلاف متد navigate، تنها یک رشته را به عنوان پارامتر میپذیرد.
در ادامه مثال جاری میخواهیم پیاده سازی ابتدایی login و logout را به برنامه اضافه کنیم. به همین منظور ابتدا ماژول جدید user را به همراه تنظیمات ابتدایی مسیریابی آن اضافه میکنیم:
>ng g m user --routing
همانند ماژول قبلی، نیاز است UserModule را به قسمت imports فایل src\app\app.module.ts نیز معرفی کنیم:
import { UserModule } from './user/user.module'; @NgModule({ declarations: [ ], imports: [ BrowserModule, FormsModule, HttpModule, ProductModule, UserModule, AppRoutingModule ],
سپس کامپوننت جدید لاگین را به ماژول user برنامه اضافه میکنیم:
>ng g c user/login
در ادامه به فایل src\app\user\user-routing.module.ts مراجعه کرده و مسیریابی جدیدی را به کامپوننت لاگین تعریف میکنیم:
import { LoginComponent } from './login/login.component'; const routes: Routes = [ { path: 'login', component: LoginComponent} ];
مرحلهی بعد، فعالسازی این مسیریابی است، با تعریف لینکی به آن. به همین جهت به فایل src\app\app.component.html مراجعه کرده و منوی برنامه را تکمیل میکنیم:
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/home']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li> <a [routerLink]="['/login']">Log In</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
تکمیل کامپوننت login و افزودن لینک logout
در ادامه میخواهیم یک فرم لاگین مقدماتی را پس از کلیک بر روی لینک لاگین نمایش دهیم و هدایت به صفحهی لیست محصولات را پس از لاگین و مخفی کردن لینک لاگین و نمایش لینک خروج را در این حالت پیاده سازی کنیم. برای این منظور ابتدا اینترفیس خالی کاربر را ایجاد میکنیم:
>ng g i user/user
export interface IUser { id: number; userName: string; isAdmin: boolean; }
پس از آن یک سرویس ابتدایی اعتبارسنجی کاربران را نیز اضافه خواهیم کرد:
>ng g s user/auth -m user/user.module
installing service create src\app\user\auth.service.spec.ts create src\app\user\auth.service.ts update src\app\user\user.module.ts
پس از ایجاد قالب ابتدایی فایل auth.service.ts آنرا به نحو ذیل تکمیل کنید:
import { IUser } from './user'; import { Injectable } from '@angular/core'; @Injectable() export class AuthService { currentUser: IUser; constructor() { } isLoggedIn(): boolean { return !this.currentUser; } login(userName: string, password: string): boolean { if (!userName || !password) { return false; } if (userName === 'admin') { this.currentUser = { id: 1, userName: userName, isAdmin: true }; return true; } this.currentUser = { id: 2, userName: userName, isAdmin: false }; return true; } logout(): void { this.currentUser = null; } }
سپس کامپوننت لاگین واقع در فایل src\app\user\login\login.component.ts را به نحو ذیل تکمیل کنید:
import { Router } from '@angular/router'; import { AuthService } from './../auth.service'; import { Component, OnInit } from '@angular/core'; import { NgForm } from '@angular/forms'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent implements OnInit { errorMessage: string; pageTitle = 'Log In'; constructor(private authService: AuthService, private router: Router) { } ngOnInit() { } login(loginForm: NgForm) { if (loginForm && loginForm.valid) { let userName = loginForm.form.value.userName; let password = loginForm.form.value.password; if (this.authService.login(userName, password)) { this.router.navigate(['/products']); } } else { this.errorMessage = 'Please enter a user name and password.'; }; } }
از AuthService برای اعتبارسنجی کاربر و لاگین او به سیستم استفاده میکنیم و از سرویس مسیریاب Angular جهت فراخوانی متد navigate آن به صفحهی مشاهدهی محصولات، پس از لاگین کاربر استفاده شدهاست.
اکنون میخواهیم قالب این کامپوننت را نیز تکمیل کنیم. پیش از آن به فایل src\app\user\user.module.ts مراجعه کرده و در قسمت imports آن FormsModule را نیز اضافه کنید:
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ CommonModule, FormsModule, UserRoutingModule ],
سپس فایل src\app\user\login\login.component.html را به نحو ذیل تغییر دهید:
<div class="panel panel-default"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <form class="form-horizontal" novalidate (ngSubmit)="login(loginForm)" #loginForm="ngForm" autocomplete="off"> <fieldset> <div class="form-group" [ngClass]="{'has-error': (userNameVar.touched || userNameVar.dirty) && !userNameVar.valid }"> <label class="col-md-2 control-label" for="userNameId">User Name</label> <div class="col-md-8"> <input class="form-control" id="userNameId" type="text" placeholder="User Name (required)" required (ngModel)="userName" name="userName" #userNameVar="ngModel" /> <span class="help-block" *ngIf="(userNameVar.touched || userNameVar.dirty) && userNameVar.errors"> <span *ngIf="userNameVar.errors.required"> User name is required. </span> </span> </div> </div> <div class="form-group" [ngClass]="{'has-error': (passwordVar.touched || passwordVar.dirty) && !passwordVar.valid }"> <label class="col-md-2 control-label" for="passwordId">Password</label> <div class="col-md-8"> <input class="form-control" id="passwordId" type="password" placeholder="Password (required)" required (ngModel)="password" name="password" #passwordVar="ngModel" /> <span class="help-block" *ngIf="(passwordVar.touched || passwordVar.dirty) && passwordVar.errors"> <span *ngIf="passwordVar.errors.required"> Password is required. </span> </span> </div> </div> <div class="form-group"> <div class="col-md-4 col-md-offset-2"> <span> <button class="btn btn-primary" type="submit" style="width:80px;margin-right:10px" [disabled]="!loginForm.valid"> Log In </button> </span> <span> <a class="btn btn-default" [routerLink]="['/welcome']"> Cancel </a> </span> </div> </div> </fieldset> </form> <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div> </div> </div>
اکنون میخواهیم پس از ورود او، نام او را نمایش داده و همچنین دکمهی logout را بجای login در منوی بالای سایت نمایش دهیم. به همین جهت در قالب کامپوننت App که منوی برنامه در آن تنظیم شدهاست، نیاز است بتوانیم به سرویس Auth سفارشی دسترسی یافته و خروجی متد isLoggedIn آنرا بررسی کنیم. به همین منظور به فایل src\app\app.component.ts مراجعه کرده و آنرا به صورت ذیل تکمیل کنید:
import { Router } from '@angular/router'; import { AuthService } from './user/auth.service'; import { Component } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { pageTitle: string = 'Routing Lab'; constructor(private authService: AuthService, private router: Router) { } logOut(): void { this.authService.logout(); this.router.navigateByUrl('/welcome'); } }
پس از این تغییرات، اکنون میتوان قالب src\app\app.component.html را به نحو ذیل تکمیل کرد:
<nav class="navbar navbar-default"> <div class="container-fluid"> <a class="navbar-brand">{{title}}</a> <ul class="nav navbar-nav"> <li> <a [routerLink]="['/home']">Home</a> </li> <li> <a [routerLink]="['/products']">Product List</a> </li> </ul> <ul class="nav navbar-nav navbar-right"> <li *ngIf="authService.isLoggedIn()"> <a>Welcome {{ authService.currentUser.userName }}</a> </li> <li *ngIf="!authService.isLoggedIn()"> <a [routerLink]="['/login']">Log In</a> </li> <li *ngIf="authService.isLoggedIn()"> <a (click)="logOut()">Log Out</a> </li> </ul> </div> </nav> <div class="container"> <router-outlet></router-outlet> </div>
اکنون اگر برنامه را توسط دستور ng serve -o اجرا کنید، صفحهی لاگین و منوی بالای صفحه چنین شکلی را خواهد داشت:
پس از لاگین، لینک لاگین از منو حذف شده و سپس نام کاربری و لینک به logout نمایان میشوند.
اینبار اگر بر روی logout کلیک کنید، نام کاربری و لینک logout از صفحه حذف و مجددا لینک لاگین نمایش داده میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-01.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
تا اینجا با نحوهی تعریف کامپوننتها و انتقال اطلاعات به آنها و برعکس، آشنا شدیم. در ادامه علاقمندیم اگر کامپوننتی را به صورت زیر تعریف کردیم:
محتوایی را که بین تگهای این کامپوننت فرزند، توسط کامپوننت والد تنظیم شدهاست، در قسمت مشخصی از UI کامپوننت فرزند نمایش دهیم.
معرفی مفهوم Render Fragment
برای درج محتوای تامین شدهی توسط کامپوننت والد در یک کامپوننت فرزند، از ویژگی به نام Render Fragment استفاده میشود. مثالی جهت توضیح جزئیات آن:
در ابتدا یک کامپوننت والد جدید را در مسیر Pages\LearnBlazor\ParentComponent.razor به صورت زیر تعریف میکنیم:
- در اینجا یک کامپوننت متداول با مسیریابی منتهی به ParentComponent/ تعریف شدهاست.
- سپس دوبار کامپوننت فرضی ChildComponent به همراه پارامتر Title و یک محتوای جدید قرار گرفتهی در بین تگهای آن، در صفحه تعریف شدهاند.
- بار دومی که ChildComponent در صفحه قرار گرفتهاست، به همراه محتوای جدیدی در بین تگهای خود نیست.
برای دسترسی به این کامپوننت از طریق منوی برنامه، مدخل منوی آنرا به کامپوننت Shared\NavMenu.razor اضافه میکنیم:
اکنون میخواهیم کامپوننت ChildComponent را افزوده و انتظارت یاد شده (دریافت یک پارامتر و نمایش محتوای سفارشی تنظیم شده) را پیاده سازی کنیم. به همین جهت فایل جدید Pages\LearnBlazor\LearnBlazorComponents\ChildComponent.razor را با محتوای زیر ایجاد میکنیم:
توضیحات:
- خاصیت عمومی Title که توسط ویژگی Parameter مزین شدهاست، امکان تنظیم مقدار مشخصی را توسط کامپوننت دربرگیرندهی ChildComponent میسر میکند.
- در اینجا پارامتر عمومی دیگری نیز تعریف شدهاست که اینبار از نوع ویژهی RenderFragment است. توسط آن میتوان به محتوایی که در کامپوننت والد ChildComponent در بین تگهای آن تنظیم شدهاست، دسترسی یافت. همچنین اگر این محتوا توسط کامپوننت والد تنظیم نشده باشد، مانند دومین باری که ChildComponent در صفحه قرار گرفتهاست، میتوان با بررسی نال بودن آن، یک محتوای پیشفرض را نمایش داد.
با این خروجی:
روش دیگری برای فراخوانی Event Call Back ها
در قسمت قبل روش انتقال اطلاعات را از کامپوننتهای فرزند، به والد مشاهده کردیم. فراخوانی آنها در سمت Child Component نیاز به یک متد اضافی داشت و همچنین تنها یک پارامتر را هم ارسال کردیم. برای ساده سازی این عملیات از روش زیر نیز میتوان استفاده کرد:
- در اینجا برای تعریف و ارسال بیش از یک پارامتر، از tuples استفاده شدهاست.
- همچنین فراخوانی OnClickBtnMethod.InvokeAsync را نیز در محل تعریف onclick@ بدون نیازی به یک متد اضافی، مشاهده میکنید. نکتهی مهم آن، قرار دادن این قطعه کد داخل ()@ است تا ابتدا و انتهای کدهای #C مشخص شود؛ وگرنه کامپایل نمیشود.
در سمت کامپوننت والد برای دسترسی به OnClickBtnMethod که اینبار یک tuple را ارسال میکند، میتوان به صورت زیر عمل کرد:
در اینجا OnClickBtnMethod تعریف شده، به متد ShowMessage متصل شدهاست که یک tuple از جنس (int, string) را دریافت میکند. سپس با انتساب خاصیت رشتهای آن به فیلد جدید MessageText، سبب نمایش آن میشویم.
امکان تعریف چندین RenderFragment
تا اینجا یک RenderFragment را در کامپوننت فرزند تعریف کردیم. امکان تعریف چندین RenderFragment در ChildComponent.razor نیز وجود دارند:
در یک چنین حالتی، روش استفادهی از این پارامترهای RenderFragment در سمت والد (ParentComponent.razor) به صورت زیر خواهد بود:
که به همراه ذکر صریح نام پارامترها به صورت تگهایی جدید، داخل تگ اصلی است.
از آنجائیکه ذکر این تگها اختیاری است، نیاز است در ChildComponent.razor بر اساس null بودن آنها، تصمیم به رندر محتوایی پیشفرض گرفت:
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-08.zip
<Comp1>This is a content coming from the parent</Comp1>
معرفی مفهوم Render Fragment
برای درج محتوای تامین شدهی توسط کامپوننت والد در یک کامپوننت فرزند، از ویژگی به نام Render Fragment استفاده میشود. مثالی جهت توضیح جزئیات آن:
در ابتدا یک کامپوننت والد جدید را در مسیر Pages\LearnBlazor\ParentComponent.razor به صورت زیر تعریف میکنیم:
@page "/ParentComponent" <h1 class="text-danger">Parent Child Component</h1> <ChildComponent Title="This title is passed as a parameter from the Parent Component"> A `Render Fragment` from the parent! </ChildComponent> <ChildComponent Title="This is the second child component"></ChildComponent> @code { }
- سپس دوبار کامپوننت فرضی ChildComponent به همراه پارامتر Title و یک محتوای جدید قرار گرفتهی در بین تگهای آن، در صفحه تعریف شدهاند.
- بار دومی که ChildComponent در صفحه قرار گرفتهاست، به همراه محتوای جدیدی در بین تگهای خود نیست.
برای دسترسی به این کامپوننت از طریق منوی برنامه، مدخل منوی آنرا به کامپوننت Shared\NavMenu.razor اضافه میکنیم:
<li class="nav-item px-3"> <NavLink class="nav-link" href="ParentComponent"> <span class="oi oi-list-rich" aria-hidden="true"></span> Parent/Child Relation </NavLink> </li>
<div> <div class="alert alert-info">@Title</div> <div class="alert alert-success"> @if (ChildContent == null) { <span> Hello, from Empty Render Fragment </span> } else { <span>@ChildContent</span> } </div> </div> @code { [Parameter] public string Title { get; set; } [Parameter] public RenderFragment ChildContent { get; set; } }
- خاصیت عمومی Title که توسط ویژگی Parameter مزین شدهاست، امکان تنظیم مقدار مشخصی را توسط کامپوننت دربرگیرندهی ChildComponent میسر میکند.
- در اینجا پارامتر عمومی دیگری نیز تعریف شدهاست که اینبار از نوع ویژهی RenderFragment است. توسط آن میتوان به محتوایی که در کامپوننت والد ChildComponent در بین تگهای آن تنظیم شدهاست، دسترسی یافت. همچنین اگر این محتوا توسط کامپوننت والد تنظیم نشده باشد، مانند دومین باری که ChildComponent در صفحه قرار گرفتهاست، میتوان با بررسی نال بودن آن، یک محتوای پیشفرض را نمایش داد.
با این خروجی:
روش دیگری برای فراخوانی Event Call Back ها
در قسمت قبل روش انتقال اطلاعات را از کامپوننتهای فرزند، به والد مشاهده کردیم. فراخوانی آنها در سمت Child Component نیاز به یک متد اضافی داشت و همچنین تنها یک پارامتر را هم ارسال کردیم. برای ساده سازی این عملیات از روش زیر نیز میتوان استفاده کرد:
<button class="btn btn-danger" @onclick="@(() => OnClickBtnMethod.InvokeAsync((1, "A message from child!")))"> Show a message from the child! </button> @code { // ... [Parameter] public EventCallback<(int, string)> OnClickBtnMethod { get; set; } }
- همچنین فراخوانی OnClickBtnMethod.InvokeAsync را نیز در محل تعریف onclick@ بدون نیازی به یک متد اضافی، مشاهده میکنید. نکتهی مهم آن، قرار دادن این قطعه کد داخل ()@ است تا ابتدا و انتهای کدهای #C مشخص شود؛ وگرنه کامپایل نمیشود.
در سمت کامپوننت والد برای دسترسی به OnClickBtnMethod که اینبار یک tuple را ارسال میکند، میتوان به صورت زیر عمل کرد:
@page "/ParentComponent" <h1 class="text-danger">Parent Child Component</h1> <ChildComponent OnClickBtnMethod="ShowMessage" Title="This title is passed as a parameter from the Parent Component"> A `Render Fragment` from the parent! </ChildComponent> <ChildComponent Title="This is the second child component"> <p><b>@MessageText</b></p> </ChildComponent> @code { string MessageText = ""; private void ShowMessage((int Value, string Message) args) { MessageText = args.Message; } }
امکان تعریف چندین RenderFragment
تا اینجا یک RenderFragment را در کامپوننت فرزند تعریف کردیم. امکان تعریف چندین RenderFragment در ChildComponent.razor نیز وجود دارند:
@code { // ... [Parameter] public RenderFragment ChildContent { get; set; } [Parameter] public RenderFragment DangerChildContent { get; set; } }
<ChildComponent OnClickBtnMethod="ShowMessage" Title="This title is passed as a parameter from the Parent Component"> <ChildContent> A `Render Fragment` from the parent! </ChildContent> <DangerChildContent> A danger content from the parent! </DangerChildContent> </ChildComponent>
از آنجائیکه ذکر این تگها اختیاری است، نیاز است در ChildComponent.razor بر اساس null بودن آنها، تصمیم به رندر محتوایی پیشفرض گرفت:
@if(DangerChildContent == null) { @if (ChildContent == null) { <span> Hello, from Empty Render Fragment </span> } else { <span>@ChildContent</span> } } else { <span>@DangerChildContent</span> }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-08.zip
اشتراکها
اهداف Visual Studio برای سال 2019
اشتراکها
کتابخانه patternLock
A light weight plugin to simulate android like pattern lock interface for your hybrid app or website. Demo
اشتراکها
معرفی تعدادی React UI Libraries
اشتراکها
دورههای رایگان فراگیری Data Science
اشتراکها