اشتراک‌ها
استفاده از LINQ در JavaScript
One of the more awesome things I like about being a .NET developer is LINQ. LINQ (Language Integrated Query) is a fluent query interface implemented in the .NET framework. It helps you query, sort and order any type of collection. This is a very neat way of querying arrays, lists, dictionaries, objects, etc. I’ve made 5 examples which run out of the box with Node.js (or io.js). You can also use the library for browser based JavaScript projects.
استفاده از LINQ در JavaScript
مطالب
بررسی معماری پیازی - قسمت اول

 معماری پیازی توسط جفری پالرمو در سال 2008 ابداع شد. این معماری راه بهتری را برای ساخت برنامه‌های کاربردی جهت تست پذیری، نگهداری و قابلیت اطمینان بهتر بر روی زیرساخت‌هایی مانند پایگاه‌های داده و خدمات ارائه می‌دهد. هدف اصلی این معماری، پرداختن به چالش‌های پیش روی معماری 3 لایه و ارائه راه حلی برای مشکلات رایج مانند اتصال و جداسازی وابستگی‌ها است. دو نوع اتصال وجود دارند؛ اتصال محکم و اتصال ضعیف که در ادامه آنها را بررسی میکنیم.


اتصال محکم

هنگامی که یک کلاس، به یک وابستگی مشخصی وابسته است، گفته می‌شود که به شدت با آن کلاس همراه است. یک اتصال محکم جفت شده، به یک شیء دیگر وابسته است. این بدان معناست که تغییر یک شیء در یک برنامه‌ی با اتصال محکم جفت شده، اغلب نیاز به تغییر در تعدادی از اشیاء دیگر دارد. هنگامیکه یک برنامه کوچک است، دشوار نیست، اما در یک برنامه‌ی بزرگ، ایجاد تغییرات بسیار دشوار است.


اتصال ضعیف

یعنی دو شیء مستقل هستند و یک شیء می‌تواند بدون اینکه به آن وابسته باشد، از شیء دیگری استفاده کند. این یک هدف طراحی است که به دنبال کاهش وابستگی‌های متقابل بین اجزای یک سیستم، با هدف کاهش خطر این است که تغییرات در یک جزء، مستلزم تغییر در هر جزء دیگر باشد.


مزایای معماری پیازی

چندین مزیت برای معماری پیازی وجود دارند که در زیر ذکر شده‌اند:

  • قابلیت نگهداری بهتری را فراهم می‌کند؛ زیرا همه کدها به لایه‌ها یا مرکز، بستگی دارند.
  • تست پذیری بهتری را فراهم می‌کند؛ زیرا آزمون واحد را می‌توان برای لایه‌های جداگانه، بدون تأثیر بر سایر ماژول‌های برنامه ایجاد کرد.
  • این برنامه یک برنامه‌ی کاربردی با اتصال آزاد را ایجاد می‌کند؛ زیرا لایه بیرونی برنامه، همیشه از طریق واسط‌ها با لایه داخلی، ارتباط برقرار می‌کند.
  • هرگونه پیاده سازی پیوسته، در زمان اجرا به برنامه ارائه می‌شود.
  • موجودیت‌های دامنه، هسته و بخش مرکزی هستند. می‌تواند به هر دو لایه پایگاه داده و UI دسترسی داشته باشد.
  • لایه‌های داخلی هرگز به لایه خارجی وابسته نیستند. کدی که ممکن است تغییر کرده باشد، باید بخشی از یک لایه خارجی باشد.


لایه‌های معماری پیاز

این معماری به شدت به اصل وارونگی وابستگی، متکی است. رابط کاربری از طریق واسط‌ها با منطق تجاری ارتباط برقرار می‌کند و دارای چهار لایه است. لایه‌ها به سمت مرکز هستند. بخش مرکزی، موجودیت‌های Domain است که نشان‌دهنده موضوعات تجاری و رفتاری است. این لایه‌ها می‌توانند متفاوت باشند اما لایه موجودیت‌های دامنه، همیشه بخشی از دامنه‌ی مرکزی است. لایه دیگر، رفتار بیشتر یک شیء را تعریف می‌کند. در ادامه به توضیح لایه‌های معماری پیاز توجه فرمایید:


Domain Entities Layer

این بخش مرکزی معماری است. تمام اشیاء دامنه‌ی برنامه را در خود نگه می‌دارد. اگر برنامه ای با چهارچوب موجودیت ORM توسعه داده شود، این لایه دارای کلاس‌های POCO (Code First) یا Edmx (Database First) با موجودیت‌ها است. این نهادهای دامنه هیچ وابستگی ندارند.


Repository Layer

