مطالب
بهینه سازی فایلهای js و css در برنامه‌های ASP.NET با استفاده از Combres - قسمت اول
 یکی از مواردی که در توسعه وب نقش مهمی دارد، بهینه سازی فایلهای js  و css است که با فشرده سازی و کش کردن آنها می‌توان سرعت بارگذاری را تا حد چشمگیری افزایش داد. برای درک بهتر، به مثال زیر توجه کنید.
یک پروژه ساده را ایجاد می‌کنیم و فایل‌های CSS و js  را مانند شکل زیر، به آن اضافه می‌کنیم:


طبق تصویر فایل‌ها را به صفحه‌ای که ساختیم اضافه می‌کنیم:


پروژه را اجرا کرده و توسط افزونه‌ی firebug درخواست‌هایی را که از سرور شده‌است، بررسی می‌کنیم. مشاهده خواهید کرد که به ازای هر فایل، یک درخواست به سرور ارسال شده و هیچکدام از فایل‌ها توسط  وب سرور فشرده سازی نشده‌اند و اطلاعاتی در مورد کش، به هدر آنها اضافه نشده است.

برای رفع این موارد، روشهای گوناگونی وجود دارد که امروز قصد داریم این کار را توسط  کتابخانه Combress انجام دهیم !
نصب کتابخانه Combres
شما می‌توانید با استفاده از nuget این کتابخانه را به پروژه خود اضافه کنید. 
ایجاد فایل تنظیمات
پس از نصب کتابخانه، فایلی با نام combres.xml در فولدر app_data ساخته می‌شود که تمامی فعالیت‌های کتابخانه براساس آن انجام می‌شود و ساختار آن بصورت زیر است:
<?xml version="1.0" encoding="utf-8" ?>
<combres xmlns='urn:combres'>
  <filters>
    <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" />
  </filters>
  <resourceSets url="~/combres.axd"
                defaultDuration="30"
                defaultVersion="auto"
                defaultDebugEnabled="false"
                defaultIgnorePipelineWhenDebug="true"
                localChangeMonitorInterval="30"
                remoteChangeMonitorInterval="60">
    <resourceSet name="siteCss" type="css">
      <resource path="~/content/Site.css" />
      <resource path="~/content/anotherCss.css" />
      <resource path="~/scripts/yetAnotherCss.css" />
    </resourceSet>
    <resourceSet name="siteJs" type="js">
      <resource path="~/scripts/jquery-1.4.4.js" />
      <resource path="~/scripts/anotherJs.js" />
      <resource path="~/scripts/yetAnotherJs.js" />
    </resourceSet>
  </resourceSets>
</combres>
ResourceSet: با استفاده از هر ResourceSet می‌توانید آن مجموعه فایل را در یک درخواست از سرور دریافت کنید.
پارامتر url : برای تولید لینک فایل‌ها از آن استفاده میکند.
پارامتر defaultDuration : این عدد به تعداد روزهای پیشفرض برای کش کردن فایلها اشاره میکند.
پارامتر defaultVersion :در صورتی که مقدار آن auto باشد به ازای هر تغییر، آدرس جدیدی برای فایل موردنظر ایجاد میشود.
پارامتر defaultDebugEnabled :با استفاده از آن میتوانید debug mode را مشخص کنید. در صورتیکه مقدار آن auto باشد، این مقدار مستقیما از وب‌کانفیگ خوانده میشود.
مقادیر پیش فرض برای تمامی ResourceSet‌ها استفاده می‌شود و در صورت نیاز میتوان این مقادیر را برای هر ResourceSet بازنویسی کرد. فیلترها برای اعمال تغییراتی در فایل js و CSS استفاده می‌شوند که باید به این شکل معرفی شوند. در قسمت بعد با فیلترها بیشتر آشنا میشویم.
فایل cobmres.xml  را به منظور استفاده در پروژه به صورت زیر تغییر می‌دهیم.
<?xml version="1.0" encoding="utf-8" ?>

<combres xmlns='urn:combres'>
  <filters>
    <filter type="Combres.Filters.FixUrlsInCssFilter, Combres" />
  </filters>
  <resourceSets url="~/combres.axd"
                defaultDuration="30"
                defaultVersion="auto"
                defaultDebugEnabled="false"
                defaultIgnorePipelineWhenDebug="true"
                localChangeMonitorInterval="30"
                remoteChangeMonitorInterval="60">
    <resourceSet name="siteCss" type="css">
      <resource path="~/Styles/Site.css" />
    </resourceSet>
    <resourceSet name="siteJs" type="js">
      <resource path="~/Scripts/jquery-1.10.2.js" />
      <resource path="~/Scripts/jquery-1.10.2.min.js" />
    </resourceSet>
  </resourceSets>
</combres>
اگر از نیوگت برای نصب کتابخانه استفاده کرده باشید تغییرات مورد نیاز در فایل وب کانفیگ به صورت خودکار اعمال می‌شود؛ در غیر اینصورت باید قسمتهای زیر را به آن اضافه کنید.
<configuration>
  <configSections>
    <section name="combres" type="Combres.ConfigSectionSetting, Combres, Version=2.2, Culture=neutral, PublicKeyToken=1ca6b37997dd7536" />
  </configSections>
  <system.web>
    <pages>
      <namespaces>
        <add namespace="Combres" />
      </namespaces>
    </pages>
  </system.web>
  <combres definitionUrl="~/App_Data/combres.xml" />
  <appSettings>
    <add key="CombresSectionName" value="combres" />
  </appSettings>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="AjaxMin" publicKeyToken="21ef50ce11b5d80f" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.84.4790.14405" newVersion="4.84.4790.14405" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>
