گاهی از اوقات نیاز است به همراه مسیریابی، اطلاعاتی را نیز به آنها ارسال کنیم. برای مثال در حین نمایش لیست محصولات، برای هدایت به صفحهی نمایش جزئیات هر محصول، نیاز است Id هر محصول نیز به همراه مسیریابی، به کامپوننت مقصد ارسال شود. اینکار توسط route parameters قابل مدیریت است.
تنظیم مسیریابیها جهت درج پارامترها
پیش از ارسال اطلاعات مورد نیاز، به مسیری خاص، نیاز است محل قرارگیری این اطلاعات را در تعاریف مسیریابیها مشخص کرد.
در ادامهی مثال این سری، دو کامپوننت جدید جزئیات و ویرایش محصولات را به ماژول محصولات اضافه میکنیم:
این دستورات سبب به روز رسانی خودکار قسمت declarations فایل src\app\product\product.module.ts نیز خواهند شد.
در ادامه با مراجعه به فایل src\app\product\product-routing.module.ts، تنظیمات مسیریابی آنرا به شکل ذیل تکمیل خواهیم کرد:
اولین شیء مسیریابی تعریف شده را در قسمتهای پیشین بررسی کردیم که امکان نمایش کامپوننت لیست محصولات را توسط یک routerLink در منوی سایت میسر میکند.
دومین شیء مسیریابی، از مسیر ریشهی یکسانی استفاده میکند (products) که علت آنرا در قسمت قبل با «انتخاب استراتژی مناسب نامگذاری مسیرها» بررسی کردیم. در اینجا id: مکانی را مشخص میکند که قرار است اطلاعاتی را به آن مسیر خاص ارسال کند. در اینجا : به معنای تعریف مکان یک پارامتر اجباری مسیریابی است. به علاوه id یک نام دلخواه است و چون در مثال جاری میخواهیم id محصولات را انتقال دهیم، Id نامگرفتهاست؛ وگرنه هر نام دیگری نیز میتواند باشد.
سومین شیء مسیریابی نیز از مسیر ریشهی یکسانی استفاده میکند و تفاوت آنرا با حالت نمایش جزئیات، با افزودن یک edit/ مشخص کردهایم.
در اینجا هر تعداد متغیر مورد نیاز، قابل تعریف است. برای مثال مسیری مانند orders/:id/items/:itemId با دو متغیر و یا بیشتر نیز قابل تعریف است. فقط باید دقت داشت که نامهای پس از : در یک مسیریابی، باید منحصربفرد باشند.
تکمیل کامپوننت نمایش لیست محصولات
پیش از ادامهی بحث نیاز است کامپوننت خالی لیست محصولات را که در قسمتهای قبل ایجاد کردیم، تکمیل کنیم تا پس از آن بتوانیم لینکهایی را به صفحات نمایش جزئیات محصولات و همچنین ویرایش و افزودن محصولات نیز اضافه کنیم. به همین جهت ابتدا اینترفیس محصول را اضافه میکنیم:
و آنرا به نحو ذیل تکمیل خواهیم کرد:
تشکیل یک منبع اطلاعات درون حافظهای
یکی از بستههای Angular به نام angular-in-memory-web-api، قابلیت تشکیل یک REST Web API ساده را دارد که از آن جهت دریافت، ثبت و به روز رسانی لیست محصولات استفاده خواهیم کرد (بدون نیاز به نوشتن کد سمت سرور خاصی؛ صرفا در جهت ساده سازی ارائهی مطلب).
به همین جهت ابتدا بستهی مرتبط با آنرا نصب کنید:
ذکر پارامتر save در اینجا، سبب به روز رسانی فایل package.json نیز خواهد شد:
سپس کلاس ProductData را به ماژول محصولات اضافه میکنیم:
این کلاس را در ادامه به صورت ذیل تکمیل خواهیم کرد:
همانطور که ملاحظه میکنید، کلاسی که قرار است به عنوان منبع دادهی بستهی angular-in-memory-web-api بکار رود باید InMemoryDbService, InMemoryBackendConfig را نیز پیاده سازی کند که نمونهای از آنرا در اینجا برای بازگشت یک لیست درون حافظهای محصولات، مشاهده میکنید.
در آخر برای فعالسازی این REST Web API ساده، تنها کافی است به فایل src\app\app.module.ts مراجعه کرده و کلاس ProductData فوق را معرفی کنیم:
- ابتدا مکان یافت شدن ماژولهای مورد نیاز ProductData و InMemoryWebApiModule تعریف شدهاند و سپس InMemoryWebApiModule.forRoot را جهت تشکیل یک Web API آزمایشی، برای ارائهی اطلاعات کلاس ProductData، به لیست imports اضافه کردهایم. باید دقت داشت که نیاز است تعریف InMemoryWebApiModule پس از تعریف HttpModule باشد تا بتواند تعدادی از پیش فرضهای آن را بازنویسی کند.
- در اینجا یک delay را هم در تنظیمات آن مشاهده میکنید. هدف از تعریف آن، شبیه سازی کند بودن دریافت اطلاعات از یک وب سرور واقعی است.
- این وب سرویس در آدرس api/products قابل دسترسی است.
تعریف سرویس HTTP کار با منبع اطلاعات درون حافظهای
پس از تعریف REST Web API درون حافظهای، یک سرویس HTTP را نیز جهت کار با آن، به برنامه اضافه خواهیم کرد:
که سبب افزوده شدن سرویس product.service.ts و همچنین به روز رسانی خودکار قسمت providers ماژول product.module.ts نیز میشود:
اگر نام ماژول را ذکر نکنیم، سرویس مدنظر تولید خواهد شد، اما قسمت providers هیچ ماژولی به صورت خودکار تکمیل نمیشود.
پس از ایجاد قالب ابتدایی فایل product.service.ts، آنرا به نحو ذیل تکمیل کنید:
این سرویس HTTP، به سرویس Web API آزمایشی واقع در آدرس baseUrl، متصل خواهد شد:
از آن برای دریافت لیست محصولات (getProducts)، دریافت جزئیات یک محصول (getProduct)، حذف یک محصول (deleteProduct)، ثبت و یا به روز رسانی یک محصول (saveProduct) استفاده خواهیم کرد.
نمایش لیست محصولات
اکنون پس از این مقدمات میتوانیم کامپوننت لیست محصولات را تکمیل کنیم. به همین جهت به قالب ابتدایی src\app\product\product-list\product-list.component.ts مراجعه کرده و آنرا به نحو ذیل تکمیل کنید:
در اینجا با استفاده از سرویس محصولاتی که پیشتر ایجاد کردیم، کار دریافت لیست محصولات انجام شده و سپس به خاصیت عمومی products نسبت داده میشود. این خاصیت را در قالب این کامپوننت نمایش خواهیم داد. به همین جهت فایل src\app\product\product-list\product-list.component.html را گشوده و آنرا به نحو ذیل تکمیل کنید:
اکنون اگر برنامه را توسط دستور ng serve -o ساخته و اجرا کنید، چنین صفحهای قابل مشاهده خواهد بود:
توضیحات:
پس از تعریف مسیریابیهای صفحات نمایش لیست محصولات و ویرایش محصولات، اکنون نوبت به اتصال آنها به لینکهایی است تا توسط کاربران برنامه مورد استفاده قرار گیرند.
در اینجا با تعریف لینکی، هر محصول را به صفحهی مشاهدهی جزئیات آن متصل کردهایم:
برای مشاهدهی جزئیات هر محصول نیاز است Id آن محصول را به عنوان پارامتر مسیریابی ارسال کنیم. به همین جهت این Id را به عنوان پارامتری جدید، به routerLink انتساب دادهایم.
و یا برای حالت edit نیز به همین صورت 'path: 'products/:id/edit را مقدار دهی کردهایم.
در اینجا ابتدا root URL segment ذکر میشود. سپس پارامترهای متغیر مسیریابی و همچنین ثوابت آن مسیر خاص نیز باید ذکر شوند. اگر URL segment ثابت edit، در انتها ذکر نشود، این مسیر با تنظیم 'path: 'products/:id تطابق داده خواهد شد و نه با حالت 'path: 'products/:id/edit.
به علاوه به فایل src\app\app.component.html نیز مراجعه کرده و لینکی را ذیل لینک لیست محصولات در منوی سایت، جهت افزودن یک محصول جدید اضافه میکنیم:
در اینجا عدد صفر را به عنوان پارامتر یا Id محصول جدید، به همان صفحهی ویرایش اطلاعات یک محصول، ارسال کردهایم. اگر به سرویس محصولات دقت کنید،
اگر id مساوی صفر باشد، یک محصول جدید ایجاد خواهد شد و اگر غیر صفر باشد، این محصول از پیش موجود، به روز رسانی میگردد.
همچنین باید دقت داشت که تمام پارامترهای routerLink را با کدنویسی و در متد navigate نیز میتوان بکار برد. برای مثال:
خواندن و پردازش پارامترهای مسیریابی
تا اینجا لیست محصولات را نمایش دادیم و همچنین لینکهایی را به صفحات نمایش جزئیات، ویرایش و افزودن این محصولات، تعریف کردیم. مرحلهی بعد، پیاده سازی این کامپوننتها است.
مسیریاب Angular، پارامترهای هر مسیر را توسط سرویس ActivatedRoute استخراج کرده و در اختیار کامپوننتها قرار میدهد. بنابراین برای دسترسی به آنها تنها کافی است این سرویس را به سازندهی کلاس کامپوننت خود تزریق کنیم. پس از آن دو روش برای خواندن اطلاعات مسیرجاری توسط این سرویس فراهم میشود:
الف) استفاده از شیء this.route.snapshot که وضعیت آغازین مسیریابی را به همراه دارد. برای مثال جهت دسترسی به مقدار پارامتر id میتوان به صورت ذیل عمل کرد:
بنابراین ابتدا یک مسیریابی به همراه پارامتر یا پارامترهایی متغیر تعریف میشود:
سپس این مسیریابی توسط لینک ذیل فعال میشود:
اکنون برای دریافت مقدار این پارامتر از URL جاری، میتوان از this.route.snapshot.params['id'] استفاده کرد. این id دقیقا نام همان متغیری است که در تعریف مسیریابی ذکر شدهاست و حساس به کوچکی و بزرگی حروف میباشد.
ب) این سرویس ویژه به همراه خاصیت this.route.params که یک Observable است نیز میباشد:
هر زمان که پارامترهای مسیریابی تغییر کنند، این Observable به آنها گوش فرا داده و برنامه را از این تغییرات مطلع میسازد.
یک نکته: ذکر علامت + در اینجا (params['id']+) سبب تبدیل رشتهی دریافتی، به عدد میگردد.
تکمیل کامپوننت نمایش جزئیات یک محصول
در ادامه قالب ابتدایی مشاهدهی جزئیات یک محصول را که در فایل src\app\product\product-detail\product-detail.component.ts قرار دارد، گشوده و به نحو ذیل تکمیل کنید:
در این حالت اگر آدرس http://localhost:4200/products/1 توسط کاربر درخواست شود، نیاز است بتوان id=1 آنرا از مسیرجاری استخراج کرد. به همین جهت سرویس ActivatedRoute به سازندهی کلاس کامپوننت جزئیات محصول تزریق شدهاست. هرچند میتوان از این سرویس در همان سازندهی کلاس نیز استفاده کرد، اما انجام اعمال async آغازین یک کامپوننت بهتر است به ngOnInit منتقل شوند تا سبب تاخیری در آغاز و نمایش کامپوننت نگردند. این life cycle hook، پس از آغاز کامپوننت فراخوانی میگردد. به همین جهت ذکر implements OnInit را در قسمت تعریف کلاس مشاهده میکنید.
در متد OnInit، مقدار id، از snapshot دریافت میگردد. سپس این id به متد getProduct ارسال میشود تا از RES Web API سرویس برنامه، جزئیات این محصول را واکشی کند و به خاصیت product نسبت دهد.
برای تکمیل قالب این کامپوننت نیز فایل src\app\product\product-detail\product-detail.component.html را گشوده و به نحو ذیل تغییر دهید تا در آن بتوان از خاصیت عمومی product استفاده کرد:
در اینجا علاوه بر استفاده از شیء product در جهت نمایش جزئیات محصول انتخابی، دو دکمهی back و edit نیز اضافه شدهاند که اولی صفحهی لیست محصولات را مجددا نمایش میدهد و دومی کار هدایت به صفحهی ویرایش جزئیات این محصول را میسر میکند.
تکمیل کامپوننت ویرایش و افزودن جزئیات یک محصول
از آنجائیکه قصد داریم به ماژول محصولات فرم جدیدی را اضافه کنیم، نیاز است به فایل src\app\product\product.module.ts مراجعه کرده و FormsModule را به قسمت imports آن اضافه کنیم:
کد کامل کامپوننت ویرایش و افزودن جزئیات یک محصول به شرح ذیل است (فایل src\app\product\product-edit\product-edit.component.ts):
به همراه کد کامل قالب آن (فایل src\app\product\product-edit\product-edit.component.html):
توضیحات:
نکتهی مهمی را که در این کدها میخواهیم بررسی کنیم، متد ngOnInit آناست:
برنامه را یکبار توسط دستور ng server -o ساخته و اجرا کنید.
- ابتدا لیست محصولات را مشاهده کنید.
- سپس بر روی دکمهی edit محصول شماره یک کلیک نمائید:
تصویر فوق حاصل خواهد شد که صحیح است. اطلاعات مربوط به محصول یک از وب سرور آزمایشی برنامه واکشی شده و به شیء product نسبت داده شدهاست. همین انتساب سبب مقدار دهی فیلدهای فرم ویرایش اطلاعات گردیدهاست.
- در ادامه بر روی لینک Add product در منوی بالای صفحه کلیک کنید:
همانطور که مشاهده میکنید، هرچند URL صفحه تغییر کردهاست، اما هنوز فرم ویرایش اطلاعات، به روز نشده و فیلدهای آن جهت درج یک محصول جدید خالی نشدهاند. علت اینجا است که در متد ngOnInit، مقدار پارامتر id را از طریق شیء route.snapshot دریافت کردهایم. اگر تنها پارامترهای URL تغییر کنند، کامپوننت مجددا آغاز نشده و متد ngOnInit فراخوانی نمیشود. در اینجا تغییر آدرس http://localhost:4200/products/1/edit به http://localhost:4200/products/0/edit به علت عدم تغییر root URL segment آن یا همان products، سبب فراخوانی مجدد ngOnInit نمیشود. به همین جهت است که فیلدهای این فرم تغییر نکردهاند.
برای حل این مشکل بجای دریافت پارامترهای مسیریابی از طریق شیء route.snapshot بهتر است به تغییرات آنها گوش فرا دهیم. اینکار را از طریق متد route.params.subscribe میتوان انجام داد:
در اینجا چون کامپوننت به علت نحوهی تعریف مسیریابی آن مجددا آغاز نمیشود، شیء route.snapshot برای دسترسی به پارامترهای تغییر کردهی مسیریابی، کارآیی لازم را نداشته و باید از روش دوم دسترسی به آن مقادیر که یک Observable است و به تغییرات پارامترها گوش فرا میدهد، استفاده کرد.
یک نکته: هر زمانیکه از Observableها استفاده میشود، نیاز است در پایان کار کامپوننت، unsubscribe آن نیز فراخوانی شود تا نشتی حافظه رخ ندهد. در اینجا یک سری استثناء هم وجود دارند، مانند this.route.params که به صورت خودکار توسط طول عمر سرویس ActivatedRoute مدیریت میشود.
روش خواندن پارامترهای مسیریابی در +Angular 4
روشی که تا به اینجا در مورد خواندن پارامترهای مسیریابی ذکر شد، با Angular 4 هم سازگار است. در Angular 4 روش دیگری را نیز اضافه کردهاند که توسط شیء paramMap مدیریت میشود:
در اینجا دو روش دسترسی به پارامتر id را مشاهده میکنید. در حالت کار با snapshot متد paramMap.get اینبار یک رشته را قبول میکند و یا بجای params میتوان از paramMap استفاده کرد.
تعریف پارامترهای اختیاری مسیریابی
فرض کنید یک صفحهی جستجو را طراحی کردهاید که در آن میتوان نام و کد محصول را جستجو کرد. سپس میخواهیم این پارامترها را به صفحهی نمایش لیست محصولات هدایت کنیم. برای طراحی این نوع مسیریابی میتوان از مطالبی که تاکنون گفته شد استفاده کرد و پارامترهایی اجباری را جهت مسیریابی تعریف نمود:
و سپس میتوان یک چنین لینکی را جهت فعالسازی آن اضافه کرد:
این روش به همراه URLهایی ناخوانا خواهد بود که قسمتهای مختلف آن مشخص نیستند و هر بار که قرار باشد گزینهی دیگری را به جستجو اضافه کرد، نیاز است این پارامترها را نیز تغییر داد. همچنین در حین جستجو ممکن است تعدادی از فیلدها اختیاری باشند و نه اجباری. برای حل این مشکل امکان تعریف پارامترهای اختیاری مسیریابی نیز پیش بینی شدهاست. دراین حالت تعریف مسیریابی صفحهی نمایش لیست محصولات به صورت ذیل خواهد بود (بدون ذکر هیچ پارامتری):
و سپس لینکی که به آن تعریف میشود، نحوهی تعریف خاصی را خواهد داشت:
در اینجا پارامترهای اختیاری به صورت یک سری key/value در آرایهی پارامترهای مسیریابی مشخص میشوند و هربار که نیاز به تغییر آنها بود، نیازی نیست تا تعریف مسیریابی اصلی مرتبط را تغییر داد. باید دقت داشت که پارامترهای اختیاری باید همواره پس از پارامترهای اجباری در این آرایه، ذکر شوند.
در این حالت لینک تولید شده چنین شکلی را خواهد داشت:
نحوهی خواندن این پارامترها، دقیقا همانند نحوهی خواندن پارامترهای اجباری هستند و در اینجا از نام keyها برای اشارهی به آنها استفاده میشود:
این پارامترها پس از هدایت کاربر به مسیری دیگر، حفظ نشده و پاک خواهند شد. به همین جهت کلیدهای تعریف شدهی در اینجا ضرورتی ندارد که یکتا بوده و با سایر قسمتهای برنامه تداخلی نداشته باشند.
تعریف پارامترهای کوئری در مسیریابی
فرض کنید لیست محصولات را بر اساس پارامتر یا پارامترهایی فیلتر کردهاید. اکنون اگر بر روی لینک مشاهدهی جزئیات یک محصول یافت شده کلیک کنید و سپس مجددا به لیست فیلتر شده بازگردید، تمام پارامترهای انتخابی پاک شده و لیست از ابتدا نمایش داده میشود. در یک چنین حالتی برای بهبود تجربهی کاربری، بهتر است پارامترهای جستجو شده را در طی هدایت کاربر به قسمتهای مختلف حفظ کرد:
در این لینک جزئیات محصول 5 نمایش داده خواهد شد. پس از عدد 5، پارامترهای کوئری ذکر شدهاند و برخلاف پارامترهای اختیاری مسیریابی، در بین مسیریابی و هدایت کاربران به صفحات مختلف، حفظ خواهند شد.
در اینجا نیز برای تعریف یک چنین قابلیتی، مسیریابی ابتدایی تعریف شده، همانند قبل خواهد بود و در آن خبری از پارامترهای کوئری نیست:
اعمال پارامترهای کوئری مختلف، به لینکهای تعریف شده، توسط دایرکتیو queryParams صورت میگیرد و در اینجا یک مجموعهی از key/valueها ذکر خواهند شد:
در این مثال یک ثابت دلخواه er مشخص شدهاست. بدیهی است میتوان متغیری را نیز بجای این ثابت تعریف کرد (یک خاصیت عمومی تعریف شدهی در سطح کامپوننت که به تکستباکس جستجو متصل است).
و یا با کدنویسی به صورت ذیل است:
باید دقت داشت که چون این پارامترهای کوئری در بین مسیریابی به صفحات مختلف حفظ میشوند، نباید کلیدهای انتخاب شدهی در اینجا با سایر کلیدهای موجود در صفحات دیگر تداخل پیدا کنند.
یک مشکل: اگر در صفحهی نمایش جزئیات یک محصول، دکمهی Back وجود داشته باشد، با کلیک بر روی آن پارامترهای کوئری پاک خواهند شد و دیگر حفظ نمیشوند. مرحلهی آخر حفظ پارامترهای کوئری در بین صفحات مختلف تنظیم ذیل است:
یعنی دکمهی back به این شکل تغییر میکند:
و یا استفاده از { preserveQueryParams: true} در حین کدنویسی.
که البته در Angular 4 مورد اول به "queryParamsHandling= "preserve و مورد دوم به { 'queryParamsHandling: 'preserve} تغییر یافتهاست و حالت قبلی منسوخ شده اعلام گردیدهاست.
پس از بازگشت به صفحهی اصلی لیست محصولات، هرچند این پارامترهای کوئری حفظ شدهاند، اما مجددا به صورت خودکار پردازش نخواهند شد. برای خواندن آنها در متد ngOnInit باید به صورت ذیل عمل کرد:
علت تعریف '' || این است که ممکن است filterBy تعریف نشده باشد (برای حالتی که صفحه برای بار اول نمایش داده میشود) و دلیل تعریف 'true' === این است که مقادیر دریافتی در اینجا رشتهای هستند و نه boolean. به همین جهت باید با رشتهی true مقایسه شوند.
در مثال تکمیل شدهی جاری، امکان فیلتر کردن جدول با اضافه کردن یک pipe جدید به نام ProductFilter میسر شدهاست:
فایل src\app\product\product-filter.pipe.ts با این محتوا:
و سپس تعریف تکست باکس فیلتر کردن در ابتدای قالب src\app\product\product-list\product-list.component.html :
و اعمال این فیلتر به حلقهی نمایش ردیفهای جدول؛ به همراه تعریف پارامتر کوئری:
در اینجا اگر به صفحهی جزئیات محصول فیلتر شده مراجعه کنیم، این فیلتر حفظ خواهد شد:
و در این حالت اگر بر روی دکمهی back کلیک کنیم، مجددا فیلتر وارد شده بازیابی شده و نمایش داده میشود:
که برای میسر شدن آن ابتدا خاصیت عمومی listFilter به کامپوننت لیست محصولات اضافه گردیده و سپس در ngOnInit، این پارامتر در صورت وجود، از سیستم مسیریابی دریافت میشود:
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-02.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
تنظیم مسیریابیها جهت درج پارامترها
پیش از ارسال اطلاعات مورد نیاز، به مسیری خاص، نیاز است محل قرارگیری این اطلاعات را در تعاریف مسیریابیها مشخص کرد.
در ادامهی مثال این سری، دو کامپوننت جدید جزئیات و ویرایش محصولات را به ماژول محصولات اضافه میکنیم:
>ng g c product/ProductDetail >ng g c product/ProductEdit
در ادامه با مراجعه به فایل src\app\product\product-routing.module.ts، تنظیمات مسیریابی آنرا به شکل ذیل تکمیل خواهیم کرد:
import { ProductEditComponent } from './product-edit/product-edit.component'; import { ProductDetailComponent } from './product-detail/product-detail.component'; import { ProductListComponent } from './product-list/product-list.component'; const routes: Routes = [ { path: 'products', component: ProductListComponent }, { path: 'products/:id', component: ProductDetailComponent }, { path: 'products/:id/edit', component: ProductEditComponent } ];
دومین شیء مسیریابی، از مسیر ریشهی یکسانی استفاده میکند (products) که علت آنرا در قسمت قبل با «انتخاب استراتژی مناسب نامگذاری مسیرها» بررسی کردیم. در اینجا id: مکانی را مشخص میکند که قرار است اطلاعاتی را به آن مسیر خاص ارسال کند. در اینجا : به معنای تعریف مکان یک پارامتر اجباری مسیریابی است. به علاوه id یک نام دلخواه است و چون در مثال جاری میخواهیم id محصولات را انتقال دهیم، Id نامگرفتهاست؛ وگرنه هر نام دیگری نیز میتواند باشد.
سومین شیء مسیریابی نیز از مسیر ریشهی یکسانی استفاده میکند و تفاوت آنرا با حالت نمایش جزئیات، با افزودن یک edit/ مشخص کردهایم.
در اینجا هر تعداد متغیر مورد نیاز، قابل تعریف است. برای مثال مسیری مانند orders/:id/items/:itemId با دو متغیر و یا بیشتر نیز قابل تعریف است. فقط باید دقت داشت که نامهای پس از : در یک مسیریابی، باید منحصربفرد باشند.
تکمیل کامپوننت نمایش لیست محصولات
پیش از ادامهی بحث نیاز است کامپوننت خالی لیست محصولات را که در قسمتهای قبل ایجاد کردیم، تکمیل کنیم تا پس از آن بتوانیم لینکهایی را به صفحات نمایش جزئیات محصولات و همچنین ویرایش و افزودن محصولات نیز اضافه کنیم. به همین جهت ابتدا اینترفیس محصول را اضافه میکنیم:
> ng g i product/IProduct
export interface IProduct { id: number; productName: string; productCode: string; }
تشکیل یک منبع اطلاعات درون حافظهای
یکی از بستههای Angular به نام angular-in-memory-web-api، قابلیت تشکیل یک REST Web API ساده را دارد که از آن جهت دریافت، ثبت و به روز رسانی لیست محصولات استفاده خواهیم کرد (بدون نیاز به نوشتن کد سمت سرور خاصی؛ صرفا در جهت ساده سازی ارائهی مطلب).
به همین جهت ابتدا بستهی مرتبط با آنرا نصب کنید:
>npm install angular-in-memory-web-api --save
"dependencies": { "angular-in-memory-web-api": "^0.3.1" },
سپس کلاس ProductData را به ماژول محصولات اضافه میکنیم:
> ng g cl product/ProductData
import { IProduct } from './iproduct'; import { InMemoryDbService, InMemoryBackendConfig } from 'angular-in-memory-web-api'; export class ProductData implements InMemoryDbService, InMemoryBackendConfig { createDb() { let products: IProduct[] = [ { 'id': 1, 'productName': 'Product 1', 'productCode': '0011' }, { 'id': 2, 'productName': 'Product 2', 'productCode': '0023' }, { 'id': 5, 'productName': 'Product 5', 'productCode': '0048' }, { 'id': 8, 'productName': 'Product 8', 'productCode': '0022' }, { 'id': 10, 'productName': 'Product 10', 'productCode': '0042' } ]; return { products }; } }
در آخر برای فعالسازی این REST Web API ساده، تنها کافی است به فایل src\app\app.module.ts مراجعه کرده و کلاس ProductData فوق را معرفی کنیم:
import { ProductData } from './product/product-data'; import { InMemoryWebApiModule } from 'angular-in-memory-web-api'; @NgModule({ declarations: [ ], imports: [ BrowserModule, FormsModule, HttpModule, InMemoryWebApiModule.forRoot(ProductData, { delay: 1000 }), ProductModule, UserModule, AppRoutingModule ],
- در اینجا یک delay را هم در تنظیمات آن مشاهده میکنید. هدف از تعریف آن، شبیه سازی کند بودن دریافت اطلاعات از یک وب سرور واقعی است.
- این وب سرویس در آدرس api/products قابل دسترسی است.
تعریف سرویس HTTP کار با منبع اطلاعات درون حافظهای
پس از تعریف REST Web API درون حافظهای، یک سرویس HTTP را نیز جهت کار با آن، به برنامه اضافه خواهیم کرد:
>ng g s product/product -m product/product.module
installing service create src\app\product\product.service.spec.ts create src\app\product\product.service.ts update src\app\product\product.module.ts
پس از ایجاد قالب ابتدایی فایل product.service.ts، آنرا به نحو ذیل تکمیل کنید:
import { Injectable } from '@angular/core'; import { Http, Response, Headers, RequestOptions } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/throw'; import 'rxjs/add/operator/map'; import 'rxjs/add/observable/of'; import { IProduct } from './iproduct'; @Injectable() export class ProductService { private baseUrl = 'api/products'; constructor(private http: Http) { } getProducts(): Observable<IProduct[]> { return this.http.get(this.baseUrl) .map(this.extractData) .do(data => console.log('getProducts: ' + JSON.stringify(data))) .catch(this.handleError); } getProduct(id: number): Observable<IProduct> { if (id === 0) { return Observable.of(this.initializeProduct()); }; const url = `${this.baseUrl}/${id}`; return this.http.get(url) .map(this.extractData) .do(data => console.log('getProduct: ' + JSON.stringify(data))) .catch(this.handleError); } deleteProduct(id: number): Observable<Response> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); const url = `${this.baseUrl}/${id}`; return this.http.delete(url, options) .do(data => console.log('deleteProduct: ' + JSON.stringify(data))) .catch(this.handleError); } saveProduct(product: IProduct): Observable<IProduct> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); if (product.id === 0) { return this.createProduct(product, options); } return this.updateProduct(product, options); } private createProduct(product: IProduct, options: RequestOptions): Observable<IProduct> { product.id = undefined; return this.http.post(this.baseUrl, product, options) .map(this.extractData) .do(data => console.log('createProduct: ' + JSON.stringify(data))) .catch(this.handleError); } private updateProduct(product: IProduct, options: RequestOptions): Observable<IProduct> { const url = `${this.baseUrl}/${product.id}`; return this.http.put(url, product, options) .map(() => product) .do(data => console.log('updateProduct: ' + JSON.stringify(data))) .catch(this.handleError); } private extractData(response: Response) { let body = response.json(); return body.data || {}; } private handleError(error: Response): Observable<any> { console.error(error); return Observable.throw(error.json().error || 'Server error'); } initializeProduct(): IProduct { // Return an initialized object return { id: 0, productName: null, productCode: null }; } }
private baseUrl = 'api/products';
نمایش لیست محصولات
اکنون پس از این مقدمات میتوانیم کامپوننت لیست محصولات را تکمیل کنیم. به همین جهت به قالب ابتدایی src\app\product\product-list\product-list.component.ts مراجعه کرده و آنرا به نحو ذیل تکمیل کنید:
import { ActivatedRoute } from '@angular/router'; import { Component, OnInit } from '@angular/core'; import { ProductService } from './../product.service'; import { IProduct } from './../iproduct'; @Component({ selector: 'app-product-list', templateUrl: './product-list.component.html', styleUrls: ['./product-list.component.css'] }) export class ProductListComponent implements OnInit { pageTitle = 'Product List'; errorMessage: string; products: IProduct[]; constructor(private productService: ProductService, private route: ActivatedRoute) { } ngOnInit(): void { this.productService.getProducts() .subscribe(products => this.products = products, error => this.errorMessage = <any>error); } }
<div class="panel panel-default"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body"> <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div> <div class="table-responsive"> <table class="table" *ngIf="products && products.length"> <thead> <tr> <th>Product</th> <th>Code</th> </tr> </thead> <tbody> <tr *ngFor="let product of products"> <td><a [routerLink]="['/products', product.id]"> {{product.productName}} </a> </td> <td>{{ product.productCode}}</td> <td> <a class="btn btn-primary" [routerLink]="['/products', product.id, 'edit']"> Edit </a> </td> </tr> </tbody> </table> </div> </div> </div>
توضیحات:
پس از تعریف مسیریابیهای صفحات نمایش لیست محصولات و ویرایش محصولات، اکنون نوبت به اتصال آنها به لینکهایی است تا توسط کاربران برنامه مورد استفاده قرار گیرند.
در اینجا با تعریف لینکی، هر محصول را به صفحهی مشاهدهی جزئیات آن متصل کردهایم:
<a [routerLink]="['/products', product.id]"> {{product.productName}} </a>
و یا برای حالت edit نیز به همین صورت 'path: 'products/:id/edit را مقدار دهی کردهایم.
<a class="btn btn-primary" [routerLink]="['/products', product.id, 'edit']"> Edit </a>
به علاوه به فایل src\app\app.component.html نیز مراجعه کرده و لینکی را ذیل لینک لیست محصولات در منوی سایت، جهت افزودن یک محصول جدید اضافه میکنیم:
<li> <a [routerLink]="['/products', 0, 'edit']">Add Product</a> </li>
saveProduct(product: IProduct): Observable<IProduct> { let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ headers: headers }); if (product.id === 0) { return this.createProduct(product, options); } return this.updateProduct(product, options); }
همچنین باید دقت داشت که تمام پارامترهای routerLink را با کدنویسی و در متد navigate نیز میتوان بکار برد. برای مثال:
this.router.navigate(['products', this.product.id]);
خواندن و پردازش پارامترهای مسیریابی
تا اینجا لیست محصولات را نمایش دادیم و همچنین لینکهایی را به صفحات نمایش جزئیات، ویرایش و افزودن این محصولات، تعریف کردیم. مرحلهی بعد، پیاده سازی این کامپوننتها است.
مسیریاب Angular، پارامترهای هر مسیر را توسط سرویس ActivatedRoute استخراج کرده و در اختیار کامپوننتها قرار میدهد. بنابراین برای دسترسی به آنها تنها کافی است این سرویس را به سازندهی کلاس کامپوننت خود تزریق کنیم. پس از آن دو روش برای خواندن اطلاعات مسیرجاری توسط این سرویس فراهم میشود:
الف) استفاده از شیء this.route.snapshot که وضعیت آغازین مسیریابی را به همراه دارد. برای مثال جهت دسترسی به مقدار پارامتر id میتوان به صورت ذیل عمل کرد:
let id = +this.route.snapshot.params['id'];
بنابراین ابتدا یک مسیریابی به همراه پارامتر یا پارامترهایی متغیر تعریف میشود:
{ path: 'products/:id', component: ProductDetailComponent }
<a [routerLink]="['/products', product.id]">{{product.productName}}</a>
ب) این سرویس ویژه به همراه خاصیت this.route.params که یک Observable است نیز میباشد:
this.route.params.subscribe( params => { let id = +params['id']; this.getProduct(id); } );
یک نکته: ذکر علامت + در اینجا (params['id']+) سبب تبدیل رشتهی دریافتی، به عدد میگردد.
تکمیل کامپوننت نمایش جزئیات یک محصول
در ادامه قالب ابتدایی مشاهدهی جزئیات یک محصول را که در فایل src\app\product\product-detail\product-detail.component.ts قرار دارد، گشوده و به نحو ذیل تکمیل کنید:
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { ProductService } from './../product.service'; import { IProduct } from './../iproduct'; @Component({ selector: 'app-product-detail', templateUrl: './product-detail.component.html', styleUrls: ['./product-detail.component.css'] }) export class ProductDetailComponent implements OnInit { pageTitle = 'Product Detail'; product: IProduct; errorMessage: string; constructor(private productService: ProductService, private route: ActivatedRoute) { } ngOnInit(): void { let id = +this.route.snapshot.params['id']; this.getProduct(id); } getProduct(id: number) { this.productService.getProduct(id).subscribe( product => this.product = product, error => this.errorMessage = <any>error); } }
در متد OnInit، مقدار id، از snapshot دریافت میگردد. سپس این id به متد getProduct ارسال میشود تا از RES Web API سرویس برنامه، جزئیات این محصول را واکشی کند و به خاصیت product نسبت دهد.
برای تکمیل قالب این کامپوننت نیز فایل src\app\product\product-detail\product-detail.component.html را گشوده و به نحو ذیل تغییر دهید تا در آن بتوان از خاصیت عمومی product استفاده کرد:
<div class="panel panel-primary" *ngIf="product"> <div class="panel-heading" style="font-size:large"> {{pageTitle + ": " + product.productName}} </div> <div class="panel-body"> <div> Name: {{product.productName}} </div> <div> Code: {{product.productCode}} </div> <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div> </div> <div class="panel-footer"> <a class="btn btn-default" [routerLink]="['/products']"> <i class="glyphicon glyphicon-chevron-left"></i> Back </a> <a class="btn btn-primary" style="width:80px;margin-left:10px" [routerLink]="['/products', product.id, 'edit']"> Edit </a> </div> </div>
تکمیل کامپوننت ویرایش و افزودن جزئیات یک محصول
از آنجائیکه قصد داریم به ماژول محصولات فرم جدیدی را اضافه کنیم، نیاز است به فایل src\app\product\product.module.ts مراجعه کرده و FormsModule را به قسمت imports آن اضافه کنیم:
import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ CommonModule, FormsModule, ProductRoutingModule ]
import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { ProductService } from './../product.service'; import { IProduct } from './../iproduct'; @Component({ selector: 'app-product-edit', templateUrl: './product-edit.component.html', styleUrls: ['./product-edit.component.css'] }) export class ProductEditComponent implements OnInit { pageTitle = 'Product Edit'; product: IProduct; errorMessage: string; constructor(private productService: ProductService, private route: ActivatedRoute, private router: Router) { } ngOnInit(): void { let id = +this.route.snapshot.params['id']; this.getProduct(id); } getProduct(id: number): void { this.productService.getProduct(id) .subscribe( (product: IProduct) => this.onProductRetrieved(product), (error: any) => this.errorMessage = <any>error ); } onProductRetrieved(product: IProduct): void { this.product = product; if (this.product.id === 0) { this.pageTitle = 'Add Product'; } else { this.pageTitle = `Edit Product: ${this.product.productName}`; } } deleteProduct(): void { if (this.product.id === 0) { // Don't delete, it was never saved. this.onSaveComplete(); } else { if (confirm(`Really delete the product: ${this.product.productName}?`)) { this.productService.deleteProduct(this.product.id) .subscribe( () => this.onSaveComplete(`${this.product.productName} was deleted`), (error: any) => this.errorMessage = <any>error ); } } } saveProduct(): void { if (true === true) { this.productService.saveProduct(this.product) .subscribe( () => this.onSaveComplete(`${this.product.productName} was saved`), (error: any) => this.errorMessage = <any>error ); } else { this.errorMessage = 'Please correct the validation errors.'; } } onSaveComplete(message?: string): void { if (message) { // TODO: show msg } // Navigate back to the product list this.router.navigate(['/products']); } }
<div class="panel panel-primary"> <div class="panel-heading"> {{pageTitle}} </div> <div class="panel-body" *ngIf="product"> <form class="form-horizontal" novalidate (ngSubmit)="saveProduct()" #productForm="ngForm"> <fieldset> <div class="form-group" [ngClass]="{'has-error': (productNameVar.touched || productNameVar.dirty) && !productNameVar.valid }"> <label class="col-md-2 control-label" for="productNameId">Product Name</label> <div class="col-md-8"> <input class="form-control" id="productNameId" type="text" placeholder="Name (required)" required minlength="3" [(ngModel)]=product.productName name="productName" #productNameVar="ngModel" /> <span class="help-block" *ngIf="(productNameVar.touched || productNameVar.dirty) && productNameVar.errors"> <span *ngIf="productNameVar.errors.required"> Product name is required. </span> <span *ngIf="productNameVar.errors.minlength"> Product name must be at least three characters. </span> </span> </div> </div> <div class="form-group" [ngClass]="{'has-error': (productCodeVar.touched || productCodeVar.dirty) && !productCodeVar.valid }"> <label class="col-md-2 control-label" for="productCodeId">Product Code</label> <div class="col-md-8"> <input class="form-control" id="productCodeId" type="text" placeholder="Code (required)" required [(ngModel)]=product.productCode name="productCode" #productCodeVar="ngModel" /> <span class="help-block" *ngIf="(productCodeVar.touched || productCodeVar.dirty) && productCodeVar.errors"> <span *ngIf="productCodeVar.errors.required"> Product code 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]="!productForm.valid" (click)="saveProduct()"> Save </button> </span> <span> <a class="btn btn-default" [routerLink]="['/products']"> Cancel </a> </span> <span> <a class="btn btn-default" (click)="deleteProduct()"> Delete </a> </span> </div> </div> </fieldset> </form> <div class="has-error" *ngIf="errorMessage">{{errorMessage}}</div> </div> </div>
توضیحات:
نکتهی مهمی را که در این کدها میخواهیم بررسی کنیم، متد ngOnInit آناست:
ngOnInit(): void { let id = +this.route.snapshot.params['id']; this.getProduct(id); }
- ابتدا لیست محصولات را مشاهده کنید.
- سپس بر روی دکمهی edit محصول شماره یک کلیک نمائید:
تصویر فوق حاصل خواهد شد که صحیح است. اطلاعات مربوط به محصول یک از وب سرور آزمایشی برنامه واکشی شده و به شیء product نسبت داده شدهاست. همین انتساب سبب مقدار دهی فیلدهای فرم ویرایش اطلاعات گردیدهاست.
- در ادامه بر روی لینک Add product در منوی بالای صفحه کلیک کنید:
همانطور که مشاهده میکنید، هرچند URL صفحه تغییر کردهاست، اما هنوز فرم ویرایش اطلاعات، به روز نشده و فیلدهای آن جهت درج یک محصول جدید خالی نشدهاند. علت اینجا است که در متد ngOnInit، مقدار پارامتر id را از طریق شیء route.snapshot دریافت کردهایم. اگر تنها پارامترهای URL تغییر کنند، کامپوننت مجددا آغاز نشده و متد ngOnInit فراخوانی نمیشود. در اینجا تغییر آدرس http://localhost:4200/products/1/edit به http://localhost:4200/products/0/edit به علت عدم تغییر root URL segment آن یا همان products، سبب فراخوانی مجدد ngOnInit نمیشود. به همین جهت است که فیلدهای این فرم تغییر نکردهاند.
برای حل این مشکل بجای دریافت پارامترهای مسیریابی از طریق شیء route.snapshot بهتر است به تغییرات آنها گوش فرا دهیم. اینکار را از طریق متد route.params.subscribe میتوان انجام داد:
ngOnInit(): void { this.route.params.subscribe( params => { let id = +params['id']; this.getProduct(id); } ); }
یک نکته: هر زمانیکه از Observableها استفاده میشود، نیاز است در پایان کار کامپوننت، unsubscribe آن نیز فراخوانی شود تا نشتی حافظه رخ ندهد. در اینجا یک سری استثناء هم وجود دارند، مانند this.route.params که به صورت خودکار توسط طول عمر سرویس ActivatedRoute مدیریت میشود.
روش خواندن پارامترهای مسیریابی در +Angular 4
روشی که تا به اینجا در مورد خواندن پارامترهای مسیریابی ذکر شد، با Angular 4 هم سازگار است. در Angular 4 روش دیگری را نیز اضافه کردهاند که توسط شیء paramMap مدیریت میشود:
// For Angular 4+ let id = +this.route.snapshot.paramMap.get('id'); this.getProduct(id); // OR this.route.paramMap.subscribe(params => { let id = +params['id']; this.getProduct(id); });
تعریف پارامترهای اختیاری مسیریابی
فرض کنید یک صفحهی جستجو را طراحی کردهاید که در آن میتوان نام و کد محصول را جستجو کرد. سپس میخواهیم این پارامترها را به صفحهی نمایش لیست محصولات هدایت کنیم. برای طراحی این نوع مسیریابی میتوان از مطالبی که تاکنون گفته شد استفاده کرد و پارامترهایی اجباری را جهت مسیریابی تعریف نمود:
{ path: 'products/:name/:code', component: ProductListComponent }
[routerLink]="['/products', productName, productCode]"
{ path: 'products', component: ProductListComponent },
[routerLink]="['/products', {name: productName, code: productCode}]"
در این حالت لینک تولید شده چنین شکلی را خواهد داشت:
http://localhost:4200/products;name=Controller;code=gmg
let name = this.route.snapshot.params['name']; let code = this.route.snapshot.params['code'];
این پارامترها پس از هدایت کاربر به مسیری دیگر، حفظ نشده و پاک خواهند شد. به همین جهت کلیدهای تعریف شدهی در اینجا ضرورتی ندارد که یکتا بوده و با سایر قسمتهای برنامه تداخلی نداشته باشند.
تعریف پارامترهای کوئری در مسیریابی
فرض کنید لیست محصولات را بر اساس پارامتر یا پارامترهایی فیلتر کردهاید. اکنون اگر بر روی لینک مشاهدهی جزئیات یک محصول یافت شده کلیک کنید و سپس مجددا به لیست فیلتر شده بازگردید، تمام پارامترهای انتخابی پاک شده و لیست از ابتدا نمایش داده میشود. در یک چنین حالتی برای بهبود تجربهی کاربری، بهتر است پارامترهای جستجو شده را در طی هدایت کاربر به قسمتهای مختلف حفظ کرد:
http://localhost:42000/products/5?filterBy=product1
در اینجا نیز برای تعریف یک چنین قابلیتی، مسیریابی ابتدایی تعریف شده، همانند قبل خواهد بود و در آن خبری از پارامترهای کوئری نیست:
{ path: 'products', component: ProductListComponent}
<a [routerLink] = "['/products', product.id]" [queryParams] = "{ filterBy: 'er', showImage: true }"> {{product.productName}} </a>
و یا با کدنویسی به صورت ذیل است:
this.router.navigate(['/products'], { queryParams: { filterBy: 'er', showImage: true} } );
یک مشکل: اگر در صفحهی نمایش جزئیات یک محصول، دکمهی Back وجود داشته باشد، با کلیک بر روی آن پارامترهای کوئری پاک خواهند شد و دیگر حفظ نمیشوند. مرحلهی آخر حفظ پارامترهای کوئری در بین صفحات مختلف تنظیم ذیل است:
[preserveQueryParams] = "true"
<a class="btn btn-default" [routerLink]="['/products']" [preserveQueryParams]="true"> <i class="glyphicon glyphicon-chevron-left"></i> Back </a>
که البته در Angular 4 مورد اول به "queryParamsHandling= "preserve و مورد دوم به { 'queryParamsHandling: 'preserve} تغییر یافتهاست و حالت قبلی منسوخ شده اعلام گردیدهاست.
this.router.navigate(['/products'], { queryParamsHandling: 'preserve'} );
پس از بازگشت به صفحهی اصلی لیست محصولات، هرچند این پارامترهای کوئری حفظ شدهاند، اما مجددا به صورت خودکار پردازش نخواهند شد. برای خواندن آنها در متد ngOnInit باید به صورت ذیل عمل کرد:
var filter = this.route.snapshot.queryParams['filterBy'] || ''; var showImage = this.route.snapshot.queryParams['showImage'] === 'true';
در مثال تکمیل شدهی جاری، امکان فیلتر کردن جدول با اضافه کردن یک pipe جدید به نام ProductFilter میسر شدهاست:
>ng g p product/ProductFilter
import { PipeTransform, Pipe } from '@angular/core'; import { IProduct } from './iproduct'; @Pipe({ name: 'productFilter' }) export class ProductFilterPipe implements PipeTransform { transform(value: IProduct[], filterBy: string): IProduct[] { filterBy = filterBy ? filterBy.toLocaleLowerCase() : null; return filterBy ? value.filter((product: IProduct) => product.productName.toLocaleLowerCase().indexOf(filterBy) !== -1) : value; } }
<div class="panel-body"> <div class="row"> <div class="col-md-2">Filter by:</div> <div class="col-md-4"> <input type="text" [(ngModel)]="listFilter" /> </div> </div> <div class="row" *ngIf="listFilter"> <div class="col-md-6"> <h3>Filtered by: {{listFilter}} </h3> </div> </div>
<tr *ngFor="let product of products | productFilter:listFilter"> <td><a [routerLink]="['/products', product.id]" [queryParams]="{filterBy: listFilter}"> {{product.productName}} </a> </td>
در اینجا اگر به صفحهی جزئیات محصول فیلتر شده مراجعه کنیم، این فیلتر حفظ خواهد شد:
و در این حالت اگر بر روی دکمهی back کلیک کنیم، مجددا فیلتر وارد شده بازیابی شده و نمایش داده میشود:
که برای میسر شدن آن ابتدا خاصیت عمومی listFilter به کامپوننت لیست محصولات اضافه گردیده و سپس در ngOnInit، این پارامتر در صورت وجود، از سیستم مسیریابی دریافت میشود:
@Component({ selector: 'app-product-list', templateUrl: './product-list.component.html', styleUrls: ['./product-list.component.css'] }) export class ProductListComponent implements OnInit { listFilter: string; ngOnInit(): void { this.listFilter = this.route.snapshot.queryParams['filterBy'] || '';
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-routing-lab-02.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس از طریق خط فرمان به ریشهی پروژه وارد شده و دستور npm install را صادر کنید تا وابستگیهای آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد.
نظرات اشتراکها
مقایسه فریمورکهای UI
اشتراکها
طراحی enumهایی بهتر در NET.
اشتراکها
5 جایگزین برای منوی های همبرگری
اشتراکها
dateTimePicker شمسی مخصوص AngularJs
اشتراکها
انجام SEO بر روی برنامه های Angular
The Open XML SDK provides tools for working with Office Word, Excel, and PowerPoint documents. It supports scenarios such as:
- High-performance generation of word-processing documents, spreadsheets, and presentations.
- Document modification, such as adding, updating, and removing content and metadata.
- Search and replace content using regular expressions.
- Splitting up (shredding) a file into multiple files, and combining multiple files into a single file.
- Updating cached data and embedded spreadsheets for charts in Word/PowerPoint.
In September of 2014, at the ng-Europe conference, the Angular team announced what would be known as Angular 2.Is was a drastic change that left many developers angry. One of the main sources of complaint was the lack of a migration path.
در سال 2014 و با معرفی آنگولار 2، که یک rewrite کامل از فریم ورک آنگولار بود، بسیاری از توسعه دهندگان بخاطر تغییرات اساسی به سمت فریم ورکهای دیگر از جمله کتابخانهی ری اکت کوچ کردند.
مشکلات کامپوننتهای Angular را چون با زبان TypeScript تهیه میشوند، میتوان بلافاصله در ادیتور مورد استفاده و یا در حین کامپایل برنامه مشاهده کرد؛ اما یک چنین بررسی در مورد قالبهای HTML ایی آن در زمان کامپایل انجام نمیشود و اگر مشکلی وجود داشته باشد، این مشکلات را صرفا در زمان اجرای برنامه در مرورگر میتوان مشاهده کرد. برای رفع این مشکل و بهبود این وضعیت، در نگارش 5.2.0 فریم ورک Angular (و همچنین Angular CLI 1.7 به بعد)، پرچم جدیدی به تنظیمات کامپایلر آن اضافه شدهاست که با فعالسازی آن، مشکلات binding احتمالی در قالبهای کامپوننتها را میتوان یافت. زمانیکه توسط Angular CLI یک برنامهی Angular را در حالت AoT کامپایل میکنیم، کامپایلر مراحلی را طی میکند که توسط آن کدهای یک قالب کامپوننت، تبدیل به دستور العملهایی قابل اجرای در مرورگر میشوند. در طی یکی از این مراحل، کامپایلر قالبهای Angular، از کامپایلر TypeScript برای اعتبارسنجی عبارتهای binding استفاده میکند. اکنون میتوان خروجی این مرحله را نیز در حین کار با Angular CLI، مشاهده و مشکلات گزارش شدهی توسط آنرا برطرف کرد.
فعالسازی بررسی مشکلات قالبهای کامپوننتها
برای فعالسازی بررسی مشکلات قالبهای کامپوننتها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
- در اینجا با معرفی خاصیت fullTemplateTypeCheck و تنظیم آن به true، مشکلات موجود در قالبها را در زمان کامپایل برنامه میتوانید مشاهده کنید.
- البته این خاصیت در حین استفادهی از یکی از دستورات ng serve --aot و یا ng build --prod انتخاب میشود.
- مقدار این پرچم در نگارشهای 5x به صورت پیشفرض به false تنظیم شدهاست؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آنرا شروع کنید.
یک مثال: بررسی خاصیت fullTemplateTypeCheck
فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کردهاید که فقط دارای خاصیت name است:
سپس یک خاصیت عمومی را بر همین مبنا در کامپوننتی، تعریف و مقدار دهی اولیه کردهاید:
اکنون در قالب این کامپوننت، به شکل زیر از این وهله استفاده شدهاست:
در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
همانطور که ملاحظه میکنید اینبار خطاهای کامپایل فایل html نیز در خروجی کامپایلر ظاهر شدهاست و عنوان میکند خاصیت age در اینترفیس PonyModel وجود خارجی ندارد.
برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینهی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوعهای نال نپذیر عادت دهید. به این ترتیب میتوانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آنها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.
یک نکتهی تکمیلی
افزونهی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارشهای پیش از 5 نیز قابل استفاده است).
فعالسازی بررسی مشکلات قالبهای کامپوننتها
برای فعالسازی بررسی مشکلات قالبهای کامپوننتها، نیاز است به فایل تنظیمات کامپایلر TypeScript و یا همان tsconfig.json مراجعه کرد و سپس قسمت جدیدی را به آن به نام angularCompilerOptions، افزود:
{ "compilerOptions": { "experimentalDecorators": true, ... }, "angularCompilerOptions": { "fullTemplateTypeCheck": true, "preserveWhiteSpace": false, ... } }
- البته این خاصیت در حین استفادهی از یکی از دستورات ng serve --aot و یا ng build --prod انتخاب میشود.
- مقدار این پرچم در نگارشهای 5x به صورت پیشفرض به false تنظیم شدهاست؛ اما در نگارش 6 آن به true تنظیم خواهد شد. بنابراین بهتر است از هم اکنون کار با آنرا شروع کنید.
یک مثال: بررسی خاصیت fullTemplateTypeCheck
فرض کنید اینترفیس یک مدل را به صورت زیر تعریف کردهاید که فقط دارای خاصیت name است:
export interface PonyModel { name: string; }
import { PonyModel } from "./pony"; @Component({ selector: "app-detect-common-errors-test", templateUrl: "./detect-common-errors-test.component.html", styleUrls: ["./detect-common-errors-test.component.css"] }) export class DetectCommonErrorsTestComponent implements OnInit { ponyModel: PonyModel = { name: "Pony1" };
<p>Hello {{ponyModel.age}}
در این حالت اگر fullTemplateTypeCheck فعال شده باشد و دستور ng build --prod را صادر کنیم، به خروجی ذیل خواهیم رسید:
\detect-common-errors-test.component.html(5,4): : Property 'age' does not exist on type 'PonyModel'.
برای اینکه بتوانید به حداکثر کارآیی این قابلیت برسید، بهتر است گزینهی strict را در تنظیمات کامپایلر TypeScript روشن کنید و خودتان را به کار با نوعهای نال نپذیر عادت دهید. به این ترتیب میتوانید تعداد خطاهای احتمالی بیشتری را پیش از موعد و پیش از وقوع آنها در زمان اجرا، در زمان کامپایل، پیدا و رفع کنید.
یک نکتهی تکمیلی
افزونهی Angular Language service نیز یک چنین قابلیتی را به همراه دارد (و حتی در نگارشهای پیش از 5 نیز قابل استفاده است).