این لایه برای ایجاد یک لایه Abstraction بین لایه نهادهای دامنه و لایه منطق تجاری یک برنامه، در نظر گرفته شده‌است. این یک الگوی دسترسی به داده‌است که باعث می‌شود یک رویکرد مرتبط‌تر برای دسترسی به داده‌ها وجود داشته باشد. ما یک مخزن عمومی را ایجاد می‌کنیم که منبع داده را برای داده‌ها جستجو می‌کند، داده‌ها را از منبع داده به یک نهاد تجاری نگاشت می‌کند و تغییرات موجودیت تجاری را به منبع داده ارائه می‌دهد.


Service Layer

این لایه دارای رابط‌هایی است که برای برقراری ارتباط بین لایه UI و لایه مخزن استفاده می‌شود و به همراه منطق تجاری برای یک موجودیت است. بنابراین به آن لایه منطق تجاری نیز می‌گویند.


UI Layer

خارجی‌ترین لایه است و می‌تواند برنامه‌ی وب، Web API یا پروژه واحد تست باشد. این لایه دارای یک پیاده سازی از جنس Dependency Inversion Principle است بطوری که برنامه، یک برنامه‌ی کاربردی جفت شده‌ی آزاد می‌سازد و از طریق واسط‌ها با لایه داخلی ارتباط برقرار می‌کند.

در مطالب بعدی با مبحث معماری پیازی، نکات تکمیلی و مهمتری از لایه پیاز را تشریح میکنیم و یک پروژه را با معماری پیاز، راه اندازی میکنیم.

مطالب
Blazor 5x - قسمت 22 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 2 - ورود به سیستم و خروج از آن
در قسمت قبل، نحوه‌ی افزودن قالب ابتدایی ASP.NET Core Identity را به یک برنامه‌ی Blazor Server بررسی کردیم. در این مطلب، قسمت‌های ورود و خروج آن‌را به همراه نمایش قسمتی از صفحه، تنها به کاربران اعتبارسنجی شده، بررسی می‌کنیم تا روش دسترسی به اطلاعات ASP.NET Core Identity را در یک برنامه‌ی Blazor Server یکپارچه شده‌ی با آن، مطالعه کنیم.


نمایش قسمتی از صفحه بر اساس وضعیت اعتبارسنجی کاربر

فرض کنید می‌خواهیم در کامپوننت Shared\LoginDisplay.razor که در قسمت قبل آن‌را اضافه کردیم، لینک‌های ثبت نام و لاگین را به کاربران غیر اعتبارسنجی شده (هنوز لاگین نکرده) نمایش دهیم و اگر کاربر، اعتبارسنجی شده بود (لاگین کرده بود)، لینک خروج را به او نمایش دهیم. برای این منظور کامپوننت Shared\LoginDisplay.razor را به صورت زیر تغییر می‌دهیم:
<AuthorizeView>
    <Authorized>
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>
    <NotAuthorized>
        <a href="Identity/Account/Register">Register</a>
        <a href="Identity/Account/Login">Login</a>
    </NotAuthorized>
</AuthorizeView>
AuthorizeView، یکی از کامپوننت‌های استاندارد Blazor Server است. زمانیکه کاربری به سیستم لاگین کرده باشد، فرگمنت Authorized و در غیر اینصورت قسمت NotAuthorized آن‌را مشاهده خواهد کرد.
البته اگر برنامه را در همین حالت اجرا کنیم، به استثنای زیر خواهیم رسید:
InvalidOperationException: Authorization requires a cascading parameter of type Task<AuthenticationState>.
Consider using CascadingAuthenticationState to supply this.
Microsoft.AspNetCore.Components.Authorization.AuthorizeViewCore.OnParametersSetAsync()
برای رفع این مشکل و ارائه‌ی AuthenticationState به تمام کامپوننت‌های یک برنامه‌ی Blazor Server، نیاز است از کامپوننت CascadingAuthenticationState استفاده کرد. در مورد پارامترهای آبشاری، در قسمت نهم این سری بیشتر بحث شد و هدف از آن، ارائه‌ی یکسری اطلاعات، به تمام زیر کامپوننت‌های یک کامپوننت والد است؛ بدون اینکه نیاز باشد مدام این پارامترها را در هر زیر کامپوننتی، تعریف و تنظیم کنیم. همینقدر که آن‌ها را در بالاترین سطح سلسله مراتب کامپوننت‌های تعریف شده تعریف کردیم، در تمام زیر کامپوننت‌های آن نیز در دسترس خواهند بود.
بنابراین به فایل BlazorServer.App\App.razor که محل تعریف ریشه‌ی مسیریابی برنامه‌است، مراجعه کرده و کامپوننت آن‌را با کامپوننت توکار CascadingAuthenticationState محصور می‌کنیم:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
اینکار سبب می‌شود تا اطلاعات AuthenticationState، بین تمام کامپوننت‌های یک برنامه‌ی Blazor به اشتراک گذاشته شود.

