مطالب
پشتیبانی از IE در برنامه‌های Angular
اگر یک برنامه‌ی Angular را به صورت پیش‌فرض در IE اجرا کنیم، یک چنین تصویری مشاهده خواهد شد:


برای اجرای برنامه توسط نگارش‌های مختلف IE می‌توانید برنامه‌ی IE Tester را نصب کنید.


مشکل چیست؟

مشکل عدم اجرای برنامه‌های Angular در IE، به قدیمی بودن موتور JavaScript آن بر می‌گردد؛ خصوصا در مورد توابع کار با آرایه‌ها. برای مثال در مورد کار با for..of هیچ نوع پشتیبانی از آن در IE وجود ندارد (و نخواهد داشت؛ با توجه به پایان دوره‌ی پشتیبانی آن):



چگونه پشتیبانی از ویژگی‌های جدید JavaScript را به مرورگر IE اضافه کنیم؟

Angular امکان افزودن کمبودهای موتور JavaScript پیش‌فرض IE را توسط کتابخانه‌ی core-js میسر کرده‌است که اصطلاحا به آن polyfills گفته می‌شود. برای این منظور فایل src\polyfills.ts را گشوده و تغییرات زیر را به آن اعمال کنید:
الف) در این فایل، هرجایی  import // وجود دارد، آن‌را تبدیل به import کنید (تمام importهایی که کامنت شده‌اند را از حالت کامنت خارج کنید).
ب) دو بسته‌ی زیر را نیز باید نصب کنید:
npm install --save classlist.js
npm install --save web-animations-js
البته اگر به کامنت‌های این فایل دقت کنید، نیاز به نصب این بسته‌ها نیز در آن عنوان شده‌است.


نتیجه‌ی نهایی پس از افزودن polyfills مخصوص IE

اکنون اگر مجددا برنامه را کامپایل و اجرا کنید، برنامه‌ی Angular بدون مشکل در IE اجرا خواهد شد:

مطالب
Getting Started with Windows SharePoint Services 3.0

کتابچه‌ی رایگان 60 صفحه‌ای از مایکروسافت در مورد SharePoint Services 3.0 با محتوای زیر:

Microsoft Corporation - Published: March 2009

Introduction to Getting Started with Windows SharePoint Services 3.0 technology
What's new for IT professionals in Windows SharePoint Services 3.0
Administration model enhancements
New and improved compliance features and capabilities
New and improved operational tools and capabilities
Improved support for network configuration
Extensibility enhancements
For further reading: Evaluation guide for Windows SharePoint Services 3.0 technology
Determine hardware and software requirements
About hardware and software requirements
Stand-alone installation
Server farm installation
Plan browser support
About browser support
Levels of browser support
Feature-specific compatibility listed by Web browser
Install Windows SharePoint Services 3.0 on a stand-alone computer
Hardware and software requirements
Configure the server as a Web server
Install and configure Windows SharePoint Services 3.0 with Windows Internal Database
Post-installation steps
Deploy in a simple server farm
Deployment overview
Deploy and configure the server infrastructure
Perform additional configuration tasks
Create a site collection and a SharePoint site
Roadmap to Windows SharePoint Services 3.0 content
Windows SharePoint Services 3.0 content by audience
Windows SharePoint Services 3.0 IT professional content by stage of the IT life cycle


دریافت

مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های Angular - قسمت اول - معرفی و ایجاد ساختار برنامه
قصد داریم در طی یک سری مطلب، یک کلاینت Angular 5.x را برای مطلب «اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity» تهیه کنیم. البته این سری، مستقل از قسمت سمت سرور آن تهیه خواهد شد و صرفا در حد دریافت توکن از سرور و یا ارسال مشخصات کاربر جهت لاگین، نیاز بیشتری به قسمت سمت سرور آن ندارد و تاکید آن بر روی مباحث سمت کلاینت Angular است. بنابراین اینکه چگونه این توکن را تولید می‌کنید، در اینجا اهمیتی ندارد و کلیات آن با تمام روش‌های پیاده سازی سمت سرور (حتی مطلب «پیاده سازی JSON Web Token با ASP.NET Web API 2.x») سازگار است.
این سری شامل بررسی موارد ذیل خواهد بود:
  1. قسمت اول - معرفی و ایجاد ساختار برنامه
  2. قسمت دوم - سرویس اعتبارسنجی
  3. قسمت سوم - ورود به سیستم
  4. قسمت چهارم - به روز رسانی خودکار توکن‌ها
  5. قسمت پنجم - محافظت از مسیرها
  6. قسمت ششم - کار با منابع محافظت شده‌ی سمت سرور 


پیشنیازها
- آشنایی با Angular CLI
- آشنایی با مسیریابی‌ها در Angular
- آشنایی با فرم‌های مبتنی بر قالب‌ها


همچنین اگر پیشتر Angular CLI را نصب کرده‌اید، قسمت «به روز رسانی Angular CLI» ذکر شده‌ی در مطلب «Angular CLI - قسمت اول - نصب و راه اندازی» را نیز اعمال کنید. در این سری از angular/cli: 1.6.0@ استفاده شده‌است.


ایجاد ساختار اولیه و مسیریابی‌های آغازین مثال این سری

در ادامه، یک پروژه‌ی جدید مبتنی بر Angular CLI را به نام ASPNETCore2JwtAuthentication.AngularClient به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
> ng new ASPNETCore2JwtAuthentication.AngularClient --routing

به علاوه، قصد استفاده‌ی از بوت استرپ را نیز داریم. به همین جهت ابتدا به ریشه‌ی پروژه وارد شده و سپس دستور ذیل را صادر کنید، تا بوت استرپ نصب شود و پرچم save آن سبب به روز رسانی فایل package.json نیز گردد:
> npm install bootstrap --save
پس از آن نیاز است به فایل angular-cli.json. مراجعه کرده و شیوه‌نامه‌ی بوت استرپ را تعریف کنیم:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
به این ترتیب، به صورت خودکار این شیوه نامه به همراه توزیع برنامه حضور خواهد داشت و نیازی به تعریف مستقیم آن در فایل index.html نیست.