حال باید Route  مربوط به Combres را به RouteTable اضافه کنیم. در صورتیکه از نیوگت استفاده کرده باشید، کلاسی به فولدر app_start  اضافه شده است که با استفاده از WebActivator اینکار را در Application_Start انجام میدهد؛ در غیر اینصورت باید به صورت زیر این کار را انجام دهیم.
protected void Application_Start(object sender, EventArgs e)
        {
            RouteTable.Routes.AddCombresRoute("Combres Route");
        }
بعد از انجام مراحل قبل، نوبت به آن رسیده است که از لینکهای combres در صفحات خود استفاده کنیم. شیوه استفاده از آن در وب فرم به این صورت است:
<%@ Import Namespace="Combres" %>
<head runat="server">
    <%= WebExtensions.CombresLink("siteCss") %>
    <%= WebExtensions.CombresLink("siteJs") %>
</head>
و در MVC به صورت زیر می‌باشد:
<%= Url.CombresLink("siteCss") %>
<%= Url.CombresLink("siteJs") %>
در هر دو مورد نام ResourceSet برای تولید لینک به متد CombresLink ارسال میشود. پس از اجرای مجدد برنامه و با استفاده از firebug خواهیم دید که به ازای هر ResourceSet، یک درخواست به سرور ارسال شده است و حجم فایلها به صورت چشمگیری کاهش یافته است و اطلاعات مربوط به کش هم به آن اضافه شده است.

در ادامه می‌توانید فایل site.css قبلی و فعلی را مقایسه کنید!

در قسمت بعد با سازوکار combres و روش استفاده از فیلترها، بیشتر آشنا میشویم.

CombresProject-67bd3b7299f24c5484edc35537fa990b.zip 

نظرات مطالب
پَرباد - راهنمای اتصال و پیاده‌سازی درگاه‌های پرداخت اینترنتی (شبکه شتاب)
شما مسیر یک اکشن متد سمت سرور را برای Redirect به آن بدهید. پس از پایان کار آن (با هر روشی که هست؛ POST یا GET)، برنامه‌ی Angular باید از صفر load شود (این Redirect همان برنامه‌ی قبلی را که باز بوده نمایش نمی‌دهد و سبب بارگذاری مجدد کل برنامه می‌شود). در اینجا در متد ngOnInit از نتیجه‌ی کار کوئری بگیرید و آن‌را نمایش دهید.
نظرات مطالب
سازماندهی برنامه‌های Angular توسط ماژول‌ها
چند نکته‌ی تکمیلی در مورد بهبود تعاریف Shared Module و Core Module

الف) چگونه از import ثانویه‌ی Core Module در سایر ماژول‌ها جلوگیری کنیم؟
Core Module فقط باید در AppModule برنامه import شود و نه در هیچ‌جای دیگری. برای جلوگیری اتفاقی از این مساله می‌توان سازنده‌ای را به شکل زیر به آن اضافه کرد:
@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: [BrowserStorageService, AppConfigService] // global singleton services of the whole app will be listed here.
})
export class CoreModule {
  constructor( @Optional() @SkipSelf() core: CoreModule) {
    if (core) {
      throw new Error("CoreModule should be imported ONLY in AppModule.");
    }
  }
};
روش کار به این صورت است که خود CoreModule را به سازنده‌ی همان کلاس تزریق می‌کنیم! اگر وهله‌ای از آن قابل دسترسی بود، یعنی Angular پیشتر این ماژول را import کرده‌است. در این حالت با صدور خطایی این مشکل را گوشزد می‌کنیم.
از همین روش برای تشخیص singleton بودن یک سرویس نیز می‌توان استفاده کرد. خودش را به خودش تزریق می‌کنیم! اگر تزریقی صورت گرفت، یک خطا را صادر می‌کنیم.


ب) چگونه از وهله سازی مجدد سرویس‌های تعریف شده‌ی در Shared Module در سایر ماژول‌ها جلوگیری کنیم؟
هدف از قسمت providers در Shared Module تنها ارائه‌ی سرویس‌هایی جهت کامپوننت‌های اشتراکی آن است؛ وگرنه سرویس‌های سراسری برنامه در CoreModule تعریف می‌شوند و این ماژول ویژه نیز تنها یکبار و آن‌هم در AppModule برنامه import خواهد شد. اما در مورد Shared Module اینطور نیست و اگر این ماژول در یک lazy loaded module استفاده شود، سرویس‌های آن طول عمر متفاوتی را پیدا خواهند کرد (هر lazy loaded module یک injector و یک طول عمر خاص خودش را تعریف می‌کند).
در این حالت برای اینکه سرویس‌های Shared Module فقط در AppModule وهله سازی شوند و نه در هیچ‌جای دیگری، روش کار به صورت ذیل است:
- ابتدا آرایه‌ی providers را از تعاریف NgModule آن حذف می‌کنیم.
- سپس متد ویژه‌ای را به نام forRoot، به کلاس آن اضافه خواهیم کرد:
@NgModule({
  imports: [CommonModule],
  declarations: [], // common and shared components/directives/pipes between more than one module and components will be listed here.
  exports: [CommonModule], // common and shared components/directives/pipes between more than one module and components will be listed here.
  /* 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`. */]
    };
  }
};
متد forRoot به صورت استاتیک تعریف می‌شود و همچنین خروجی از نوع ModuleWithProviders دارد. توسط ModuleWithProviders سبب خواهیم شد، AppModule، این ماژول را به همراه آرایه‌ی providers آن import کند؛ اما سایر ماژول‌ها خیر.
سایر ماژول‌ها چون دسترسی به آرایه‌ی حذف شده‌ی providers این ماژول را ندارند، دیگر نمی‌توانند سرویس‌های آن‌را وهله سازی کنند. اما AppModule با فراخوانی ()SharedModule.forRoot در لیست import خود، تنها یکبار سبب وهله سازی سرویس‌های آن می‌گردد.
بنابراین در اینجا AppModule باید ()SharedModule.forRoot را import کند. سایر ماژول‌ها فقط SharedModule را import می‌کنند (بدون ذکر متد forRoot). به این ترتیب سرویس‌های آن تنها یکبار توسط AppModule در طول عمر برنامه به اشتراک گذاشته می‌شوند و در این حالت تفاوتی نمی‌کند که SharedModule در یک lazy loaded module استفاده شده‌است یا خیر.