اکنون اگر برنامه را اجرا کنیم، مشاهده خواهیم کرد که در اولین بار مراجعه‌ی به آن (پیش از لاگین)، لینک به صفحه‌ی خروج، نمایش داده نشده‌است؛ چون آن‌را در فرگمنت مخصوص Authorized قرار دادیم:



آزمایش نمایش منوی خروج برنامه

برای آزمایش برنامه، نیاز است ابتدا یک کاربر جدید را ثبت کنیم؛ چون هنوز هیچ کاربری در آن ثبت نشده‌است و همچنین کاربر پیش‌فرضی را هم به همراه ندارد. در مورد روش ثبت کاربران پیش‌فرض ASP.NET Core Identity، می‌توانید به مطلب «بازنویسی متد مقدار دهی اولیه‌ی کاربر ادمین در ASP.NET Core Identity توسط متد HasData در EF Core» مراجعه کنید و تمام نکات آن، در اینجا هم صادق است (چون پایه‌ی سیستم Identity مورد استفاده، یکی است و هدف ما در اینجا بیشتر بررسی نکات یکپارچه سازی آن با Blazor Server است و نه مرور تمام نکات ریز Identity).
بنابراین ابتدا از منوی بالای صفحه، گزینه‌ی Register را انتخاب کرده و کاربری را ثبت می‌کنیم. پس از ثبت نام، بلافاصله به منوی جدید زیر می‌رسیم که در آن گزینه‌های ورود و ثبت نام، مخفی شده‌اند و اکنون گزینه‌ی خروج از سیستم را نمایش می‌دهد:



بهبود تجربه‌ی کاربری خروج از سیستم

در همین حال که گزینه‌ی خروج نمایش داده شده‌است، اگر بر روی لینک آن کلیک کنیم، ابتدا ما را به صفحه‌ی مجزای logout هدایت می‌کند. سپس باید در این صفحه، مجددا بر روی لینک logout بالای آن کلیک کنیم. زمانیکه اینکار را انجام دادیم، اکنون صفحه‌ی دیگری را نمایش می‌دهد که به همراه پیام «خروج موفقیت آمیز از سیستم» است! در این پروسه، کاربر احساس می‌کند که کاملا از برنامه‌ی اصلی خارج شده‌است و همچنین مراحل طولانی را نیز باید طی کند.
مدیریت این مراحل توسط دو فایل زیر انجام می‌شوند:
Areas\Identity\Pages\Account\Logout.cshtml
Areas\Identity\Pages\Account\Logout.cshtml.cs

می‌خواهیم کدهای این دو فایل را به نحوی تغییر دهیم که اگر کاربری بر روی لینک logout برنامه‌ی اصلی کلیک کرد، به صورت خودکار logout شده و سپس مجددا به صفحه‌ی اصلی برنامه‌ی Blazor Server هدایت شود و مجبور نباشد تا مراحل طولانی یاد شده را تکرار کند.
به همین جهت ابتدا فایل Logout.cshtml.cs را حذف می‌کنیم؛ چون نیازی به آن نداریم. سپس محتوای فایل Logout.cshtml را به صورت زیر تغییر می‌دهیم:
@page
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager

@functions
{
    public async Task<IActionResult> OnGet()
    {
        if (SignInManager.IsSignedIn(User))
        {
            <p>You have successfully logged out of the application.</p>
            await SignInManager.SignOutAsync();
        }
        return Redirect("~/");
    }
}
با استفاده از سرویس SignInManager در ASP.NET Core Identity می‌توان یک کاربر را logout کرد که نمونه‌ای از آن‌را در اینجا مشاهده می‌کنید. در این حالت بررسی می‌شود که آیا کاربر جاری، به سیستم وارد شده‌است؟ اگر بله، کوکی‌های او حذف شده و سپس به صفحه‌ی اصلی برنامه، Redirect می‌شود. به این ترتیب به تجربه‌ی کاربری خروج بهتری خواهیم رسید.


نمایش User Claims، در یک برنامه‌ی Blazor Server

سیستم ASP.NET Core Identity، بر اساس User Claims کار می‌کند؛ اطلاعات بیشتر. پس از استفاده از CascadingAuthenticationState در بالاترین سطح برنامه، اطلاعات آن در سراسر برنامه‌ی Blazor Server هم قابل دسترسی است. برای مثال در کامپوننت Shared\LoginDisplay.razor، به نحو زیر می‌توان نام کاربر ثبت نام شده را که یکی از User Claims او است، نمایش داد:
<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name
        <a href="Identity/Account/Logout">Logout</a>
    </Authorized>



محدود کردن دسترسی به صفحات برنامه تنها برای کاربران اعتبارسنجی شده

