در این مطلب قصد داریم پیامها و اخطارهای برنامه را توسط کامپوننت
Angular2 Toasty نمایش داده و همچنین برای کاهش میزان تکرار قسمتهای نمایش خطا در برنامه، کار مدیریت متمرکز و سراسری آنها را نیز انجام دهیم.
نمایش پیامها و اخطارهای یک برنامهی Angular توسط ng2-toasty
در مطلب «
ایجاد Drop Down Listهای آبشاری در Angular» در قسمت دریافت اطلاعات drop down دوم از سرور، اگر کاربر مجددا گروه را بر روی حالت «لطفا گروهی را انتخاب کنید ...» قرار دهد، مقدار categoryId به undefined تغییر میکند:
fetchProducts(categoryId?: number) {
console.log(categoryId);
this.products = [];
if (categoryId === undefined || categoryId.toString() === "undefined") {
return;
}
در اینجا میخواهیم توسط کامپوننت Angular2 Toasty، پیام متناسبی را نمایش دهیم:
پیشنیازهای کار با کامپوننت Angular2 Toasty توسط یک برنامهی Angular CLI
برای کار با کامپوننت Angular2 Toasty، ابتدا از طریق خط فرمان به پوشهی ریشهی برنامه وارد شده و سپس دستور ذیل را صادر میکنیم:
> npm install ng2-toasty --save
اینکار سبب خواهد شد تا این کامپوننت در پوشهی node_modules\ng2-toasty نصب شده و همچنین فایل package.json نیز جهت درج مدخل آن به روز رسانی شود:
یک نکته: اگر در حین اجرای این دستور به خطای ذیل برخوردید:
npm ERR! Error: EPERM: operation not permitted, rename
چون VSCode پوشهی node_modules را تحت نظر قرار میدهد، ممکن است یک سری اعمال npm مجوز اجرا را پیدا نکنند. بنابراین ابتدا VSCode را بسته و مجددا دستور npm را اجرا کنید.
پس از آن نیاز است یکی از شیوهنامههایی را که در تصویر فوق ملاحظه میکنید، در فایل angular-cli.json. مشخص کنیم:
"styles": [
"../node_modules/bootstrap/dist/css/bootstrap.min.css",
"../node_modules/ng2-toasty/bundles/style-bootstrap.css",
"styles.css"
],
که برای نمونه در اینجا، شیوهنامهی بوت استرپ آن انتخاب شدهاست.
سپس باید به فایل src\app\app.module.ts مراجعه کرد و ماژول این کامپوننت را معرفی نمود:
import { ToastyModule } from "ng2-toasty";
@NgModule({
imports: [
BrowserModule,
ToastyModule.forRoot(),
همچنین در همین قسمت، به فایل قالب src\app\app.component.html مراجعه کرده و selector tag این کامپوننت را در ابتدای آن تعریف میکنیم:
<ng2-toasty [position]="'top-right'"></ng2-toasty>
در اینجا با استفاده از property binding و تعیین مقدار رشتهای top-right، محل نمایش اعلانات برنامه را مشخص میکنیم. مقدارهای ممکن آن شامل bottom-right، bottom-left، top-right، top-left، top-center، bottom-center، center-center هستند. برای مثال اگر میخواهید آنرا در میانهی صفحه نمایش دهید، مقدار center-center را انتخاب کنید. همچنین باید دقت داشت که این مقدار باید درون '' قرار گیرد تا مشخص شود که رشتهای به خاصیت position انتساب داده شدهاست و این مقدار یک خاصیت عمومی تعریف شدهی در کامپوننت متناظر با قالب، نیست.
نمایش یک پیام خطا توسط ToastyService
اکنون که کار برپایی کامپوننت Angular2 Toasty به پایان رسید، کار کردن با آن به سادگی تزریق سرویس آن به سازندهی یک کامپوننت و فراخوانی متدهای info، success ، wait ، error و warning آن است:
import { ToastyService, ToastOptions } from "ng2-toasty";
export class ProductGroupComponent implements OnInit {
constructor(
private productItemsService: ProductItemsService,
private toastyService: ToastyService) { }
fetchProducts(categoryId?: number) {
console.log(categoryId);
this.products = [];
if (categoryId === undefined || categoryId.toString() === "undefined") {
this.toastyService.error(<ToastOptions>{
title: "Error!",
msg: "Please select a category.",
theme: "bootstrap",
showClose: true,
timeout: 5000
});
return;
}
- در اینجا در ابتدا ماژولهای مورد نیاز import شدهاند.
- سپس ToastyService به سازندهی کلاس کامپوننت مدنظر تزریق شدهاست تا بتوان از امکانات آن استفاده کرد.
- در ادامه، فراخوانی متد this.toastyService.error سبب نمایش اخطار قرمز رنگی میشود که تصویر آنرا در ابتدای مطلب جاری مشاهده کردید.
- علت ذکر <ToastOptions> در اینجا این است که وجود آن سبب خواهد شد تا intellisense در VSCode فعال شود و پس از آن بتوان تمام گزینههای این متد و تنظیمات را بدون مراجعهی به مستندات آن از طریق intellisense یافت و درج کرد:
مدیریت سراسری خطاهای مدیریت نشده، در یک برنامهی Angular
در برنامههای Angular از این دست کدها بسیار مشاهده میشوند:
this.productItemsService.getCategories().subscribe(
data => {
this.categories = data;
},
err => console.log("get error: ", err)
);
تا اینجا قسمت err یا بروز خطا را با console.log مدیریت کردهایم. در این حالت کاربر ممکن است 10 بار بر روی دکمهای کلیک کند یا صفحهای را بارگذاری کند و دست آخر متوجه نشود که مشکل کار چیست. به همین جهت میتوان خطاها را نیز توسط ToastyService نمایش داد تا کاربران دقیقا متوجه بروز مشکل رخ داده شوند. اما ... به این ترتیب تکرار کد زیادی را خواهیم داشت و باید به ازای تمام این موارد، یکبار this.toastyService.error را فراخوانی کنیم. برای مدیریت بهتر یک چنین سناریویی در Angular، کلاس و سرویس توکاری به نام ErrorHandler وجود دارد. در هر قسمتی از برنامهی Angular که استثنایی مدیریت نشده رخ دهد، ابتدا از این کلاس رد شده و سپس به برنامه انتشار پیدا میکند. بنابراین میتوان یک ErrorHandler سفارشی را با ارث بری از آن تهیه کرد و سپس بجای سرویس توکار اصلی، به برنامه معرفی و از آن استفاده نمود. به این ترتیب میتوان یک Global Error Interceptor را طراحی نمود.
به همین منظور کلاس جدیدی را به صورت ذیل در پوشهی src\app اضافه میکنیم:
> ng g cl app.error-handler
با این خروجی
installing class
create src\app\app.error-handler.ts
سپس این کلاس را به نحو ذیل تکمیل خواهیم کرد:
import { ErrorHandler } from "@angular/core";
export class AppErrorHandler implements ErrorHandler {
handleError(error: any): void {
console.log("Error:", error);
}
}
کلاس جدید AppErrorHandler از کلاس پایه ErrorHandler ارث بری میکند. بنابراین import آنرا در ابتدای کار مشاهده میکنید. سپس باید متد handleError آنرا با امضایی که مشاهده میکنید، پیاده سازی کنیم. فعلا با استفاده از console.log این خطا را در کنسول developer tools نمایش میدهیم.
اکنون نیاز است این ErrorHandler سفارشی را بجای نمونهی اصلی به برنامه معرفی کنیم. برای این منظور به فایل src\app\app.module.ts مراجعه کرده و تغییرات ذیل را اعمال میکنیم:
import { NgModule, ErrorHandler } from "@angular/core";
import { AppErrorHandler } from "./app.error-handler";
@NgModule({
providers: [
{ provide: ErrorHandler, useClass: AppErrorHandler }
]
ابتدا ErrorHandler به لیست imports اضافه شدهاست و همچنین محل تامین AppErrorHandler نیز مشخص گردیدهاست. سپس در قسمت providers ماژول جاری، از تعریف خاصی که ملاحظه میکنید، استفاده خواهد شد. به این ترتیب به Angular اعلام میکنیم، هرگاه نیازی به وهلهای از کلاس توکار ErrorHandler بود، وهلهای از کلاس سفارشی AppErrorHandler را مورد استفاده قرار بده.
اکنون برای آزمایش آن، در کدهای سمت سرور مطلب «
ایجاد Drop Down Listهای آبشاری در Angular»، یک استثنای عمدی را قرار میدهیم:
[HttpGet("[action]/{categoryId:int}")]
public async Task<IActionResult> GetProducts(int categoryId)
{
throw new Exception();
به این ترتیب هر زمانیکه گروهی انتخاب شد، دریافت محصولات آن گروه با خطا مواجه میشود.
برای اینکه AppErrorHandler، مورد استفاده قرار گیرد، قسمت err دریافت لیست محصولات را نیز حذف میکنیم (تا تبدیل به یک استثنای مدیریت نشده شود):
this.productItemsService.getProducts(categoryId).subscribe(
data => {
this.products = data;
this.isLoadingProducts = false;
}// ,
// err => {
// console.log("get error: ", err);
// this.isLoadingProducts = false;
// }
);
اکنون اگر برنامه را اجرا کنیم، چنین پیامی، در کنسول developer tools ظاهر میشود و مشخص است از فایل AppErrorHandler صادر شدهاست:
افزودن ToastyService به AppErrorHandler
در ادامه میخواهیم بجای console.log از ToastyService برای نمایش خطاهای مدیریت نشدهی برنامه در کلاس AppErrorHandler استفاده کنیم:
import { ToastyService, ToastOptions } from "ng2-toasty";
import { ErrorHandler } from "@angular/core";
export class AppErrorHandler implements ErrorHandler {
constructor(private toastyService: ToastyService) {
}
handleError(error: any): void {
// console.log("Error:", error);
this.toastyService.error(<ToastOptions>{
title: "Error!",
msg: "Fatal error!",
theme: "bootstrap",
showClose: true,
timeout: 5000
});
}
}
به همین منظور سرویس آنرا به سازندهی کلاس AppErrorHandler تزریق کرده و سپس از آن به نحو متداولی در متد handleError استفاده میکنیم. به این ترتیب بجای دهها و یا صدها قسمت مدیریت err=>this.toastyService.error در برنامه، تنها یک مورد مدیریت مرکزی را خواهیم داشت.
مشکل اول! اکنون اگر برنامه را اجرا کنیم، در کنسول developer tools چنین خطایی ظاهر میشود:
Uncaught Error: Can't resolve all parameters for AppErrorHandler: (?).
به این معنا که Angular قادر نیست وهلهای از AppErrorHandler را ایجاد کند؛ چون نمیداند که چگونه باید پارامتر سازندهی ToastyService را وهله سازی و تزریق نماید. علت اینجا است که کار آغاز کلاس ویژهی ErrorHandler سراسری، پیش از کار بارگذاری ماژول مرتبط با ToastyService انجام میشود. به همین جهت، این مورد جزو معدود مواردی است که باید به صورت دستی تزریق شود:
import { ErrorHandler, Inject } from "@angular/core";
export class AppErrorHandler implements ErrorHandler {
constructor(
@Inject(ToastyService) private toastyService: ToastyService
) {
}
در اینجا توسط Inject decorator، کار تزریق دستی ToastyService انجام خواهد شد. اکنون اگر برنامه را مجدد اجرا کنیم، خطای قبلی برطرف شده؛ یعنی کلاس AppErrorHandler با موفقیت وهله سازی شدهاست.
مشکل دوم! اینبار برنامه را اجرا کنید. سپس گروهی را انتخاب نمائید. مشاهده میکنید که خطایی نمایش داده نشد؛ هرچند در کنسول developer tools میتوان اثری از آن را مشاهده کرد. مجددا گروه دیگری را انتخاب کنید، در این بار دوم است که خطای ارائه شدهی توسط this.toastyService.error ظاهر میشود. توضیح آن نیاز به بررسی مفهومی به نام Zones در Angular دارد.
مفهوم Zones در Angular
زمانیکه متد this.toastyService.error در یک کامپوننت برنامه مورد استفاده قرار گرفت، به خوبی کار میکرد و در همان بار اول فراخوانی، پیام را نمایش میداد. اما با انتقال آن به کلاسAppErrorHandler ، این قابلیت از کار افتاد. علت اینجا است که زمینهی اجرایی این قطعه کد، اکنون خارج از Zone یا ناحیهی Angular است و به همین دلیل متوجه تغییرات آن نمیشود. Zone زمینهی اجرایی اعمال async است و اگر به فایل package.json یک برنامهی Angular دقت کنید، بستهی zone.js، یکی از وابستگیهای همراه آن است.
تغییرات حالت برنامه، توسط یکی از اعمال ذیل رخ میدهند:
الف) بروز رخدادهایی مانند کلیک، ورود اطلاعات و یا ارسال فرم
ب) اعمال Ajax ایی
ج) استفاده از Timers مانند استفاده از setTimeout و setInterval
هر سه مورد یاد شده از نوع async بوده و زمانیکه رخ میدهند، حالت برنامه را تغییر خواهند داد. Angular نیز تنها به این موارد علاقمند بوده و به آنها در جهت به روز رسانی رابط کاربری برنامه واکنش نشان میدهد.
برای مثال this.toastyService.error دارای خاصیتی است به نام timeout: 5000 که در آن، مورد «ج» فوق رخ میدهد؛ یعنی یک Timer پس از 5 ثانیه سبب بسته شدن آن خواهد شد. به همین جهت است که اگر پیش از پایان این 5 ثانیه مجددا درخواست واکشی لیست محصولات یک گروه را بدهیم، خطای مربوطه مشاهده میشود. چون Angular زمینهی اجرایی لازم را فراهم کرده (یا همان Zone در اینجا) و مجبور به واکنش به عملیات async از نوع Timer است.
برای دسترسی به امکانات کتابخانهی zone.js، میتوان از طریق تزریق سرویس آن به نام NgZone به سازندهی کلاس شروع کرد:
import { ToastyService, ToastOptions } from "ng2-toasty";
import { ErrorHandler, Inject, NgZone } from "@angular/core";
import { LocationStrategy, PathLocationStrategy } from "@angular/common";
export class AppErrorHandler implements ErrorHandler {
constructor(
@Inject(NgZone) private ngZone: NgZone,
@Inject(ToastyService) private toastyService: ToastyService,
@Inject(LocationStrategy) private locationProvider: LocationStrategy
) {
}
handleError(error: any): void {
// console.log("Error:", error);
const url = this.locationProvider instanceof PathLocationStrategy ? this.locationProvider.path() : "";
const message = error.message ? error.message : error.toString();
this.ngZone.run(() => {
this.toastyService.error(<ToastOptions>{
title: "Error!",
msg: `URL:${url} \n ERROR:${message}`,
theme: "bootstrap",
showClose: true,
timeout: 5000
});
});
// IMPORTANT: Rethrow the error otherwise it gets swallowed
// throw error;
}
}
در اینجا فراخوانی
this.ngZone.run سبب میشود تا درخواست نمایش خطای رخداده وارد Angular Zone شده و بلافاصله سبب نمایش آن گردد:
چند نکته
1- اگر میخواهید علاوه بر رخدادگردانی سراسری خطاها، این خطاها را به محل اصلی آنها نیز انتشار دهید، نیاز است سطر throw error را در انتهای متد handleError نیز ذکر کنید. در غیر اینصورت، کار در همینجا به پایان خواهد رسید و این خطاها دیگر منتشر نمیشوند.
2- روش دریافت URL جاری صفحه را نیز در اینجا مشاهده میکنید. این اطلاعات میتوانند جهت ارسال به سرور برای ثبت و بررسیهای بعدی مفید باشند.
3- مقدار new Error().stack معادل stack trace جاری است و تقریبا در تمام مرورگرهای جدید پشتیبانی میشود.
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید: angular-template-driven-forms-lab-07.zip
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کردهاید. سپس به ریشهی پروژه وارد شده و دو پنجرهی کنسول مجزا را باز کنید. در اولی دستورات
>npm install
>ng build --watch
و در دومی دستورات ذیل را اجرا کنید:
>dotnet restore
>dotnet watch run
اکنون میتوانید برنامه را در آدرس http://localhost:5000 مشاهده و اجرا کنید.