روش تعریف متد forRoot توسط سیستم مسیریابی Angular نیز استفاده می‌شود و یک الگوی پذیرفته شده در بین توسعه دهندگان Angular است. برای مثال ()RouterModule.forRoot در AppModule تعریف می‌شود و ()RouterModule.forChild برای سایر ماژول‌ها.

نمونه‌ای از AppModule ، ShardModule و CoreModule
مطالب
PowerShell 7.x - قسمت هفتم - غنی‌سازی PowerShell
غنی‌سازی پاورشل
PowerShell توسط اپلیکیشن‌های مختلفی مانند VS Code یا Console قابل میزبانی است. با کمک این اپلیکیشن‌ها، دستورات به موتور PowerShell ارسال میشوند. این موتور است که دستورات را دریافت کرده و آنها را اجرا میکند و در نهایت خروجی، درون این اپلیکشن‌های میزبان، نمایش داده خواهند شد. علاوه بر آن، یک اپلیکیشن میزبان، مسئولیت بارگذاری و اجرای اسکریپت‌ها را با هربار اجرای شل، بر عهده دارد. درون این اسکریپت‌ها، فرصت این را خواهیم داشت تا ماژول‌های موردنیازمان را بارگذاری کنیم؛ دایرکتوری پیش‌فرض را تغییر دهیم، یکسری توابع را تعریف و یا فراخوانی کنیم. بنابراین این امکان را داریم تا موتور PowerShell را درون یک پراسس NET. میزبانی کنیم. در این‌حالت باید خودمان Input/Output را هندل کنیم. به عنوان مثال میتوانیم Error streams را درون یک Message Box نمایش دهیم، یا اینکه Information streams را درون یکسری RichText Box نمایش دهیم. در اینجا میتوانید مراحل پیاده‌سازی یک نمونه Host سفارشی را مشاهده کنید. 
برای مشاهده‌ی مشخصات اپلیکیشن میزبان میتوانید از دستور Get-Host یا از متغیر خودکار host$ نیز استفاده کنید: 
PS /> Get-Host

Name             : ConsoleHost
Version          : 7.3.0
InstanceId       : c3f625f0-dad8-4325-a0a1-f6499afecb8a
UI               : System.Management.Automation.Internal.Host.InternalHostUserInte
                   rface
CurrentCulture   : en-GB
CurrentUICulture : en-GB
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
DebuggerEnabled  : True
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace
یکسری از بخش‌های Host نیز درون سشن جاری، قابل سفارشی‌سازی هستند؛ به عنوان مثال: 
Function Write-Color {
    Param (
        [ValidateNotNullOrEmpty()]
        [string] $newColor
    )
    $oldColor = $host.UI.RawUI.ForegroundColor
    $host.UI.RawUI.ForegroundColor = $newColor
    If ($args) {
        Write-Output $args
    }
    Else {
        $input | Write-Output
    }
    $host.UI.RawUI.ForegroundColor = $oldColor
}
سفارش‌سازی Prompt
حالت پیش‌فرض نمایش prompt اینچنین است: 
# macOS
PS /{current_dir}>

# Windows
PS C:\>
این نحوه نمایش، توسط تابعِ خودکار Prompt تعیین میشود. این تابع قابل بازنویسی نیز میباشد و خروجی آن میتواند یک شیء یا یک رشته باشد. اما توصیه میشود خروجی به صورت یک رشته‌ی فرمت شده برگردانده شود: 
PS /> function prompt { "Hello, World > " }                 
Hello, World >
منظور از شیء نیز این است که حتی خروجی تابع Prompt میتواند اینچنین نیز باشد: 
PS /> Function prompt { Get-Process Slack }
در اینحالت خروجی که درون Prompt نمایش داده میشود، پیاده‌سازی پیش‌فرض متد ToString شیء استفاده شده خواهد بود: 
System.Diagnostics.Process (Slack)
بنابراین خروجی را میتوانید به هر حالتی که بخواهید نمایش دهید. به عنوان مثال در ادامه یک رشته‌ی فرمت شده را که حاوی زمان جاری، به همراه نام کامپیوتر میزبان است، بجای Prompt نمایش داده‌ایم: 
function prompt { 
$time = (Get-Date).ToShortTimeString() 
"$time $([net.dns]::GetHostName()):> "
}

# eg: 
11:00 Sirwans-MacBook-Pro.local:>
یک مثال دیگر نیز نمایش اطلاعات Git، درون پوشه‌ی جاری میباشد: 
Function Write-Branch {
    If (Test-Path .git) {
        $branch = git branch --show-current
        $lastCommitAuthor = git log -1 --pretty=format:"%an"
        If ($null -ne $lastCommitAuthor) {
            Return "($branch - latest commit written by 🤦👉 $lastCommitAuthor)"
        }
        Return "($branch)"
    }
    Else {
        "Not in a git repo"
    }
}