پس از لاگین موفق به سیستم، اکنون می‌خواهیم دسترسی به صفحات تعریف اتاق‌ها و یا امکانات رفاهی هتل را تنها به کاربران لاگین شده، محدود کنیم. برای اینکار تنها کافی است از ویژگی Authorize استفاده کنیم. برای مثال به کامپوننت Pages\HotelRoom\HotelRoomList.razor مراجعه کرده و یک سطر زیر را به آن اضافه می‌کنیم:
@attribute [Authorize]
دسترسی به کامپوننتی که دارای دایرکتیو فوق باشد، تنها مختص به کاربران اعتبارسنجی شده‌ی سیستم است.

مشکل! با اینکه تمام کامپوننت‌های مثال جاری را به ویژگی Authorize مزین کرده‌ایم، اما ... کار نمی‌کند! و هنوز هم می‌توان بدون لاگین به سیستم، به محتوای آن‌ها دسترسی داشت.
برای رفع این مشکل، مجددا نیاز است کامپوننت BlazorServer.App\App.razor را ویرایش کرد:
<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
        <Found Context="routeData">
            @*<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />*@
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <NotAuthorized>
                    <p>Sorry, you do not have access to this page</p>
                </NotAuthorized>
            </AuthorizeRouteView>
        </Found>
        <NotFound>
            <LayoutView Layout="@typeof(MainLayout)">
                <p>Sorry, there's nothing at this address.</p>
            </LayoutView>
        </NotFound>
    </Router>
</CascadingAuthenticationState>
در اینجا RouteView پیشین را کامنت کرده و با AuthorizeRouteView، جایگزین کرده‌ایم. کار آن فعالسازی پردازش ویژگی Authorize افزوده شده‌ی به کامپوننت‌های برنامه‌است. همچنین در اینجا محتوای سفارشی را که در صورت درخواست یک چنین کامپوننت‌هایی نمایش داده می‌شود، در فرگمنت NotAuthorized مشاهده می‌کنید؛ که حتی می‌تواند یک کامپوننت مجزا هم باشد:



کدهای کامل این مطلب را از اینجا می‌توانید دریافت کنید: Blazor-5x-Part-22.zip
مطالب
شیوه کدنویسی در الکترون
در قسمت قبلی، انبوهی از کدهای جاوااسکرپیتی را دیدیم که در کنار یکدیگر نوشته شده بودند و این حجم کد، در یک برنامه‌ی واقعی‌تر افزایش پیدا می‌کند و بهتر است الگوی‌های کدنویسی آن را بهتر بشناسیم، تا کمتر به مشکل برخورد کنیم.

برای شناسایی ایرادات در کد و بهبود کیفیت کدها می‌توانید از ابزارهای دسته‌ی lint استفاده کنید که تعدادی از معروفترین این ابزارها (jslint ,cpplint ,eslint ,nodelint و ...) هستند.
برای نصب چنین ابزاری مثل lint می‌توانید به شکل زیر آن را نصب کنید:
npm install -g eslint
فلگ g در بالا به معنای global بوده و باعث می‌شود که eslint نصب شده، بر روی همه پروژه‌های node.js اجرا شود.
برای اولین بار نیاز است که آماده سازی ابتدایی برای این کتابخانه صورت بگیرد که با دستور زیر قابل انجام است:
eslint --init
سپس تعدادی سوال شخصی از شما پرسیده می‌شود که باید پاسخ داده شود که نمونه‌ای از آن‌را در زیر می‌بینید:
? How would you like to configure ESLint? Answer questions about your style
? Are you using ECMAScript 6 features? Yes
? Are you using ES6 modules? Yes
? Where will your code run? Browser, Node
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Double
? What line endings do you use? Windows
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in D:\ali\electron
از این پس با دستور زیر، قادرید کیفیت کدهای خود را بهبود بخشید
eslint .
یا
eslint index.js

می‌توانید این دستور را در در خصوصیت test، در بخش اسکریپت، به شکل زیر وارد کنید:
{
  "name": "electron",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "electron .",
    "test":"eslint ."
  },
  "author": "",
  "license": "ISC"
}
تا از این پس با دستور زیر قابل اجرا باشد:
npm test
بعد از اجرای دستور و طبق سوالات پاسخ داده شده، بهبود کیفیت کد به شکل زیر پاسخ داده می‌شود:
D:\ali\electron\index.js
   1:26  error  Strings must use doublequote                            quotes
  16:8   error  Strings must use doublequote                            quotes
  19:3   error  Expected indentation of 4 space characters but found 2  indent
  22:3   error  Expected indentation of 4 space characters but found 2  indent

✖ 4 problems (4 errors, 0 warnings)