در ادامه برای تکمیل مثال جاری، دو کامپوننت جدید خوش‌آمدگویی و همچنین یافتن نشدن مسیرها را به برنامه اضافه می‌کنیم:
>ng g c welcome
>ng g c PageNotFound
که سبب ایجاد کامپوننت‌های src\app\welcome\welcome.component.ts و src\app\page-not-found\page-not-found.component.ts خواهند شد؛ به همراه به روز رسانی خودکار فایل src\app\app.module.ts جهت تکمیل قسمت declarations آن:
@NgModule({
  declarations: [
    AppComponent,
    WelcomeComponent,
    PageNotFoundComponent
  ],
سپس فایل src\app\app-routing.module.ts را به نحو ذیل تکمیل نمائید:
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
  { path: 'welcome', component: WelcomeComponent },
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
در اینجا زمانیکه کاربر ریشه‌ی سایت را درخواست می‌کند، به کامپوننت welcome هدایت خواهد شد.
همچنین مدیریت مسیریابی آدرس‌های ناموجود در سایت نیز با تعریف ** صورت گرفته‌است.


ایجاد ماژول Authentication و تعریف کامپوننت لاگین

کامپوننت‌های احراز هویت و اعتبارسنجی کاربران را در ماژولی به نام Authentication قرار خواهیم داد. بنابراین ماژول جدید آن‌را به همراه تنظیمات ابتدایی مسیریابی آن ایجاد می‌کنیم:
>ng g m Authentication -m app.module --routing
با این خروجی
  create src/app/authentication/authentication-routing.module.ts (257 bytes)
  create src/app/authentication/authentication.module.ts (311 bytes)
  update src/app/app.module.ts (696 bytes)
اگر به سطر آخر آن دقت کنید، فایل app.module.ts را نیز به صورت خودکار به روز رسانی کرده‌است:
import { EmployeeRoutingModule } from './employee/employee-routing.module';
@NgModule({
  imports: [
    BrowserModule,
    AppRoutingModule,
    AuthenticationModule
  ]
در اینجا AuthenticationModule را به انتهای لیست imports افزوده‌است که نیاز به اندکی تغییر دارد و باید آن‌را پیش از AppRoutingModule تعریف کرد. علت این است که AppRoutingModule، دارای تعریف مسیریابی ** یا catch all است که آن‌را جهت مدیریت مسیرهای یافت نشده به برنامه افزوده‌ایم. بنابراین اگر ابتدا AppRoutingModule تعریف شود و سپس AuthenticationModule، هیچگاه فرصت به پردازش مسیریابی‌های ماژول اعتبارسنجی نمی‌رسد؛ چون مسیر ** پیشتر برنده شده‌است.
بنابراین فایل app.module.ts چنین تعاریفی را پیدا می‌کند:
import { EmployeeModule } from './employee/employee.module';
@NgModule({
  imports: [
    BrowserModule,
    AuthenticationModule,
    AppRoutingModule 
  ]

در ادامه کامپوننت جدید لاگین را به این ماژول اضافه می‌کنیم:
>ng g c Authentication/Login
با این خروجی
  create src/app/Authentication/login/login.component.html (24 bytes)
  create src/app/Authentication/login/login.component.ts (265 bytes)
  create src/app/Authentication/login/login.component.css (0 bytes)
  update src/app/Authentication/authentication.module.ts (383 bytes)
اگر به سطر آخر آن دقت کنید، کار به روز رسانی فایل ماژول authentication، جهت درج این کامپوننت جدید، در قسمت declarations فایل authentication.module.ts نیز به صورت خودکار انجام شده‌است:
import { LoginComponent } from "./login/login.component";

@NgModule({
  declarations: [LoginComponent]
})

در ادامه می‌خواهیم قالب این کامپوننت را در منوی اصلی سایت قابل دسترسی کنیم. به همین جهت به فایل src/app/authentication/authentication-routing.module.ts مراجعه کرده و مسیریابی این کامپوننت را تعریف می‌کنیم:
import { LoginComponent } from "./login/login.component";

const routes: Routes = [
  { path: "login", component: LoginComponent }
];
ابتدا کامپوننت لاگین import شده و سپس آرایه‌ی Routes، مسیری را به این کامپوننت تعریف کرده‌است.


ایجاد ماژول‌های Core و Shared

در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» در مورد اهمیت ایجاد ماژول‌های Core و Shared بحث شد. در اینجا نیز این دو ماژول را ایجاد خواهیم کرد.
فایل src\app\core\core.module.ts، جهت به اشتراک گذاری سرویس‌های singleton سراسری برنامه، یک چنین ساختاری را پیدا می‌کند:
import { NgModule, SkipSelf, Optional, } from "@angular/core";
import { CommonModule } from "@angular/common";
import { RouterModule } from "@angular/router";

// import RxJs needed operators only once
import "./services/rxjs-operators";

import { BrowserStorageService } from "./browser-storage.service";

@NgModule({
  imports: [CommonModule, RouterModule],
  exports: [
    // components that are used in app.component.ts will be listed here.
  ],
  declarations: [
    // components that are used in app.component.ts will be listed here.
  ],
  providers: [
    // global singleton services of the whole app will be listed here.
    BrowserStorageService
  ]
})
export class CoreModule {
  constructor( @Optional() @SkipSelf() core: CoreModule) {
    if (core) {
      throw new Error("CoreModule should be imported ONLY in AppModule.");
    }
  }
}
در اینجا از BrowserStorageService مطلب «ذخیره سازی اطلاعات در مرورگر توسط برنامه‌های Angular» استفاده شده‌است تا در سراسر برنامه در دسترس باشد. از آن در جهت ذخیره سازی توکن دریافتی از سرور در مرورگر کاربر، استفاده خواهیم کرد.
همچنین سطر "import "./services/rxjs-operators نیز از مطلب «روش‌هایی برای مدیریت بهتر عملگرهای RxJS در برنامه‌های Angular» کمک می‌گیرد تا مدام نیاز به import عملگرهای rxjs نباشد.

و ساختار فایل src\app\shared\shared.module.ts جهت به اشتراک گذاری کامپوننت‌های مشترک بین تمام ماژول‌ها، به صورت ذیل است:
import { NgModule, ModuleWithProviders } from "@angular/core";
import { CommonModule } from "@angular/common";

@NgModule({
  imports: [
    CommonModule
  ],
  entryComponents: [
    // All components about to be loaded "dynamically" need to be declared in the entryComponents section.
  ],
  declarations: [
    // common and shared components/directives/pipes between more than one module and components will be listed here.
  ],
  exports: [
    // common and shared components/directives/pipes between more than one module and components will be listed here.
    CommonModule
  ]
  /* No providers here! Since they’ll be already provided in AppModule. */
})
export class SharedModule {
  static forRoot(): ModuleWithProviders {
    // Forcing the whole app to use the returned providers from the AppModule only.
    return {
      ngModule: SharedModule,
      providers: [ /* All of your services here. It will hold the services needed by `itself`. */]
    };
  }
}

و در آخر تعاریف این دو ماژول جدید به فایل src\app\app.module.ts اضافه خواهند شد:
import { FormsModule } from "@angular/forms";
import { HttpClientModule } from "@angular/common/http";

import { CoreModule } from "./core/core.module";
import { SharedModule } from "./shared/shared.module";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    CoreModule,
    SharedModule.forRoot(),
    AuthenticationModule,
    AppRoutingModule
  ]
})
export class AppModule { }
در اینجا «FormsModule» و «HttpClientModule جدید» اضافه شده از Angular 4.3 را نیز import کرده‌ایم.


افزودن کامپوننت Header

در ادامه می‌خواهیم لینکی را به این مسیریابی جدید در نوار راهبری بالای سایت اضافه کنیم. همچنین قصد نداریم فایل app.component.html را با تعاریف آن شلوغ کنیم. به همین جهت یک کامپوننت هدر جدید را برای این منظور اضافه می‌کنیم:
> ng g c Header
با این خروجی:
  create src/app/header/header.component.html (25 bytes)
  create src/app/header/header.component.ts (269 bytes)
  create src/app/header/header.component.css (0 bytes)
  update src/app/app.module.ts (1069 bytes)
سپس به فایل src\app\header\header.component.html مراجعه کرده و آن‌را به صورت ذیل تغییر می‌دهیم:
<nav>
  <div>
    <div>
      <a [routerLink]="['/']">{{title}}</a>
    </div>
    <ul>
      <li role="menuitem" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
        <a [routerLink]="['/welcome']">Home</a>
      </li>
      <li role="menuitem" routerLinkActive="active">
        <a queryParamsHandling="merge" [routerLink]="['/login']">Login</a>
      </li>
    </ul>
  </div>
</nav>
که title آن نیز به صورت ذیل تامین می‌شود:
export class HeaderComponent implements OnInit {
    title = "Angular.Jwt.Core";

در آخر به فایل app.component.html مراجعه کرده و selector این کامپوننت را در آن درج می‌کنیم:
<app-header></app-header>
<div>
  <router-outlet></router-outlet>
</div>
زمانیکه یک کامپوننت فعالسازی می‌شود، قالب آن در router-outlet نمایش داده خواهد شد. app-header نیز کار نمایش nav-bar را انجام می‌دهد.

تا اینجا اگر دستور ng serve -o را صادر کنیم (کار build درون حافظه‌ای، جهت محیط توسعه و نمایش خودکار برنامه در مرورگر)، چنین خروجی در مرورگر نمایان خواهد شد (البته می‌توان پنجره‌ی کنسول ng serve را باز نگه داشت تا کار watch را به صورت خودکار انجام دهد؛ این روش سریعتر و به همراه build تدریجی است):



انتقال کامپوننت‌هایی که در app.component.ts استفاده می‌شوند به CoreModule

 با توجه به مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها»، کامپوننت‌هایی که در app.component.ts مورد استفاده قرار می‌گیرند، باید به Core Module منتقل شوند و قسمت declarations فایل app.module.ts از آن‌ها خالی گردد. به همین جهت پوشه‌ی جدید src\app\core\component را ایجاد کرده و سپس پوشه‌ی src\app\header را به آنجا منتقل می‌کنیم (با تمام فایل‌های درون آن).
پس از آن، تعریف HeaderComponent را از قسمت declarations مربوط به AppModule حذف کرده و آن‌را به دو قسمت exports و declarations مربوط به CoreModule منتقل می‌کنیم:
import { HeaderComponent } from "./component/header/header.component";

@NgModule({
  exports: [
    // components that are used in app.component.ts will be listed here.
    HeaderComponent
  ],
  declarations: [
    // components that are used in app.component.ts will be listed here.
    HeaderComponent
  ]
})
export class CoreModule {


کدهای کامل این سری را از اینجا می‌توانید دریافت کنید.
برای اجرای آن فرض بر این است که پیشتر Angular CLI را نصب کرده‌اید. سپس از طریق خط فرمان به ریشه‌ی پروژه‌ی ASPNETCore2JwtAuthentication.AngularClient وارد شده و دستور npm install را صادر کنید تا وابستگی‌های آن دریافت و نصب شوند. در آخر با اجرای دستور ng serve -o برنامه ساخته شده و در مرورگر پیش فرض سیستم نمایش داده خواهد شد (و یا همان اجرای فایل ng-serve.bat). همچنین باید به پوشه‌ی ASPNETCore2JwtAuthentication.WebApp نیز مراجعه کرده و فایل dotnet_run.bat را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
اشتراک‌ها
همکاری تیم TypeScript و AngularJS

For the last several months, the Microsoft TypeScript and Google Angular teams have been working closely together. Today at ng-conf in Salt Lake City, the Angular and the TypeScript teams are unveiling the first fruits of that collaboration.  We’re excited to 

همکاری تیم TypeScript و AngularJS
مطالب
مدیریت سراسری خطاها در یک برنامه‌ی Angular
در این مطلب قصد داریم پیام‌ها و اخطارهای برنامه را توسط کامپوننت 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 مشاهده و اجرا کنید.
اشتراک‌ها
آموزش AngularJS از کمپانی لیندا
مدت زمان فیلم یک ساعت
سرفصلهای آموزش شامل :

Introduction
  Welcome
  What you need to know
Using the exercise files
  Using the challenges
1. Configuring a New Angular Project
  Why Angular?
  Downloading Angular and dependencies
  Developing an application boilerplate
  Starting a Node server
2. Templates
  Supplying scope data
  Filtering output
  Controlling scopes
  Including partials
  Challenge: Editing airports
  Solution: Editing airports
3. Application Structure
  Routing views
  Supplying navigation
  Nesting scopes
  Linking individual records
  Challenge: Displaying two airports
  Solution: Displaying two airports
4. Server-Side Integration
  Defining services
  Retrieving individual records
  Searching through models
  Saving form data
  Challenge: Combining multiple data sources
  Solution: Combining multiple data sources
Conclusion
  Exploring advanced techniques
  Finding Angular resources 
آموزش AngularJS از کمپانی لیندا