Function Prompt {
    $CurrentDirectory = Split-Path -Path $pwd -Leaf
    Write-Host "`nPS " -NoNewline -ForegroundColor Cyan
    Write-Host $($CurrentDirectory) -NoNewline -ForegroundColor Green
    Write-Host " $(Write-Branch) " -NoNewline -ForegroundColor Yellow
    Return '> '
}
در کد فوق ابتدا یک تابع را برای استخراج متادیتای گیت تهیه کرده‌ایم. ابتدا بررسی شده‌است که درون دایرکتوری جاری گیت، initialise شده باشد. سپس توسط دستور git branch —show-current برنچ جاری را دریافت کرده و به یک متغیر انتساب داده‌ایم. در ادامه با کمک git log آخرین کامیت (با کمک 1-) را استخراج کرده‌ایم. در ادامه درون تابع Prompt، دایرکتوری جاری را دریافت کرده و در نهایت آن را با نتیجه‌ی فراخوانی تابع Write-Branch ادغام کرده‌ایم: 


ذخیره‌سازی تقییرات شل درون پروفایل

نکته‌ایی که باید به آن دقت داشته باشید این است که تغییرات، تنها برای سشن جاری ذخیره خواهند شد و به محض بستن سشن، این تغییرات از حافظه پاک خواهند شد. همانطور که در قسمت قبل نیز اشاره شد، برای اینکه تغییرات را همیشه موقع باز کردن شل مشاهده کنیم، باید کدها را درون پروفایل ذخیره کنیم. به این معنا که هر وقت PowerShell را باز کنیم، توابع و کدهایی که درون پروفایل تعریف شده باشند، به صورت سراسری قابل استفاده خواهند بود. توسط متغیر خودکار Profile$ میتوانیم پروفایل جاری را مشاهده کنیم:  

PS /> $Profile

{HOME_USER}/.config/powershell/Microsoft.PowerShell_profile.ps1

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

PS /> $PROFILE | Get-Member -Type NoteProperty | Select-Object Name, Value

Name                   Value
----                   -----
AllUsersAllHosts
AllUsersCurrentHost
CurrentUserAllHosts
CurrentUserCurrentHost

مسیر هر کدام از پروفایل‌های فوق را میتوانید در اینجا مشاهده نمائید. همچنین توسط پرچم NoProfile- میتوانیم PowerShell را بدون بارگذاری هیچ پروفایلی باز کنیم: 

pwsh -NoProfile

بنابراین برای ذخیره‌ی تغییرات قبل، میتوانیم توابع تعریف شده را درون پروفایل موردنظر قرار دهیم، تا با هربار باز شدن سشن، کدهای موردنظر قابل استفاده باشند: 

PS /> code $PROFILE.CurrentUserCurrentHost

Function Write-Branch {
    # As before
}


Function Prompt {
    # As before
}

اگر از ماژول Posh برای تغییر ظاهر PowerShell استفاده کرده باشید، متوجه خواهید شد که این ماژول نیز به همین روال کار میکند؛ یعنی با هربار باز شدن سشن، این دستور برای بارگذاری Prompt سفارشی فراخوانی خواهد شد: 

oh-my-posh init pwsh | Invoke-Expression


مطالب
کامپوننت‌ها در AngularJS 1.5
در نسخه‌های  AngularJS 1.x عموماً با کمک کنترلرها و دایرکتیوها، می‌توانیم ویژگی‌های جدیدی را به اپلیکیشن‌هایمان اضافه کنیم؛ از دایرکتیوها برای ایجاد عناصر سفارشی HTML می‌توانستیم (می‌توانیم) استفاده کنیم. مشکل دایرکتیوها این است که برای ایجاد یک عنصر سفارشی ساده باید تنظیمات زیادی را انجام دهیم. در نسخه‌ی AngularJS 1.5 یک API جدید با نام کامپوننت معرفی شده است و این قابلیت، مدل ساده‌ی برنامه‌نویسی در کنترلرها و همچنین قدرت دایرکتیوها را در اختیارمان قرار خواهد داد. سینتکس این API خیلی شبیه به استفاده از کامپوننت‌ها در Angular 2.0 است. این یک مزیت مهم محسوب می‌شود؛ زیرا امکان مهاجرت از نسخه‌ی 1.5 به نسخه‌ی 2 را خیلی ساده خواهد کرد.

نحوه‌ی تعریف یک کامپوننت در AngularJS 1.5
همانند کنترلر و دایرکتیو، برای تعریف یک کامپوننت نیز باید از module API استفاده کنیم:

بنابراین برای ایجاد یک کامپوننت می‌توانیم به اینصورت عمل کنیم:

var app = angular.module("dntModule", []);
app.component("pmApp", {
  template: `Hello this is a simple component`
});

همانطور که مشاهده می‌کنید تابع component دو پارامتر را از ورودی دریافت خواهد کرد؛ نام کامپوننت و یک شیء برای تعیین تنظیمات کامپوننت. نام کامپوننت در اینجا به صورت camel case تعریف شده است؛ که در واقع یک convention برای Angular است. در این‌حالت برای استفاده‌ی از کامپوننت باید به اینصورت عمل کنیم:

<pm-app></pm-app>

در قسمت تنظیمات کامپوننت، در ساده‌ترین حالت یک template تعیین شده‌است که بیانگر نحوه‌ی رندر شدن یک کامپوننت می‌باشد. در اینحالت وقتی انگیولار به تگ فوق برسد، یک کامپوننت با نام pmApp را بارگذاری خواهد کرد.


ایجاد یک کامپوننت ساده

در ادامه می‌خواهیم یک کامپوننت ساده را جهت نمایش یکسری URL درون صفحه طراحی کنیم. ساختار صفحه index.html به صورت زیر خواهد بود:

<html ng-app="DNT">
<head>
    <meta charset="UTF-8">
    <title>Using Angular Component</title>
    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.min.css">
</head>
<body>
    <div class="container">
        <div class="row">
            <div class="col-md-3">
                <dnt-widget></dnt-widget>
            </div>
        </div>
    </div>
    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/app.js"></script>
    <script src="scripts/components/dnt-widget.component.js"></script>
</body>
</html>

در اینجا ابتدا توسط دایرکتیو ng-app، به Angular، ماژول‌مان را معرفی کرده‌ایم. سپس مداخل بوت‌استرپ و کتابخانه‌ی font-awesome را مشاهده می‌کنید. در ادامه، کتابخانه‌ی Angular و همچنین فایل app.js جهت معرفی ماژول برنامه معرفی شده‌است. در نهایت نیز یک فایل در مسیر ذکر شده برای قرار دادن کدهای کامپوننت در مسیر scripts/components اضافه شده‌است.

همانطور که ملاحظه می‌کنید، کامپوننت‌مان به صورت یک تگ سفارشی، درون صفحه قرار گرفته است:

<dnt-archive></dnt-archive>

در ادامه باید به Angular، نحوه‌ی تعریف این کامپوننت را اعلام کنیم. بنابراین یک فایل جاوا اسکریپتی را با نام dnt-widget.component، با محتویات زیر ایجاد کنید:

(function () {    
    "use strict";    
    var app = angular.module("DNT");    
    function DntArchiveController() {
      var model = this;      
      model.panel = {
          title: "Panel Title",
          items: [
              {
                  title: "Dotnettips", url: "https://www.dntips.ir"
              },
              {
                  title: "Google", url: "http://www.google.con"
              },
              {
                  title: "Yahoo", url: "http://www.yahoo.con"
              }
          ]
      };  
    };
    
    app.component("dntWidget", {
        templateUrl: '/scripts/components/dnt-widget.component.html',
        controllerAs: "model",
        controller: DntArchiveController
    });
} ());

توضیح کدهای فوق:

همانطور که مشاهده می‌کنید، برای پارمتر دوم کامپوننت، سه پراپرتی را تعیین کرده‌ایم:

templateUrl: به کمک این پراپرتی به Angular گفته‌ایم که محتوای قالب این کامپوننت، درون یک فایل HTML مجزا قرار دارد و به صورت linked template می‌باشد.

controllerAs: یکی از مزایای استفاده از کامپوننت‌ها، استفاده از controller as syntax می‌باشد. لازم به ذکر است اگر این پراپرتی را مقداردهی نکنیم، به صورت پیش‌فرض مقدار ctrl$ در نظر گرفته خواهد شد.

controller: مزیت دیگر کامپوننت‌ها، استفاده از کنترلرها است. با استفاده از این پراپرتی، یک کنترلر را برای کامپوننت‌مان رجیستر کرده‌ایم. در نتیجه زمانیکه‌ی Angular می‌خواهد کامپوننت‌مان را نمایش دهد، تابع تعریف شده برای این پراپرتی، جهت ایجاد یک controller instance فراخوانی خواهد شد. بنابراین هر پراپرتی یا تابعی که برای این controller instance تعریف کنیم، به راحتی درون ویوی آن جهت اعمال بایندینگ در دسترس خواهد بود (در نتیجه نیازی به scope$ نخواهد بود).

درون کنترلر نیز برای راحتی کار و همچنین به عنوان یک best practice، مقدار this را توسط یک متغیر با نام model، کپچر کرده‌ایم. در اینجا یک شیء را با نام panel نیز به مدل اضافه کرده‌ایم.


محتویات تمپلیت:

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">
            <span class="fa fa-archive"></span>
            {{ model.panel.title}}
        </h3>
    </div>
    <ul class="list-group">
        <li class="list-group-item" ng-repeat="item in model.panel.items">
            <span class="fa fa-industry"></span>
            <a href="{{ item.url }}">{{ item.title }}</a>
        </li>
    </ul>
</div>

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



همانطور که مشاهده می‌کنید استفاده از کامپوننت‌ها در Angular 1.5 در مقایسه با ایجاد دایرکتیوها و کنترلر‌ها خیلی ساده‌تر است. در واقع امکانات این API جدید تنها به مثال فوق ختم نمی‌شود؛ بلکه این API یک سیستم مسیریابی جدید را نیز معرفی کرده است که در قسمت‌های بعدی به آن نیز خواهیم پرداخت.


جهت تکمیل بحث نیز یک تقویم شمسی ساده را در اینجا قرار داده‌ام. می‌توانید جهت مرور بحث جاری به کدهای آن مراجعه کنید. البته هدف از تعریف این پروژه تنها یک مثال ساده برای معرفی کامپوننت‌ها بود و طبیعتاً باگ‌های زیادی دارد. اگر مایل بودید می‌توانید در توسعه‌ی آن مشارکت نمائید.


کدهای این قسمت را نیز از اینجا می‌توانید دریافت کنید.

مطالب
Angular Animation – بخش اول
متحرک سازی (Motion)، یکی از مفاهیم مهم در طراحی وب‌اپلیکیشن‌های مدرن محسوب می‌شود. امروزه استفاده از انیمیشن در طراحی رابط کاربری به عنوان جزء جدا ناپذیر UX محسوب می‌شود. برای درک اهمیت انیمیشن در طراحی، « نه فقط به برای زیبایی و چیدمان، بلکه به عنوان جزء جدایی ناپذیر UX » را ببینید. در Angular طراحی انیمیشن برای ساخت رابط کاربری نه تنها کاری سرگرم کننده، بلکه بسیار آسان است.