برای نامگذاری فایل‌های js، به جای استفاده از _ از - استفاده کنید. این قاعده‌ای است که گیت هاب در نامگذاری فایل‌های js انجام می‌دهد. یعنی به جای عبارت dotnet_tips.js از عبارت dotnet-tips.js استفاده کنید. خاطرتان جمع باشد که هیچ فایل جاوااسکرپیتی رسمی در الکترون، خارج از این قاعده نامگذاری نشده است.
سعی کنید از جدیدترین قواعد موجود در ES6 استفاده کنید مانند:
let برای تعریف متغیرها
const برای تعریف ثابت ها
توابع جهتی به جای نوشتن عبارت function
استفاده از template string‌ها به جای چسباندن رشته‌ها از طریق عملگر +

برای نامگذاری متغیرها از همان قوانین تعریف شده برای node.js استفاده کنید که شامل موارد زیر است:
- موقعی که یک ماژول را به عنوان یک کلاس استفاده می‌کنید از متد CamelCase استفاده کنید مثل BrowserWindow
- موقعی که از یک ماژول که حاوی مجموعه‌ای از دستورات و api‌ها است استفاده می‌کنید، از قانون mixedCase استفاده کنید مثل app یا globalShortcut
- موقعی که یک api به عنوان خصوصیت یک شیء مورد استفاده قرار میگیرد، از mixedCase استفاده کنید؛ به عنوان مثال win.webContents یا fs.readFileSync
- برای مابقی اشیا مثل دستورات process و یا تگ webview که به شکل متفاوت‌تری مورد استفاده قرار می‌گیرند، از همان عنوان‌های خودشان استفاده می‌کنیم.

موقعی که api جدیدی را می‌سازید و قصد تعریف متدی را دارید بهتر است دو متد برای get و set تعریف شود، نسبت به حالتی که در کتابخانه جی کوئری مورد استفاده قرار می‌گیرد یعنی عبارت‌های زیر
setText('test');
getText();
نسبت به عبارت
.text([text]);
ترجیح داده می‌شوند.
مطالب
شروع به کار با AngularJS 2.0 و TypeScript - قسمت پنجم - بررسی چرخه‌ی حیات کامپوننت‌ها
در قسمت قبل، نگاهی داشتیم به 4 نوع مختلف data binding در AngularJS 2.0. در قسمت جاری می‌خواهیم کیفیت کدهای کامپوننت لیست محصولات را با strong typing بهبود بخشیده و همچنین چرخه‌ی حیات کامپوننت‌ها را به همراه ایجاد custom pipes بررسی کنیم.


افزودن strong typing به کامپوننت نمایش لیست محصولات

یکی از مزایای کار با TypeScript امکان انتساب نوع‌های مشخص یا سفارشی، به متغیرها و اشیاء تعریف شده‌است. برای مثال تاکنون هر خاصیت تعریف شده‌ای دارای نوع است. اما هنوز نوعی را برای آرایه‌ی محصولات تعریف نکرده‌ایم و نوع آن، نوع پیش فرض any است که برخلاف رویه‌ی متداول کار با TypeScript است.
برای تعریف نوع‌های سفارشی می‌توان از اینترفیس‌های TypeScript استفاده کرد. یک اینترفیس، قراردادی است که نحوه‌ی تعریف تعدادی خاصیت و متد به هم مرتبط را مشخص می‌کند. سپس کلاس‌های مختلف می‌توانند با پیاده سازی این اینترفیس، قرارداد تعریف شده‌ی در آن را عملی کنند. همچنین از اینترفیس‌ها می‌توان به عنوان یک data type جدید نیز استفاده کرد. البته ES 5 و ES 6 از اینترفیس‌ها پشتیبانی نمی‌کنند و تعریف آن‌ها در TypeScript صرفا جهت کمک به کامپایلر، برای یافتن خطاها، پیش از اجرای برنامه است و به کدهای جاوا اسکریپتی معادلی ترجمه نمی‌شوند.

در ادامه برای تکمیل مثال این سری، فایل جدید App\products\product.ts را به پروژه اضافه کنید؛ با این محتوا:
export interface IProduct {
    productId: number;
    productName: string;
    productCode: string;
    releaseDate: string;
    price: number;
    description: string;
    starRating: number;
    imageUrl: string;
}
یک اینترفیس، با واژه‌ی کلیدی interface تعریف شده و سپس نام آن تعریف می‌شود. مرسوم است این نام با I شروع شود؛ هرچند الزامی نیست و در بسیاری از اینترفیس‌های AngularJS 2.0 از این روش نامگذاری استفاده نشده‌است.
همچنین از آنجائیکه این اینترفیس را در یک فایل ts مجزا قرار داده‌ایم، برای اینکه بتوان از آن در سایر قسمت‌های برنامه استفاده کرد، نیاز است در ابتدای آن، واژه‌ی کلیدی export را نیز ذکر کرد.

پس از تعریف این اینترفیس، برای استفاده از آن به عنوان یک data type جدید، ابتدا ماژول آن import خواهد شد و سپس از نام آن به عنوان نوع داده‌ی جدیدی، استفاده می‌شود. برای این منظور فایل product-list.component.ts را گشوده و تغییرات ذیل را به آن اعمال کنید:
import { Component } from 'angular2/core';
import { IProduct } from './product';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html'
})
export class ProductListComponent {
    // as before ...

    products: IProduct[] = [
        // as before ...
    ]; 

    // as before ...
}
در اینجا ابتدا IProduct را import و سپس بجای any، نوع جدید IProduct را جهت مشخص سازی نوع آرایه‌ی products تعریف کرده‌ایم.
مزیت اینکار این است که برای مثال در اینجا اگر در لیست اعضای آرایه‌ی products، نام خاصیتی اشتباه تایپ شده باشد یا حتی بجای عدد، از رشته استفاده شده باشد، بلافاصله در ادیتور مورد استفاده، خطای مرتبط گوشزد شده و همچنین این فایل دیگر کامپایل نخواهد شد. به علاوه اینبار برای تعریف خواص اعضای آرایه‌ی products، ادیتور مورد استفاده، intellisense را نیز دراختیار ما قرار می‌دهد و کاملا مشخص است که چه اعضایی مدنظر هستند و نوع آن‌ها چیست.



مدیریت cssهای هر کامپوننت به صورت مجزا

هنگام ساخت یک قالب یا template، در بسیاری از اوقات نیاز است css مرتبط با آن نیز، منحصر به همان قالب بوده و نشتی نداشته باشد. برای مثال زمانیکه یک کامپوننت را درون کامپوننتی دیگر قرار می‌دهیم، باید css آن نیز در دسترس قرار بگیرد و css فعلی کامپوننت دربرگیرنده را بازنویسی نکند. روش‌‌های مختلفی برای مدیریت این مساله وجود دارند:
الف) تعریف شیوه نامه‌ها به صورت inline داخل خود قالب‌ها. این حالت، مشکلات نگهداری و استفاده‌ی مجدد را دارد.
ب) تعریف شیوه نامه‌ها در یک فایل خارجی css و سپس لینک کردن آن به صفحه‌ای اصلی یا index.html
در این حالت به ازای هر فایل، یکبار باید این تعریف در صفحه‌ای اصلی سایت صورت گیرد. همچنین این فایل‌ها می‌توانند مقادیر یکدیگر را بازنویسی کرده و بر روی هم تاثیر بگذارند.
ج) تعریف شیوه نامه‌ها به همراه تعریف کامپوننت. این روشی است که توسط AngularJS توصیه شده‌است و نگهداری و مقیاس پذیری آن ساده‌تر است.
تزئین کننده‌ی Component به همراه دو خاصیت دیگر به نام‌های styles و stylesUrl نیز می‌باشد.
در حالت استفاده از خاصیت styles، شیوه‌نامه‌ی متناظر با کامپوننت، در همانجا به صورت inline تعریف می‌شود:
 @Component({
    //...
    styles: ['thead {color: blue;}']
})
همانطور که مشاهده می‌کنید، خاصیت styles به صورت یک آرایه تعریف شده‌است و امکان پذیرش چندین شیوه نامه‌ی جدا شده‌ی با کاما را دارد.
روش بهتر، استفاده از خاصیت styleUrls است که در آن می‌توان مسیر یک یا چند فایل css را مشخص کرد:
 @Component({
     //...
     styleUrls: ['app/products/product-list.component.css']
})
این خاصیت نیز یک آرایه را می‌پذیرد و در اینجا می‌توان مسیر چندین فایل css را در صورت نیاز ذکر کرد.