نصب Angular Animations  
برای شروع کار با Animation در Angular، ابتدا باید کتابخانه Angular Animation را توسط دستور زیر نصب کرد و سپس BrowserAnimationsModule را به ماژول اصلی برنامه اضافه کنید. 
> npm install @angular/animations@latest --save
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
  imports: [
    //…
    BrowserAnimationsModule
  ],
})

در اینجا مثال ساده‌ای را مشاهده خواهید کرد که انتقال (transition) المنت li را از حالت active به inactive، با یک انمیشن ساده همراه خواهد کرد. محل تعریف انیمیشن‌ها در Angular، داخل متادیتای component@ و توسط خصوصیت animations می‌باشد. در ابتدای کار باید توابع مختص انیمیشن را در فایل component مورد نظر خود توسط دستور import به شکل زیر وارد کنیم: 
import { trigger, state, style, transition, animate } from '@angular/animations';
برای پیاده سازی انیمیشن در component، وارد کردن تمامی توابع بالا اجباری هستند. در قدم بعدی با افزودن خصوصیت animations به متادیتای component@، کدهای مربوط به کامپوننت را به شکل زیر تغییر می‌دهیم: 
import { Component } from '@angular/core';
import { trigger,state,style,transition,animate } from '@angular/animations';

@Component({
  selector: 'app-root',
  template: `
    <div style='width:50%; margin:auto'>
      <ul>
        <li [@myState]="currentState"
          style='width:100px; padding:10px; list-style-type: none' 
            (click)="toggleState()">
          {{currentState}}
        </li>
      </ul>
    </div>
  `,
  styleUrls: ['./app.component.css'],
  animations: [
      trigger('myState', [
        state('inactive', style({
          backgroundColor: '#eee',
          transform: 'scale(1)'
        })),
        state('active',   style({
          backgroundColor: '#cfd8dc',
          transform: 'scale(1.1)'
        })),
        transition('inactive => active', animate('100ms ease-in')),
        transition('active => inactive', animate('100ms ease-out'))
      ])
    ]
})
export class AppComponent {
  currentState: string = 'inactive';
  toggleState():void{
    this.currentState = this.currentState === 'inactive' ? 'active' : 'inactive';
  }
}


توضیحات تکمیلی

trigger: جهت تعریف یک انیمیشن، از تابع trigger استفاده می‌کنیم. این تابع استفاده شده‌ی در خصوصیت animations، در پارامتر اول خود، یک نام یکتا را دریافت خواهد کرد و در پارامتر بعدی، لیستی از توابع وارد شده مختص انیمیشن را دریافت می‌کند.

نکته: خصوصیت animations، قابلیت دریافت چندین تابع trigger را برای داشتن چندین انیمیشن مجزا، دارا است.

state: با استفاده از این تابع قادر به تعریف وضعیت‌های مختلف خواهیم بود. انیمشن در Angular بر دو منطق وضعیت (state) و گذار (transition) پیاده سازی می‌شود. به عبارت دیگر انیمیشن در Angular بر روی گذار (transition) بین وضعیت‌ها (states) قابل تعریف هستند. این تابع در پارامتر اول خود یک نام و در پارامتر دوم خود تابع style را دریافت می‌کند.

style: با استفاده از این تابع قادر به تعریف استایلی برای وضعیت تعریف شده خواهیم بود.

transition: برای ساخت انیمیشن واقعی مورد استفاده قرار می‌گیرد. این تابع در پارامتر اول خود، مسیر حرکت بین دو حالت (state) را به صورت یک رشته دریافت کرده و در پارامتر دوم خود، تابع animate را دریافت می‌کند. در این مثال مسیر حرکت به صورت 'inactive => active' تعریف شده است. یعنی هنگام گذار از وضعیت inactive به وضعیت active، تابع animate در پارامتر دوم اجرا خواهد شد.

animate: این تابع دو پارامتر timing و styles را دریافت می‌کند. timing مقداری از جنس رشته (string) است که می‌تواند ترکیبی از مدت زمان (duration) با مقدار اختیاری تاخیر(delay) و مقدار easing باشد. به عنوان مثال مقدار کامل زیر را درنظر بگیرید: 

'1s 100ms ease-out'

در این اینجا مدت زمان، برابر ۱ ثانیه، تاخیر، ۱۰۰ میلی ثانیه و easing، مقدار ease-out خواهد بود و به معنی اجرای انیمیشن با افکت ease-out و در مدت زمان ۱ ثانیه و با تاخیر در شروع به مدت ۱۰۰ میلی ثانیه می‌باشد. در صورتیکه به این پارامتر مقداری عددی را ارسال کنیم، عدد به عنوان مدت زمان (duration) و بر مبنای واحد میلی ثانیه در نظر گرفته خواهد شد. تمامی مقادیر زیر برای ارسال به این پارامتر معتبر می‌باشند: 

500            // duration=500ms 
"1s"            // duration=1s
"100ms 0.5s"        // duration=100ms, delay=0.5s
"5s ease"        // duration=5s, easing=ease
"5s 10ms cubic-bezier(.17,.67,.88,.1)"    // duration=5s, delay=10ms, easing=cubic-bezier(.17,067,.88,.1)

پارامتر styles در تابع animate یکی از توابع style یا keyframes می‌باشد. البته این پارامتر اختیاری است و در قسمت نکات تکمیلی توضیح داده خواهد شد. در مثال بالا از این پارامتر صرف نظر شده است.

برای متصل کردن انیمیشن تعریف شده به المنت‌های موجود در صفحه، از خصوصیت [triggerName@] استفاده کنید. trigger تعریف شده در قطعه کد بالا myState نام دارد. بنابراین برای اینکه المنت li در گذار بین حالت‌ها، این انیمیشن را داشته باشد، باید [myState@] را به المنت خود اضافه کنید. همچنین مقدار حالت جاری را باید برای این خصوصیت تامین کرد. این مقدار می‌تواند یک رشته استاتیک یا یک عبارت محاسبه شده توسط یک تابع یا یک متغیر باشد.

همانطور که در مثال بالا مشاهده می‌کنید، با استفاده از متغیر currentState، المنت li در ابتدا در حالت inactive قرار دارد. با کلیک اول بر روی المنت، تابع toggleState باعث تغییر وضعیت المنت از وضعیت inactive به وضعیت active خواهد شد (inactive=>active) بنابراین انیمیشن زیر اجرا خواهد شد.

 transition('inactive => active', animate('100ms ease-in'))

با کلیک دوباره، المنت از وضعیت active به inactive خواهد رفت (active=>inactive)، بنابراین انیمیشن زیر اجرا می‌شود.

 

نکات تکمیلی

 در صورتیکه در تابع transition، پارامتر دوم برای حالتهای مختلف یکسان باشد، برای مثال رفتن از حالت active به حالت inactive و برعکس، می‌توان از روش زیر استفاده کرد. 
transition('inactive => active, active => inactive', animate('100ms ease-out'))

یا به شکل ساده‌تر: 

transition('inactive <=> active', animate('100ms ease-out'))


کاراکتر * جایگزین تمامی حالتهای موجود در برنامه خواهد بود. برای مثال:

'* <= active': بر روی تمامی گذارهای از حالت active به هر حالت دیگر، اعمال خواهد شد.

'active <= *': بر روی تمامی گذارهای از هر حالت به حالت active، اعمال خواهد شد.

 '* <= *': بر روی تمامی گذارها اعمال خواهد شد. 



همانطور که قبلا ذکر شد، تابع animate در پارامتر دوم خود یک تابع style یا keyframes را دریافت می‌کند. در صورتیکه بخواهیم در مدت زمان اجرای انیمیشن بر روی المنت، استایلی را نیز همراه کنیم، می‌توانیم از تابع style استفاده کنیم. اما این استایل بعد از اتمام انیمیشن بر روی المنت باقی نخواهد ماند؛ چون حالت (state) مقصد برای خود استایل تعریف شده‌است. علاوه بر این، در تابع transition می‌توان به شکل زیر یک استایل خطی را نیز تعریف کرد: 

transition('inactive => active', [
  style({
    backgroundColor: '#cfd8dc',
    transform: 'scale(1.3)'
  }),
  animate('80ms ease-in', style({
    backgroundColor: '#eee',
    transform: 'scale(1)'
  }))
]),

این تعریف استایل در تابع transition در شروع گذار بلافاصله بر روی المنت اعمال خواهد شد و در طول انیمیشن استایل را از دست خواهد داد و به استایل مقصد خواهد رسید.

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

مطالب
احراز هویت و اعتبارسنجی کاربران در برنامه‌های 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 را اجرا کنید، تا توکن سرور برنامه نیز فعال شود.
نظرات مطالب
ساختار پروژه های Angular
زمانی که تعداد فایل‌ها و دایرکتوری‌ها در پروژه زیاد می‌شود(البته این جزء جدانشدنی پروژه‌های مقیاس بزرگ است) برای جلوگیری از لود یک باره کنترلرها و دایرکتیوها، بهتر از lazy loading برای لود فایل‌های مورد نیاز استفاده شود.  متاسفانه Angular به صورت رسمی از lazy loading پشتیبانی نمی‌کند اما با کمی تغییر در ساختار و استفاده از کتابخانه‌های جانبی مثل requireJs یا ScriptJs می‌توان به این مهم دست یافت.
(با عنوان این مطلب که قصد داشتم این مورد را طی یک پست جداگانه بررسی کنم)
برای مثال:
ابتدا ماژول app خود را به این شکل تنظیم کنید:
(function()
{
    var app = angular.module('app', []);

    app.config(function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide)
    {
        app.controllerProvider = $controllerProvider;
        app.compileProvider    = $compileProvider;
        app.routeProvider      = $routeProvider;
        app.filterProvider     = $filterProvider;
        app.provide            = $provide;

    });
})();

با استفاده از سرویس controllerProvider$ می‌توان چرخه ساخت کنترلر را به دست گرفت. هم چنین سرویس compileProvider$ برای نمونه سازی دایرکتیو‌ها و filterProvider$ برای فیلتر‌ها استفاده می‌شوند.
ساخت کنترلر‌ها و دایرکتیو‌ها نیز به صورت زیر انجام خواهد شد:
angular.module('app').controllerProvider.resgister('SomeLazyController', function($scope)
{
    $scope.key = '...';
});
و هم چنین یک نمونه از ساخت directive
$compileProvider.directive('SomeLazyDirective', function()
{
    return {
        restrict: 'A',
        templateUrl: 'templates/some-lazy-directive.html'
    }
})