برای آزمایش آن فایل جدید product-list.component.css را به پوشه‌ی products مثال این سری اضافه کنید؛ با این محتوا:
thead {
  color: #337AB7;
}
سپس لینک این فایل را به مجموعه خواص کامپوننت لیست محصولات (product-list.component.ts) اضافه می‌کنیم:
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css']
})
export class ProductListComponent {
   //...
در این حالت اگر برنامه را اجرا کنید، رنگ سرستون‌های جدول محصولات به نحو ذیل تغییر کرده‌اند:


یک نکته

شیوه نامه‌ای که به این صورت توسط AngularJS 2.0 اضافه می‌شود، با سایر شیوه نامه‌های موجود تداخل نخواهد کرد. علت آن‌را در تصویر ذیل که با استفاده از developer tools مرورگرها قابل بررسی است، می‌توان مشاهده کرد:


در اینجا AngularJS 2.0، با ایجاد ویژگی‌های سفارشی خودکار (attributes) میدان دید css را کنترل می‌کند. به این ترتیب شیوه نامه‌ی کامپوننت یک، که درون کامپوننت دو قرار گرفته‌است، نشتی نداشته و بر روی سایر قسمت‌های صفحه تاثیری نخواهد گذاشت؛ برخلاف شیوه نامه‌هایی که به صورت متداولی به صفحه‌ی اصلی سایت لینک شده‌‌اند.


بررسی چرخه‌ی حیات کامپوننت‌ها

هر کامپوننت دارای چرخه‌ی حیاتی است که توسط AngularJS 2.0 مدیریت می‌شود و شامل مراحلی مانند ایجاد، رندر، ایجاد و رندر فرزندان آن، پردازش تغییرات آن و در نهایت تخریب آن کامپوننت می‌شود. برای اینکه بتوان با برنامه نویسی به این مراحل چرخه‌ی حیات یک کامپوننت دسترسی یافت، تعدادی life cycle hook طراحی شده‌اند. سه مورد از مهم‌ترین life cycle hooks شامل موارد ذیل هستند:
الف) OnInit: از این hook برای انجام کارهای آغازین یک کامپوننت مانند دریافت اطلاعات از سرور، استفاده می‌شود.
ب) OnChanges: از آن جهت انجام اعمالی پس از تغییرات input properties استفاده می‌شود.
خواص ورودی و همچنین کار با سرور را در قسمت‌های بعدی بررسی خواهیم کرد.
ج) OnDestroy: از آن جهت پاکسازی منابع اختصاص داده شده استفاده می‌شود.