فقط کافیست در هنگام پیاده سازی routing (که در این مقاله شرح داده شده است) نوع بارگذاری کنترلرها و دایرکتیو و ... را به صورت lazy انجام دهید :
$routeProvider.when('/about', {templateUrl:'views/about.html', resolve:{deps:function($q, $rootScope)
{
    var deferred = $q.defer();
    var dependencies =
    [
        'controllers/AboutViewController.js',
        'directives/some-directive.js'
    ];
 
    //*نکته اول 
    $script(dependencies, function()
    {
        // *نکته دوم
        $rootScope.$apply(function()
        {
            deferred.resolve();
        });
    });
 
    return deferred.promise;
}}})
*نکته اول: تمام وابستگی‌ها توسط scriptJs مدیریت می‌شوند.
*نکته دوم: تمام وابستگی‌ها مروبط به این scope بعد از فراخوانی تابع deffered.resolved بارگذاری خواهند شد.
نقطه شروع برنامه نیز به صورت زیر است:
$script(['appModule.js'], function()
{
    angular.bootstrap(document, ['app'])
});
angular.bootstrap
مطالب
مفهوم Workspace در Angular
Workspace، اولین بار در انگیولار 6 معرفی شد. Angular Workspace به شما اجازه می‌دهد کل web application خود را به چندین پروژه‌ی کوچکتر سازماندهی کنید. بیشتر پروژه‌های کوچک انگیولار تنها شامل یک یا دو پروژه، به ازای هر Workspace می‌باشند. برای مثال وقتی شما یک پروژه‌ی جدید انگیولار را با Angular CLI ایجاد می‌کنید، در این حالت Workspace شامل یک app  واقعی و یک پروژه e2e tests می‌باشد. 

مستندات انگیولار یک پروژه را به صورت زیر توصیف می‌کند: 
A set of files that comprise a standalone app, a library, or a set of end-to-end (e2e) tests.


درست همانند مفهوم یک کتابخانه در توسعه نرم افزار، یک کتابخانه اشاره دارد به مجموعه‌ای از کد‌های با قابلیت استفاده‌ی مجدد که می‌توانند بین پروژه‌های مختلف به اشتراک گذاشته شوند؛ هم چنین Angular CLI این کار را برای شما آسان می‌کند تا کتابخانه خودتان را در یک مرکز ثبت مثل npm  انتشار دهید تا توسط دیگر توسعه دهندگان استفاده شود.
نوع Application اشاره به یک Application مستقل انگیولار دارد.    


Create Empty Workspaces  

در انگیولار 7 یک CLI option جدید ( create-application-- ) معرفی شد که شما می‌توانید با اضافه کردن این option به دستور ng new، سبب ایجاد یک Workspace خالی شوید. این option  با دستور ng new استفاده می‌شود و می‌تواند مقدار true یا false را دریافت کند که به صورت پیش فرض به true  تنظیم شده است. بنابراین وقتی شما یک پروژه‌ی جدید را ایجاد می‌کنید، سبب ایجاد یک پروژه‌ی اولیه انگیولار در Workspace هستید؛ همچنین می‌توانید create-application-- را به false تنظیم کنید تا CLI یک پروژه اولیه انگیولار را در Workspace  ایجاد نکند .

اکنون اجازه دهید آن را در عمل ببینیم: 
مطمئن شوید که نگارش انگیولار شما، 7 یا بالاتر باشد:
ng --version

Command Prompt را باز کرده و سپس درستور زیر را در آن اجرا کنید:
ng new angular-apps --create-application=false
سپس Angular CLI این هشدار را به شما می‌دهد که آیا تمایل دارید، Angular routing را اضافه کنید و همچنین از کدام stylesheet format تمایل دارید که استفاده کنید؛ سپس فایل‌های زیر تولید خواهند شد:  
angular-apps/README.md
angular-apps/angular.json
angular-apps/package.json
angular-apps/tsconfig.json
angular-apps/tslint.json
angular-apps/.editorconfig
angular-apps/.gitignore
و همچنین CLI تمام وابستگی‌های انگیولار را در یک پوشه به نام node_modules نصب خواهد کرد.


اکنون به Command Prompt برگردیم و وارد Angular Workspace خودمان شویم:
cd angular-apps

در ادامه یک پروژه جدید را با دستور زیر ایجاد می‌کنیم:
ng generate application admin
دستور بالا سبب ایجاد دو پروژه می‌شود ( admin  و admin-e2e )، در پوشه Projects. 
 همچنین می‌توانید یک کتابخانه را در Workspace خود با استفاده از دستور زیر ایجاد کنید:
ng generate library ngx-qrcode
ngx-qrcode نام کتابخانه می‌باشد. این کتابخانه در پوشه projects، درست مثل مابقی Application ها اضافه خواهد شد. 
بعد از ایجاد کردن چندین Application بدون Application پیش فرض برای Workspace خود، می‌توانید اقدام به ایجاد modules ، service ، components  و ... در یک پروژه مشخص با استفاده از project option-- کنید. 

برای نمونه یک سرویس را در پروژه admin ایجاد می‌کنیم:
ng generate service auth --project=admin
که سبب ایجاد دو فایل زیر می‌شود:
projects/admin/src/app/auth.service.spec.ts
projects/admin/src/app/auth.service.ts

Serving Specific Apps in Your Workspace 

جهت به خدمت گرفتن یک Application مشخص در Workspace دستور زیر را اجرا کنید:
ng serve admin
admin نام Application شما می‌باشد که می‌خواهید آن را به خدمت بگیرید. 


نکته
اگر در Command Prompt دستور زیر را وارد کنید: 
ng new
سریعا هشدار زیر را به شما می‌دهد:
What name would you like to use for the new workspace and initial project?
از شما می‌خواهد که نام Workspace  مورد نظرتان را وارد کنید و بعد از آن همان دو هشدار Routing و stylesheet format  می‌باشد که سبب ایجاد یک پروژه اولیه انگیولار در Workspace می‌شود.


نتیجه گیری 
در این مطلب متوجه مفهوم Angular Workspace و روش ایجاد کردن یک Workspace خالی شدیم و همچنین چگونه یک Application و یک Library را در یک Workspace   ایجاد کنیم.