برای استفاده‌ی از این hookها، نیاز است اینترفیس آن‌ها را پیاده سازی کنیم. از آنجائیکه AngularJS 2.0 نیز با TypeScript نوشته شده‌است، به همراه تعدادی اینترفیس از پیش تعریف شده می‌باشد. برای مثال به ازای هر life cycle hook، یک اینترفیس تعریف شده در آن وجود دارد. برای نمونه اینترفیس hook ایی به نام OnInit، دقیقا همان OnInit، نام دارد (و با I شروع نشده‌است):
 export class ProductListComponent implements OnInit {
پس از ذکر implements OnInit در انتهای تعریف کلاس، اکنون باید ماژول مرتبط با آن نیز جهت شناسایی این اینترفیس import شود:
 import { Component, OnInit } from 'angular2/core';
و دست آخر متد ngOnInit تعریف شده‌ی در این اینترفیس باید توسط کلاس پیاده سازی کننده‌ی آن تامین شود:
ngOnInit(): void {
    console.log('In OnInit');
}
نام این متدها عموما شروع شده با ng و ختم شده به نام اینترفیس hook متناظر هستند؛ مانند ngOnInit فوق.

به عنوان تمرین، فایل product-list.component.ts را گشوده و سه مرحله‌ی implements سپس import و در آخر تعریف متد ngOnInit فوق را به آن اضافه کنید.
در ادامه برنامه را اجرا کرده و به کنسول developer tools مرورگر خود جهت مشاهده‌ی console.log فوق مراجعه کنید:



ساخت یک Pipe سفارشی جهت فعال سازی textbox فیلتر کردن محصولات

همانطور که در قسمت قبل نیز عنوان شد، کار pipes، تغییر اطلاعات حاصل از data binding، پیش از نمایش آن‌ها در رابط کاربری است و AngularJS 2.0 به همراه تعدادی pipe توکار است؛ مانند currency، percent و غیره. در ادامه قصد داریم یک pipe سفارشی را ایجاد کنیم تا بر روی حلقه‌ی ngFor* نمایش لیست محصولات تاثیرگذار شود و همچنین ورودی خود را از مقدار وارد شده‌ی توسط کاربر دریافت کند.
برای این منظور، یک فایل جدید را به نام product-filter.pipe.ts به پوشه‌ی products اضافه کنید. سپس کدهای آن‌را به نحو ذیل تغییر دهید:
import { PipeTransform, Pipe } from 'angular2/core';
import { IProduct } from './product';
 
@Pipe({
    name: 'productFilter'
})
export class ProductFilterPipe implements PipeTransform {
 
    transform(value: IProduct[], args: string[]): IProduct[] {
        let filter: string = args[0] ? args[0].toLocaleLowerCase() : null;
        return filter ? value.filter((product: IProduct) =>
            product.productName.toLocaleLowerCase().indexOf(filter) != -1) : value;
    }
}
برای تعریف یک pipe سفارشی جدید، کار با پیاده سازی اینترفیس PipeTransform شروع می‌شود. این اینترفیس دارای متدی است به نام transform که امضای آن‌را در کدهای فوق ملاحظه می‌کنید. کار آن اعمال تغییرات بر روی value دریافتی و سپس بازگشت آن است. بنابراین اولین پارامتر آن، مقادیر اصلی را که قرار است تغییر کنند، مشخص می‌کند. در اینجا نوع آن‌را از نوع اینترفیسی که در ابتدای بحث تعریف کردیم، تعیین کرده‌ایم. پارامتر دوم آن، لیست پارامترها و آرگومان‌های اختیاری این فیلتر را مشخص می‌کند.
برای مثال در اینجا می‌خواهیم شرایط فیلتر محصولات وارد شده‌ی توسط کاربر را دریافت کنیم.
خروجی این متد نیز از نوع آرایه‌ای از IProduct تعریف شده‌است؛ از این جهت که نتیجه نهایی فیلتر اطلاعات نیز آرایه‌ای از همین نوع است. کار این pipe پیاده سازی متد contains به صورت غیرحساس به کوچکی و بزرگی حروف است.
سپس بلافاصله بالای نام این کلاس، از یک decorator جدید به نام Pipe استفاده شده‌است تا به AngularJS 2.0 اعلام شود، این کلاس، صرفا یک کلاس معمولی نیست و یک Pipe است.
در ابتدای فایل هم importهای لازم جهت تعریف اینترفیس‌های مورد استفاده‌ی در این ماژول، ذکر شده‌اند.

اگر دقت کنید، الگوی ایجاد یک pipe جدید، بسیار شبیه است به الگوی ایجاد یک کامپوننت و از این لحاظ سعی شده‌است طراحی یک دستی در سراسر این فریم ورک بکار گرفته شود.

پس از تعریف این pipe سفارشی، برای استفاده‌ی از آن در یک template، به فایل product-list.component.html مراجعه کرده و سپس ngFor* آن‌را به نحو ذیل تغییر می‌دهیم:
 <tr *ngFor='#product of products | productFilter:listFilter'>
همانطور که ملاحظه می‌کنید، نام این pipe جدید که در decorator مرتبط با آن، توسط خاصیت name مشخص گردیده‌است، ذکر شده‌است. پس از آن یک : قرار گرفته‌است که مشخص کننده‌ی پارامتر اول این pipe است که در اینجا خاصیت listFilter تعریف شده‌ی در قسمت قبل را به آن انتساب داده‌ایم.
اگر از قسمت قبل به خاطر داشته باشید، این خاصیت را توسط two-way binding به روز می‌کنیم (اطلاعات وارد شده‌ی در textbox، بلافاصله به این خاصیت منعکس می‌شوند و برعکس):
 <input type='text'  [(ngModel)]='listFilter' />
تا اینجا این pipe را در قالب لیست محصولات بکار بردیم؛ اما کامپوننت آن نمی‌داند که این pipe را باید از کجا تامین کند. به همین جهت فایل product-list.component.ts را گشوده و خاصیت pipes را به نحو ذیل مقدار دهی کنید:
import { Component, OnInit } from 'angular2/core';
import { IProduct } from './product';
import { ProductFilterPipe } from './product-filter.pipe';
 
@Component({
    selector: 'pm-products',
    templateUrl: 'app/products/product-list.component.html',
    styleUrls: ['app/products/product-list.component.css'],
    pipes: [ProductFilterPipe]
})
export class ProductListComponent implements OnInit {
   //...
در اینجا دو کار صورت گرفته‌است. ابتدا افزودن pipe جدید ProductFilterPipe به لیست خاصیت pipes کامپوننت و سپس import ماژول آن درابتدای فایل.

اکنون اگر برنامه را اجرا کنید، خروجی ذیل را مشاهده خواهید کرد:


در اینجا چون مقدار فیلتر وارد شده‌ی پیش فرض، cart است، فقط ردیف Garden Cart نمایش داده شده‌است. اگر این مقدار را خالی کنیم، تمام ردیف‌ها نمایش داده می‌شوند و اگر برای مثال ham را جستجو کنیم، فقط ردیف Hammer نمایش داده می‌شود.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید: MVC5Angular2.part5.zip


خلاصه‌ی بحث

- اینترفیس‌ها یکی از روش‌های بهبود strong typing برنامه‌های AngularJS 2.0 هستند.
- جهت مدیریت بهتر شیوه‌نامه‌های هر کامپوننت بهتر است از روش styleUrls استفاده شود تا از نشتی‌های تعاریف شیوه‌نامه‌ها جلوگیری گردد.
- از life cycle hooks برای مدیریت رخدادهای مرتبط با طول عمر یک کامپوننت استفاده می‌شود؛ برای مثال دریافت اطلاعات از سرور و یا پاکسازی منابع مصرفی.
- تعریف یک pipe سفارشی با پیاده سازی اینترفیس PipeTransform انجام می‌شود. سپس نام این Pipe، به قالب مدنظر اضافه شده و در ادامه نیاز است کامپوننت استفاده کننده‌ی از این قالب را نیز از وجود این Pipe مطلع کرد.