مطالب
چگونه پروژه‌های Angular ی سبکی داشته باشیم - قسمت دوم
همانطور که در مطلب قبل دیدیم، با اضافه کردن style ها  و فایل‌های Javascript ای حجم صفحات خروجی رو به افزایش بودند. اولین راه بهینه سازی، استفاده از feature module است. می‌خواهیم هر زمان که به ماژولی نیاز داریم، آن را import و استفاده کنیم.
در ادامه دو فایل زیر را برای استفاده از ماژول‌های Angular Material و Kendo Angular UI در مسیر app\modules تعریف می‌کنیم.
// angular-material-feature.module.ts

import { NgModule } from '@angular/core';

// Import angular kendo angular
import {
  MatFormFieldModule, MatInputModule,
  MatButtonModule, MatButtonToggleModule,
  MatDialogModule, MatIconModule,
  MatSelectModule, MatToolbarModule,
  MatDatepickerModule,
  DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatTableModule, MatCheckboxModule, MatRadioModule, MatCardModule, fadeInContent,
  MatListModule, MatProgressBarModule, MatTabsModule
} from '@angular/material';

import {
  MatSidenavModule,
  MatSlideToggleModule,
} from '@angular/material';

import { MatMenuModule } from '@angular/material/menu';

@NgModule({
  declarations: [
   // KendoGridPaginationComponent
  ],
  imports: [

    MatSidenavModule,
    MatSlideToggleModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule, MatButtonToggleModule,
    MatDialogModule, MatIconModule,
    MatSelectModule, MatToolbarModule,
    MatDatepickerModule,
    MatCheckboxModule,
    MatRadioModule,
    MatCardModule,
    MatMenuModule,
    MatListModule,
    MatProgressBarModule,
    MatTabsModule
  ],


  exports: [


    MatSidenavModule,
    MatSlideToggleModule,
    MatInputModule,
    MatFormFieldModule,
    MatButtonModule, MatButtonToggleModule,
    MatDialogModule, MatIconModule,
    MatSelectModule, MatToolbarModule,
    MatDatepickerModule,
    MatCheckboxModule,
    MatRadioModule,
    MatCardModule,
    MatMenuModule,
    MatListModule,
    MatProgressBarModule,
    MatTabsModule


  ],
  providers: [


  ]

})
export class AngularMaterialFeatureModule {
}
و 
// kendo-feature.module.ts

import { NgModule } from '@angular/core';

// Import kendo angular ui
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { GridModule, ExcelModule } from '@progress/kendo-angular-grid';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
import { DialogsModule } from '@progress/kendo-angular-dialog';
import { RTL } from '@progress/kendo-angular-l10n';
import { LayoutModule } from '@progress/kendo-angular-layout';

import { WindowService, WindowRef, WindowCloseResult, DialogService, DialogRef, DialogCloseResult } from '@progress/kendo-angular-dialog';
import { SnotifyModule, SnotifyService, SnotifyPosition, SnotifyToastConfig, ToastDefaults } from 'ng-snotify';


@NgModule({
  declarations: [
   
  ],
  imports: [


    ButtonsModule,
    GridModule,
    ExcelModule,
    DropDownsModule,
    InputsModule,
    DateInputsModule,
    DialogsModule,
    LayoutModule,

    SnotifyModule,
    


  ],
  exports: [

    ButtonsModule,
    GridModule,
    ExcelModule,
    DropDownsModule,
    InputsModule,
    DateInputsModule,
    DialogsModule,
    LayoutModule,

  ],
  providers: [

    
    { provide: 'SnotifyToastConfig', useValue: ToastDefaults },
    SnotifyService,


    // Enable Right-to-Left mode for Kendo UI components
    { provide: RTL, useValue: true },
  ]

})
export class KendoFeatureModule {
}

 از این پس، هر زمانیکه به ماژولی نیاز داریم، feature module آن را import می‌کنیم.

در ادامه‌ی بهینه سازی، از قابلیت Lazy-Loading استفاده می‌کنیم و فایل ‌های زیر را جهت پیاده سازی lazy-loading تغییر می‌دهیم.
محتویات فایل styles.scss را پاک می‌کنیم. 
فایل app.module.ts : 
// app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserAnimationsModule,
    RouterModule,
    AppRoutingModule,

  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
 و فایل app.component.ts : 
// app.component.ts

import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  encapsulation: ViewEncapsulation.None,
})
export class AppComponent {
  title = 'HowToKeepAngularDeploymentSizeSmall';

  constructor() {

  }

}
 و  محتویات فایل app.component.scss  را پاک می‌کنیم. 
و فایل app.component.html : 
// app.component.html

<router-outlet></router-outlet>
و فایل app-routing.module.ts 
// app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [

  { path: '', loadChildren: './projects/home/home.module#HomeModule' } 

];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

نکته 1: سعی کنید کامپوننت‌های کمتری را به app.module.ts اضافه کنید.
نکته 2: stylesheet‌ها را در ماژول‌هایی که به آنها نیاز دارند import کنید.

تا به اینجای کار، ماژول اصلی پروژه را سبک کردیم. حال می‌خواهیم صفحه‌ی اول پروژه را به module ای به نام home، در مسیر app\projects\home مسیردهی کنیم. 
home.module.ts :
// home.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import {AngularMaterialFeatureModule , KendoFeatureModule} from '../../modules/index';
import {HomeRoutingModule } from './home.routing';

import { HomeComponent } from './home.component';
import { IndexComponent } from './pages/index/index.component';

import {LoginComponent } from './pages/login/login.component';

@NgModule({

  declarations: [
    HomeComponent,
     IndexComponent,
    LoginComponent
    ],
  imports: [
    CommonModule,
    HomeRoutingModule,
    AngularMaterialFeatureModule
  ],
  providers:[

  ],
  entryComponents:[
    LoginComponent

  ]

})
export class HomeModule { }
home.routing.ts :
// home.routing.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { HomeComponent } from './home.component';
import { IndexComponent } from './pages/index/index.component';

const routes: Routes = [
  {
    path: "",
    component: HomeComponent,
    children: [
      {
        path: "",
        component: IndexComponent,
        data: {
          breadcrumb: " Index "
        }
      },
      {
        path: "about",
        loadChildren: "./pages/aboutus/aboutus.module#AboutusModule"
      },
      {
        path: "blog",
        loadChildren: "./pages/blog/blog.module#BlogModule"
      },
      {
        path: "blog-detail",
        loadChildren: "./pages/blogdetail/blogdetail.module#BlogdetailModule"
      },
      {
        path: "pricing",
        loadChildren: "./pages/pricing/pricing.module#PricingModule"
      },
      {
        path: "contact",
        loadChildren: "./pages/contact/contact.module#ContactModule"
      }
    ]
  }
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule],
})
export class HomeRoutingModule  {
}

و فایل home.component.ts :
// home.component.ts

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';

import { LoginComponent } from './pages/login/login.component';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss'],
  encapsulation: ViewEncapsulation.None
})
export class HomeComponent implements OnInit {

  constructor(
    private dialog: MatDialog) { }

  ngOnInit() {

  }


  public toggleModal(){
    let dialogRef = this.dialog.open(LoginComponent, {
    });
  }

}
و فایل  home.component.scss : 
// home.component.scss

@import '../../../assets/home/css/themify-icons.css';
@import '../../../assets/home/css/font-awesome.min.css';
@import '../../../assets/home/css/set1.css';

@import '../../../assets/home/css/bootstrap.min.css';
@import '../../../assets/home/css/style.css';
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
و فایل  home.component.html :
// home.component.html

<!--============================= HEADER =============================-->
<div>
  <div>
    <div>
      <div>
        <div>
          <nav>
            <a href="index.html"><img src="../../../assets/home/images/logo.png" alt="logo"></a>
            <button type="button" data-toggle="collapse" data-target="#navbarNavDropdown"
              aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
              <span></span>
            </button>
            <div id="navbarNavDropdown">
              <ul>

                <li>
                  <a  target="_blank" [routerLink]="['/dashboard']">Dashboard</a>
                </li>


                <li>
                  <a [routerLink]="['/about']">About</a>
                </li>

                <li>
                  <a [routerLink]="['/contact']">Contact</a>
                </li>

                <li>
                  <a [routerLink]="['/pricing']">Pricing</a>
                </li>

                <li>
                  <a href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                    Blog
                    <span></span>
                  </a>
                  <div>
                    <a [routerLink]="['/blog']">Blog Listing</a>
                    <a [routerLink]="['/blog-detail']">Blog Detail</a>
                  </div>
                </li>
                <li>
                  <a (click)='toggleModal()' style="cursor: pointer;">Login</a>
                </li>
                <li><a href="add-listing.html"><span></span> Add
                    Listing</a></li>
              </ul>
            </div>
          </nav>
        </div>
      </div>
    </div>
  </div>
</div>

<!--//END HEADER -->

<router-outlet></router-outlet>


<!--============================= FOOTER =============================-->
<footer>
  <div>
    <div>
      <div>
        <div>
          <i aria-hidden="true"></i>
          <p>503 Sylvan Ave, Mountain View<br> CA 94041, United States</p>
        </div>
      </div>
      <div>
        <div>
          <img src="../../../assets/home/images/footer-logo.png" alt="#">
        </div>
      </div>
      <div>
        <ul>
          <li><a href="#"><i aria-hidden="true"></i></a></li>
          <li><a href="#"><i aria-hidden="true"></i></a></li>
          <li><a href="#"><i aria-hidden="true"></i></a></li>
          <li><a href="#"><i aria-hidden="true"></i></a></li>
          <li><a href="#"><i aria-hidden="true"></i></a></li>
        </ul>
      </div>
    </div>
    <div>
      <div>
        <div>
          <p>Copyright © 2017 Listed Inc. All rights reserved</p>
          <a href="#">Privacy</a>
          <a href="#">Terms</a>
        </div>
      </div>
    </div>
  </div>
</footer>
<!--//END FOOTER -->

پروژه را با دستور ng serve --open --prod  اجرا کرده و خروجی را بررسی می‌کنیم. حجم خروجی صفحه اول را همراه با قالبی از پیش طراحی شده در تصویر زیر می‌بینیم:


سورس کامل پروژه در HowToKeepAngularDeploymentSizeSmall قرار دارد.
اشتراک‌ها
پیاده سازی RSS Reader در ASP.NET MVC
 In this post I am going to explain you how we can create a basic RSS Reader with the help of Linq-To-Xml and ASP.NET MVC Razor. 
پیاده سازی RSS Reader در ASP.NET MVC
مطالب
شروع کار با webpack - قسمت چهارم
در سه قسمت قبلی با مفاهیم گوناگونی از وبپک آشنا شدیم که می‌توان به ساخت باندل‌های ساده و پیشرفته، استفاده از لودرها، فایل‌های پیکربندی  و ... اشاره کرد.

بارگذاری فایل‌های css به کمک وبپک

همان طور که قبلا اشاره شد، هسته‌ی وبپک به خودی خود بجز باندل کردن اسکریپت‌های خام جاوا اسکریپت و در نهایت Minify کردن آنها، قادر به انجام کار دیگری نیست. همین طور ذکر شد، برای اینکه وبپک فوت و فن جدیدی را یاد بگیرد، از Loader‌ها استفاده می‌کنیم. وارد کردن فایل‌های css به باندل نیز به کمک Loader‌ها خواهد بود. برای اینکار به دو Loader احتیاج داریم و به کمک npm آنها را به عنوان وابستگی برای توسعه‌ی پروژه نصب خواهیم کرد .
// نصب لودر‌های مورد نظر
npm install css-loader style-loader -D
لودر css وظیفه‌ی بارگذاری فایل‌های css و همچنین وابستگی آنها ( import هایی که در این فایل‌ها انجام شده) را دارد و در نهایت css نهایی را بر می‌گرداند.
لودر style نیز وظیفه‌ی اضافه کردن ماژول اکسپورت شده را به DOM، به عنوان یک style خواهد داشت.
در ساختار پروژه نیز تغییراتی انجام شده و فایل‌ها و لودر‌های اضافی حذف شده‌اند تا تمرکز بر روی مطلب بیشتر باشد. مانند قسمت اول، یک تک صفحه‌ی index.html را خواهیم داشت که دارای محتوای زیر است:
//index.html file
<html>
  <head>
    <title>webpack part 4</title>

  </head>
  <body>
    <h1>webpack is awesome</h1>
    <p>part 4 of tutorial</p>
    <div>i have a background</div>
    <h1>تست فونت !</h1>

    <script src="/assets/js/bundle.js">
    </script>
  </body>
</html>
اگر دقت کنید، در این فایل لینکی به فایل‌های استایل، مانند css وجود ندارد و هدف این است که این فایل‌ها توسط وبپک پردازش شوند.
برای شروع به فایل پیکربندی وبپک مراجعه کرده و دو Loader ی را که برای این کار، بالاتر در پروژه نصب کردیم، به وبپک معرفی می‌کنیم.
//webpack.config.js
var path = require("path");
var webpack = require("webpack");

module.exports = {
    context: path.resolve("js"),
    entry: ['./main.js']
    , output: {
        path: path.resolve("build/js"),
        publicPath: "assets/js",
        filename: 'bundle.js'
    },
    devServer: {
        contentBase: "assets"
    }
    , watch: true
    , module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader'
            }
        ]
    }
}
در قطعه کد بالا محتوای فایل پیکربندی قابل مشاهده است. مشخص است که لودر جدیدی را به وبپک معرفی کرده‌ایم که به دنبال فایل‌هایی با پسوند css در پروژه می‌گردد. ولی در کلید loader که وظیفه‌ی فراخوانی لودر مورد نظر را در مواجهه با این فایل‌ها دارد، کمی با قبل تفاوت وجود دارد.
loader:'style-loader!css-loader'
در قطعه کد بالا، نشانگر ! در مواقعی استفاده می‌شود که قصد داریم لودر‌ها را به صورت زنجیره‌ای استفاده کنیم. مثلن در اینجا به وبپک می‌گوییم که برای فایل‌های css ابتدا لودر css را فراخوانی کن و سپس با خروجی که از این لودر می‌گیری، لودر style را صدا بزن و منتظر جواب آن باش. ترتیب فراخوانی نیز از آخر به اول می‌باشد (اول لودر css سپس لودر style).
در مطالب قبلی ذکر شد که دو حالت برای معرفی فایل‌ها به وبپک وجود دارد؛ یکی معرفی آنها در کلید entry فایل پیکربندی و دیگری استفاده از تابع require در اسکریپت‌ها، برای بارگذاری پویای ماژول‌های دیگر. استفاده از استایل‌های css نیز به همین صورت است. برای بارگذاری فایل‌های استایل، از روش پویا استفاده کرده و در فایل main.js استایل مورد نظر را با کمک require وارد می‌کنیم.
محتوای فایل main.js بدین صورت است:
// main.js file
require("./../assets/main.css");
console.log(`i'm bundled by webpack`);
محتوای فایل main.css نیز بدین شکل می‌باشد:
// main.css
body{
    background-color: #DAA520;
}
با راه اندازی وبپک و باز کردن صفحه‌ی index می‌توان دید که فایل استایل ما به همراه باندل وارد شده است:

در تصویر بالا مشخص است که در تگ Head صفحه، یک تگ جدید style، توسط وبپک ایجاد شده و استایل ما به صفحه تزریق شده‌است. همچنین اگر وبپک را به حالت Minify کردن باندل ببریم (در مطلب قبلی نحوه‌ی این کار ذکر شد)، باندل نهایی برای فایل‌های css نیز Minify خواهد شد.


استفاده از Sass با کمک وبپک 


روش استفاده از Sass نیز تفاوتی با css نخواهد داشت و فقط کافی است Loader آن را در پروژه نصب کنیم و در نهایت آن را در فایل پیکربندی، به وبپک معرفی کنیم. با دستور زیر لودر Sass را در پروژه وارد می‌کنیم:

// نصب لودر sass
npm install -D sass-loader node-sass

( node-sass به عنوان وابستگی لودر sass، در کنار آن نصب شده است)

حال به فایل پیکربندی می‌رویم و لودر جدید را به قسمت لودر‌ها اضافه می‌کنیم:

// webpack.config.js 
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!sass-loader'
            }
        ]
    }

در پوشه‌ی assets نیز فایل جدیدی را با عنوان main.scss ساخته و محتوای زیر را در آن وارد می‌کنیم:

// main.scss
$background-color:#DAA520;

body{
    background-color: $background-color;
}

سپس در فایل main.js به جای وارد کردن فایل css قبلی، فایل scss جدید را با کمک require وارد می‌کنیم و در ادامه وبپک را اجرا می‌کنیم. خواهیم دید که مانند قبل بدون مشکلی وبپک اجرا شده، فایل scss را به css ترجمه کرده و سپس به کمک بقیه لودر‌ها، به باندل اضافه می‌کند. استفاده از بقیه‌ی فریمورک‌های css مانند Less و ... نیز با کمک لودر آنها به همین صورت قابل انجام است.


استفاده از Autoprefixer 

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

npm install -D autoprefixer-loader

و بعد از نصب شدن آن، در فایل پیکربندی وبپک به لودرهایی که برای فایل‌های css و scss اضافه کرده بودیم، این لودر را نیز به صورت زنجیر وار اضافه می‌کنیم:

//webpack.config.js
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader!autoprefixer-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader'
            }
        ]
    }

در هر دو لودری که برای css و scss ساخته بودیم، از لودر autoprefixer استفاده کردیم. برای تست اینکه این لودر بدون مشکل کار می‌کند، در فایل main.scss تغییر زیر را ایجاد می‌کنیم:

//main.scss
$background-color:#DAA520;

body{
    background-color: $background-color;
    display: flex;
}

حال با اجرای وبپک خواهیم دید که prefix‌های مورد نیاز توسط لودر اضافه شده اند ( این لودر از کتابخانه‌ی postcss کمک می‌گیرد).




باندل کردن تصاویر و فونت‌ها با کمک وبپک


تا اینجا با نحوه‌ی وارد کردن فایل‌های استایل، مانند css و ... به باندل آشنا شدیم. در ادامه قصد داریم که تصاویر و فونت‌ها را نیز وارد باندل کنیم. روند کار شبیه به گذشته است و این کار نیز به کمک لودر‌های وبپک انجام خواهد شد.

جهت باندل کردن تصاویر و فونت‌ها، به لودر جدیدی با نام url-loader احتیاج داریم. قبل از هر چیزی این لودر را در پروژه با کمک npm نصب می‌کنیم:

npm install -D url-loader file-loader

(لودر file-loader به عنوان وابستگی مورد نیاز است)

روند همچنان مثل گذشته است و پس از نصب لودر، وارد فایل پیکربندی شده و لودر جدید را به وبپک معرفی می‌کنیم:

//webpack.config.js file
module: {
        loaders: [
            {
                test: /\.css$/
                , exclude: /node_modules/
                , loader: 'style-loader!css-loader!autoprefixer-loader'
            }
            ,{
                test:/\.scss$/
                ,exclude:/node_modules/
                ,loader:'style-loader!css-loader!autoprefixer-loader!sass-loader'
            },{
                test:/\.(png|jpg|ttf|eot)$/
                ,exclude:/node_modules/
                ,loader:'url-loader?limit=100000'
            }
        ]
    }

در لودر اضافه شده، پسوند فایلهایی را که قصد داریم به باندل وارد شوند، معرفی می‌کنیم. در اینجا فرمت‌های png , jpg ,ttf, eot ذکر شده‌اند.

تنها نکته‌ی جدید، در مشخص کردن نام لودر وجود دارد و آن نیز قسمت پس از علامت ؟ می‌باشد. هنگام مشخص کردن اینکه از چه لودری قصد استفاده داریم، می‌توانیم با استفاده از ؟ پارامترهایی را به لودر مورد نظر ارسال کنیم. در اینجا به پارامتر limit، مقدار 100000 را داده‌ایم که برای این لودر به این معناست که اگر حجم فایل در حال پردازش، حجمی بیشتر از این مقدار را داشت، این فایل را به صورت یک لینک جدا از باندل قرار بده. ولی اگر حجمی کمتر از این مقدار داشت، لودر به صورت خودکار فایل را به فرمت Base64 انکود می‌کند و در درون باندل قرار می‌دهد.


برای تست اینکه آیا این لودر به درستی کار می‌کند یا نه، یک تصویر نمونه را در فولدر assets قرار می‌دهیم و سپس در فایل main.scss تغییرات زیر را انجام می‌دهیم.

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



حال محدودیت حجم فایل را بالا می‌بریم و می‌توان دید که تصویر در باندل نهایی به صورت انکود شده قرار گرفته است .


قطعا انجام این کار برای تصاویری با حجم بالا مناسب نخواهد بود و برنامه نویس بسته به نیاز بایستی مقدار محدودیت حجم را برای لودر مشخص کند.


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

برای مثال فونت ساحل در پوشه‌ی assets قرار داده شده و در فایل main.scss تغییرات زیر انجام شده‌اند:

// main.scss

$background-color:#DAA520;
 
div{
    background-image: url("galaxy.jpg");
}

@font-face {
  font-family: Sahel;
  src: url('Sahel.eot');
  src: url('Sahel.eot?#iefix') format('embedded-opentype'),
       url('Sahel.woff') format('woff'),
       url('Sahel.ttf') format('truetype');
  font-weight: normal;
}

@font-face {
  font-family: Sahel;
  src: url('Sahel-Bold.eot');
  src: url('Sahel-Bold.eot?#iefix') format('embedded-opentype'),
       url('Sahel-Bold.woff') format('woff'),
       url('Sahel-Bold.ttf') format('truetype');
  font-weight: bold;
}

@font-face {
  font-family: Sahel;
  src: url('Sahel-Black.eot');
  src: url('Sahel-Black.eot?#iefix') format('embedded-opentype'),
       url('Sahel-Black.woff') format('woff');
    
  font-weight: 900;
} 

body{
    background-color: $background-color;
    font-family: 'Sahel';
    display: flex;
}

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



روش دیگری برای وارد کردن تصاویر نیز موجود است؛ به این صورت که به فرض مثال یک تگ img در اسکریپت ساخته و سپس پروپرتی src آن را با کمک require برابر با آدرس تصویر مورد نظر قرار می‌دهیم. این روش نیز برای وبپک قابل فهم بوده و فایل وارد باندل می‌شود. در ادامه مثالی از این روش آورده شده است:

var img = document.createElement("img");
img.width="200px";
img.height="200px";
img.src= require("path to some image");


چند نکته‌ی پایانی :

1. در فایل پیکربندی همیشه پسوند فایل‌هایی را که در کلید entry قرار داشتند، مشخص کردیم:

entry:['./main.js','./shared.ts']

با کلیدی با نام resolve در فایل پیکربندی می‌توان مشخص کرد در صورتیکه پسوند فایلی مشخص نبود، به ترتیب مشخص شده به دنبال آن بگردد. به طور مثال:

// webpack.config.js
resolve:{
            extensions:['','.js','.ts']
  }

در تعریف بالا ذکر می‌شود در صورتیکه پسوند فایل ورودی مشخص نبود، ابتدا به دنبال فایل بدون پسوند، سپس فایل‌هایی با پسوند js و در نهایت به دنبال فایل‌هایی با پسوند ts بگرد. توجه داشته باشید که ترتیب مشخص کردن پسوند فایل‌ها مهم است و وبپک بر اساس این ترتیب به دنبال فایل مورد نظر خواهد گشت.

حال می‌توان مقدار کلید entry را اینطور تعریف کرد:

entry:['./main','./shared']


2.استفاده از فایل‌های css ی که در درونشان فونت‌های مورد نیاز لینک شده‌اند تنها با استفاده از لودر css قابل انجام نیست. به طور مثال استفاده از کتابخانه‌ی بوت استرپ تنها با این لودر ممکن نیست و بایستی لودر url-loader نیز در پروژه نصب شده باشد تا در هنگامیکه وبپک به فونت‌ها برخورد کرد، بتواند آنها را وابسته به شرایط، وارد باندل نهایی کند.


فایل‌های پروژه: dntwebpack-part4.zip 

مطالب
استفاده از Google Analytics API در دات نت فریم ورک

بالاخره گوگل کار تهیه API مخصوص ابزار Analytics خود را به پایان رساند و اکنون برنامه نویس‌ها می‌توانند همانند سایر سرویس‌های گوگل از این ابزار گزارشگیری نمایند.
خلاصه کاربردی این API ، دو صفحه تعاریف پروتکل (+) و ریز مواردی (+) است که می‌توان گزارشگیری نمود.
هنوز کتابخانه google-gdata جهت استفاده از این API به روز رسانی نشده است؛ بنابراین در این مقاله سعی خواهیم کرد نحوه کار با این API را از صفر بازنویسی کنیم.
مطابق صفحه تعاریف پروتکل، سه روش اعتبارسنجی جهت دریافت اطلاعات API معرفی شده است که در اینجا از روش ClientLogin که مرسوم‌تر است استفاده خواهیم کرد.
مطابق مثالی که در آن صفحه قرار دارد، اطلاعاتی شبیه به اطلاعات زیر را باید ارسال و دریافت کنیم:

POST /accounts/ClientLogin HTTP/1.1
User-Agent: curl/7.15.1 (i486-pc-linux-gnu) libcurl/7.15.1
OpenSSL/0.9.8a zlib/1.2.3 libidn/0.5.18
Host: www.google.com
Accept: */*
Content-Length: 103
Content-Type: application/x-www-form-urlencoded
accountType=GOOGLE&Email=userName@google.com&Passwd=myPasswrd&source=curl-tester-1.0&service=analytics

HTTP/1.1 200 OK
Content-Type: text/plain
Cache-control: no-cache
Pragma: no-cache
Date: Mon, 02 Jun 2008 22:08:51 GMT
Content-Length: 497
SID=DQ...
LSID=DQAA...
Auth=DQAAAG8...
در دات نت فریم ورک، این‌کار را به صورت زیر می‌توان انجام داد:
        string getSecurityToken()
{
if (string.IsNullOrEmpty(Email))
throw new NullReferenceException("Email is required!");

if (string.IsNullOrEmpty(Password))
throw new NullReferenceException("Password is required!");

WebRequest request = WebRequest.Create("https://www.google.com/accounts/ClientLogin");
request.Method = "POST";

string postData = "accountType=GOOGLE&Email=" + Email + "&Passwd=" + Password + "&service=analytics&source=vahid-testapp-1.0";
byte[] byteArray = Encoding.ASCII.GetBytes(postData);

request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = byteArray.Length;

using (Stream dataSt = request.GetRequestStream())
{
dataSt.Write(byteArray, 0, byteArray.Length);
}

string auth = string.Empty;
using (WebResponse response = request.GetResponse())
{
using (Stream dataStream = response.GetResponseStream())
{
using (StreamReader reader = new StreamReader(dataStream))
{
string responseFromServer = reader.ReadToEnd().Trim();
string[] tokens = responseFromServer.Split('\n');
foreach (string token in tokens)
{
if (token.StartsWith("SID="))
continue;

if (token.StartsWith("LSID="))
continue;

if (token.StartsWith("Auth="))
{
auth = token.Substring(5);
}
else
{
throw new AuthenticationException("Error authenticating Google user " + Email);
}
}
}
}
}

return auth;

}

همانطور که ملاحظه می‌کنید به آدرس https://www.google.com/accounts/ClientLogin ، اطلاعات postData با متد POST ارسال شده (دقیقا مطابق توضیحات گوگل) و سپس از پاسخ دریافتی، مقدار نشانه Auth را جدا نموده و در ادامه عملیات استفاده خواهیم کرد. وجود این نشانه در پاسخ دریافتی به معنای موفقیت آمیز بودن اعتبار سنجی ما است و مقدار آن در طول کل عملیات باید نگهداری شده و مورد استفاده مجدد قرار گیرد.
سپس مطابق ادامه توضیحات API گوگل باید لیست پروفایل‌هایی را که ایجاد کرده‌ایم پیدا نمائیم:

string getAvailableProfiles(string authToken)
{
return fetchPage("https://www.google.com/analytics/feeds/accounts/default", authToken);
}

متد fetchPage را از پیوست این مقاله می‌توانید دریافت نمائید. خروجی یک فایل xml است که با انواع و اقسام روش‌های موجود قابل آنالیز است، از کتابخانه‌های XML دات نت گرفته تا Linq to xml و یا روش serialization که من روش آخر را ترجیح می‌دهم.
مرحله بعد، ساخت URL زیر و دریافت مجدد اطلاعات مربوطه است:
            string url = string.Format("https://www.google.com/analytics/feeds/data?ids={0}&metrics=ga:pageviews&start-date={1}&end-date={2}", id, from, to);
return fetchPage(url, auth);
و سپس آنالیز اطلاعات xml دریافتی، جهت استخراج تعداد بار مشاهده صفحات یا pageviews استفاده شده در این مثال. لیست کامل مواردی که قابل گزارشگیری است، در صفحه Dimensions & Metrics Reference گوگل ذکر شده است.

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

مثالی در مورد نحوه استفاده از آن:
            CGoogleAnalytics cga = new CGoogleAnalytics
{
Email = "username@gmail.com",
Password = "password",
From = DateTime.Now.Subtract(TimeSpan.FromDays(1)),
To = DateTime.Now.Subtract(TimeSpan.FromDays(1))
};
List<CGoogleAnalytics.SitePagePreviews> pagePreviews =
cga.GetTotalNumberOfPageViews();

foreach (var list in pagePreviews)
{
//string site = list.Site;
//int pw = list.PagePreviews;
}

مطالب
ارسال خودکار مطلب به بلاگر

اکثر خدمات گوگل دارای API هم هستند و به این ترتیب با استفاده از برنامه نویسی نیز می‌توان به آن‌ها دسترسی پیدا کرد. برای نمونه API دسترسی به Blogger در اینجا توضیح داده شده است. برای کار با این امکانات یا می‌توان چرخ را از نو اختراع کرد یا از کتابخانه‌های مرتبطی همانند Gdata API for .NET استفاده نمود. برای دات نت فریم ورک، از آدرس http://code.google.com/p/google-gdata/ می‌توان آخرین کتابخانه‌های کار با GData یا Google Data API را دریافت کرد. برای نمونه فایل Google_Data_API_Setup_1.9.0.0.msi فعلی آن حدود 28 مگ حجم دارد و به درد کسانی می‌خورد که علاقمند هستند تا تمام امکانات موجود آن‌را بررسی کنند. راه ساده‌تری هم برای دسترسی به این کتابخانه‌ها وجود دارد؛ می‌توان از NuGet استفاده کرد.


به این ترتیب به سادگی و سرعت هرچه تمامتر فایل 200 کیلوبایتی Google.GData.Client.dll دریافت شده و ارجاعی نیز به آن اضافه خواهد شد. همین حد جهت کار با بلاگر کافی است.
برای نمونه قطعه کد زیر کار ارسال یک مطلب جدید به وبلاگ بلاگری شما را انجام خواهد داد:

using System;
using System.Collections.Generic;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BloggerAutoPoster
{
public string UserName { set; get; }

public string Password { set; get; }

public string PostTitle { set; get; }

public IList<string> PostTags { set; get; }

public string PostBody { set; get; }

public string BlogUrl { set; get; }

public bool PostAsDraft { set; get; }

public bool PostNewEntry()
{
var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(UserName, Password)
};
var newPost = constructNewEntry();
var result = service.Insert(new Uri(BlogUrl), newPost);
return result != null;
}

private AtomEntry constructNewEntry()
{
var newPost = new AtomEntry
{
Title = { Text = PostTitle },
Content = new AtomContent
{
Content = string.Format(@"<div xmlns=""http://www.w3.org/1999/xhtml"">{0}</div>", PostBody),
Type = "xhtml"
},
IsDraft = PostAsDraft
};

foreach (var tag in PostTags)
{
newPost.Categories.Add(
new AtomCategory
{
Term = tag,
Scheme = "http://www.blogger.com/atom/ns#"
});
}

return newPost;
}
}
}

مثالی از استفاده آن هم به صورت زیر می‌باشد:

new BloggerAutoPoster
{
BlogUrl = "https://www.blogger.com/feeds/number/posts/default",
UserName = "name@gmail.com",
Password = "pass",
PostTitle = "بررسی ارسل خودکار-3",
PostTags = new List<string> { "بررسی ارسال خودکار" },
PostBody = "تست می‌شود123",
PostAsDraft = false
}.PostNewEntry();

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

using System;
using System.Collections.Generic;
using System.Linq;
using Google.GData.Client;

namespace BloggerAutoPoster
{
public class BlogInfo
{
public string Title { set; get; }
public string Url { set; get; }
}

public class BloggerInfo
{
public static IList<BlogInfo> FindMyBlogsUrls(string username, string password)
{
var result = new List<BlogInfo>();

var service = new Service("blogger", "blogger-example")
{
Credentials = new GDataCredentials(username, password)
};

var query = new FeedQuery { Uri = new Uri("https://www.blogger.com/feeds/default/blogs") };
var feed = service.Query(query);

if (feed == null)
throw new NotSupportedException("You don't have any blogs!");

foreach (var entry in feed.Entries)
{
result.AddRange(entry.Links.Where(t => t.Rel.Equals("http://schemas.google.com/g/2005#post"))
.Select(t => new BlogInfo
{
Url = new Uri(t.HRef.ToString()).AbsoluteUri,
Title = entry.Title.Text
}));
}

return result;
}
}
}

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


مطالب
معرفی ELMAH

عموما کاربران نمی‌توانند گزارش خطای خوبی را ارائه بدهند و البته انتظاری هم از آنان نیست. تنها گزارشی که از یک کاربر دریافت می‌کنید این است: "برنامه کار نمی‌کنه!" و همین!
روش‌های متعددی برای لاگ کردن خطاهای یک برنامه ASP.Net موجود است؛ چه خودتان آن‌ها را توسعه دهید و یا از ASP.NET health monitoring استفاده کنید.
روش دیگری که این روزها در وبلاگ‌های متعددی در مورد آن مطلب منتشر می‌شود، استفاده از ELMAH است. (البته ELMAH به تازگی منتشر نشده ولی تا کیفیت محصولی به عموم ثابت شود مدتی زمان می‌برد)

ELMAH یک ماژول رایگان و سورس باز لاگ کردن خطاهای مدیریت نشده برنامه‌های ASP.Net‌ است. برای استفاده از این ماژول نیازی نیست تا تغییری در برنامه خود ایجاد کنید یا حتی آن‌را کامپایل مجدد نمائید. یک فایل dll‌ دارد به همراه کمی تغییر در web.config برنامه جهت معرفی آن و این تمام کاری است که برای برپایی آن لازم است صورت گیرد. این ماژول تمامی خطاهای مدیریت نشده‌ی برنامه شما را لاگ کرده (در حافظه سرور، در یک فایل xml ، در یک دیتابیس اس کیوال سرور یا اوراکل ، در یک دیتابیس اکسس و یا در یک دیتابیس اس کیوال لایت) و برای مرور آن‌ها یک صفحه‌ی وب سفارشی یا فیدی مخصوص را نیز در اختیار شما قرار می‌دهد. همچنین این قابلیت را هم دارد که به محض بروز خطایی یک ایمیل را نیز به شما ارسال نماید.

با توجه به این‌که این ماژول در Google code قرار گرفته احتمالا دسترسی به آن مشکل خواهد بود. سورس و فایل‌های کامپایل شده آن‌را از آدرس‌های زیر نیز می‌توان دریافت نمود:
( + و + و +)

نحوه استفاده از ELMAH :

برای استفاده از ELMAH دو کار را باید انجام دهید:
الف) کپی کردن فایل Elmah.dll در پوشه bin برنامه
ب) تنظیم وب کانفیگ برنامه

بهترین مرجع برای آشنایی با نحوه بکار گیری این ماژول، مراجعه به فایل web.config موجود در پوشه samples آن است. بر اساس این فایل نمونه:

- ابتدا باید configSections آن را به وب کانفیگ خود اضافه کنید.

- سپس تگ elmah باید اضافه شود. در این تگ موارد زیر مشخص می‌شوند:
الف) آیا خطاها توسط آدرس elmah.axd توسط کاربران راه دور قابل مشاهده شود یا خیر.
ب) خطاها کجا ذخیره شوند؟ موارد زیر پشتیبانی می‌شوند:
دیتابیس‌های اس کیوال سرور ، اوراکل ، حافظه سرور، فایل‌های xml ، دیتابیس SQLite ، دیتابیس اکسس و یا دیتابیسی از نوع VistaDB
ج) آیا خطاها ایمیل هم بشوند؟ اگر بلی، تگ مربوطه را تنظیم کنید.
د) آیا خطاها به اکانت twitter شما نیز ارسال شوند؟

- در ادامه تگ مربوط به معرفی این httpModules باید تنظیم شود.

- سپس httpHandlers ایی به نام elmah.axd که جهت مرور خطاها می‌توان از آن استفاده نمود معرفی می‌گردد.

- از IIS7 استفاده می‌کنید؟ قسمت system.webServer را نیز باید اضافه نمائید.

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

برای نمونه، اگر بخواهید از دیتابیس SQLite جهت ذخیره سازی خطاهای حاصل شده استفاده نمائید و نیز از ارسال ایمیل صرفنظر کنید، وب کانفیگ برنامه شما باید به شکل زیر تغییر یابد:
<?xml version="1.0"?>

<configuration>
<configSections>
<sectionGroup name="elmah">
<section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/>
<section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah" />
<section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
<section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/>
<section name="errorTweet" requirePermission="false" type="Elmah.ErrorTweetSectionHandler, Elmah"/>
</sectionGroup>
</configSections>

<elmah>
<security allowRemoteAccess="1" />
<errorLog type="Elmah.SQLiteErrorLog, Elmah" connectionStringName="cn1" />
</elmah>


<appSettings/>

<connectionStrings>
<add name="cn1" connectionString="data source=~/ErrorsLog/Errors.db" />
</connectionStrings>

<system.web>
<httpModules>
<add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/>
</httpModules>

<httpHandlers>
<add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
</httpHandlers>

<compilation debug="true"/>

<authentication mode="Windows" />
</system.web>

</configuration>
در اینجا یک پوشه جدید به نام ErrorsLog را باید به ریشه سایت خود اضافه کنید (یا هر نام دلخواه دیگری که در قسمت connectionStrings باید تنظیم شود). فایل Errors.db به صورت خودکار ایجاد خواهد شد. بدیهی است کاربر ASP.net باید دسترسی write بر روی این پوشه داشته باشد تا عملیات ثبت خطاها با موفقیت صورت گیرد. همچنین فایل System.Data.SQLite.DLL نیز باید در پوشه bin برنامه شما کپی شود.
ساده‌ترین تنظیم این ماژول استفاده از حالت xml است که به ازای هر خطا یک فایل xml را تولید کرده و نیاز به اسمبلی دیگری بجز ماژول مربوطه نخواهد داشت.

تذکر:
از لحاظ امنیتی مثال فوق توصیه نمی‌شود زیرا allowRemoteAccess آن 1 است و قسمت authorization ذکر نشده است. این مثال فقط جهت راه اندازی و آزمایش اولیه ارائه گردیده است. (همچنین بهتر است این نام پیش فرض را به نامی دیگر مثلا myloggermdl.axd تغییر داده و در قسمت httpHandlers تنظیم نمائید. سپس این نام را به تگ location نیز اضافه کنید)

اکنون برای مشاهده خروجی این ماژول به انتهای آدرس سایت خود، elmah.axd را اضافه کرده و سپس enter کنید:



همانطور که در تصویر مشخص است، تمامی خطاهای لاگ شده گزارش داده شده‌اند. همچنین دو نوع فید به همراه امکان دریافت خطاها به صورت CSV نیز موجود است. با کلیک بر روی لینک details ، صفحه‌ی بسیار ارزنده‌ای ارائه می‌شود که تقریبا نحوه‌ی وقوع ماجرا را بازسازی می‌کند.
نکته‌ی مهمی که در صفحه‌ی جزئیات ارائه می‌شود (علاوه بر stack trace‌ و مشخصات کاربر)، مقادیر تمامی فیلدهای یک صفحه هنگام بروز خطا است (قسمت Raw/Source data in XML or in JSON در این صفحه) :



به این صورت دیگر نیازی نیست از کاربر بپرسید چه چیزی را وارد کرده بودید که خطا حاصل شد. دقیقا مقادیر فیلدهای همان صفحه‌ی زمان بروز خطا نیز برای شما لاگ می‌گردد.


نکته:
ماژول SQLite ایی که به همراه مجموعه ELMAH ارائه می‌شود 32 بیتی کامپایل شده (64 بیتی آن نیز موجود است که باید از آن در صورت لزوم استفاده شود). بنابراین برای اینکه در یک سرور 64 بیتی به مشکل برنخورید و خطای BadImageFormat را دریافت نکنید نیاز است تا به این نکته دقت داشت.

برای مطالعه بیشتر:

Error Logging Modules And Handlers
Sending ELMAH Errors Via GMail
Exception-Driven Development
Using HTTP Modules and Handlers to Create Pluggable ASP.NET Components

مطالب
کار با modal dialogs مجموعه Bootstrap در برنامه‌های Angular
در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» با نحوه‌ی دریافت، نصب و راه اندازی کتابخانه‌ی ngx-bootstrap آشنا شدیم. در اینجا می‌خواهیم نحوه‌ی کار با کامپوننت Modal آن را بررسی کنیم.


سازماندهی بهتر کامپوننت‌های ngx-bootstrap

پس از نصب بسته‌ی npm کتابخانه‌ی ngx-bootstrap و تنظیم فایل angular-cli.json. که در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» بررسی شدند، برای کار با کامپوننت‌های این کتابخانه باید متدهای BsDropdownModule.forRoot، TooltipModule.forRoot، ModalModule.forRoot و ... را به قسمت imports فایل app.module.ts اضافه کرد. با انجام این‌کار پس از مدتی به یک فایل بسیار شلوغ app.module.ts خواهیم رسید. برای مدیریت بهتر آن می‌توان شبیه به مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» در پوشه‌ی Shared برنامه، ماژول ذیل را تدارک دید. برای اینکار ابتدا فایل جدید src\app\shared\shared.bootstrap.module.ts را ایجاد نمائید. سپس کامپوننت‌های این کتابخانه را به صورت ذیل در این تک ماژول اختصاصی قرار دهید:
import { NgModule } from "@angular/core";
import { CommonModule } from "@angular/common";
import { BsDropdownModule } from "ngx-bootstrap/dropdown";
import { TooltipModule } from "ngx-bootstrap/tooltip";
import { ModalModule } from "ngx-bootstrap/modal";

@NgModule({
  imports: [
    CommonModule,
    BsDropdownModule.forRoot(),
    TooltipModule.forRoot(),
    ModalModule.forRoot()
  ],
  exports: [
    BsDropdownModule,
    TooltipModule,
    ModalModule
  ]
})
export class SharedBootstrapModule { }
متدهای forRoot در قسمت imports قرار می‌گیرند (فلسفه‌ی وجودی این متد و الگوی ویژه را در مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» پیشتر بررسی کرده‌ایم). سپس برای اینکه این کامپوننت‌ها در سایر ماژول‌های برنامه قابل استفاده باشند، باید نام ماژول مرتبط با هر کدام را در قسمت exports نیز ذکر کرد.
اکنون برای استفاده‌ی از SharedBootstrapModule اختصاصی فوق، می‌توان دو روش را بکار برد:
الف) import مستقیم آن در فایل app.module.ts
import { SharedBootstrapModule } from './shared/shared.bootstrap.module';
@NgModule({
  imports: [BrowserModule, SharedBootstrapModule],
  // ...
})
export class AppModule {}
ب) import آن در SharedModule
و یا اگر فایل src\app\shared\shared.module.ts را مطابق مطلب «سازماندهی برنامه‌های Angular توسط ماژول‌ها» ایجاد کرده‌اید، این ماژول به صورت ذیل، در دو قسمت imports و exports آن اضافه خواهد شد:
import { SharedBootstrapModule } from "./shared.bootstrap.module";

@NgModule({
  imports: [
    CommonModule,
    SharedBootstrapModule
  ],
  exports: [
    CommonModule,
    SharedBootstrapModule
  ]
})


نمایش یک modal dialog توسط کامپوننت Modal

پس از تعریف ModalModule.forRoot، اکنون می‌توان به کامپوننت Modal این ماژول دسترسی یافت. برای این منظور کامپوننتی که قرار است یک Modal را نمایش دهد، چنین ساختاری را پیدا می‌کند:
import { Component, OnInit, TemplateRef } from "@angular/core";
import { BsModalRef, BsModalService } from "ngx-bootstrap";

@Component({
  selector: "app-modal-dialog-test",
  templateUrl: "./modal-dialog-test.component.html",
  styleUrls: ["./modal-dialog-test.component.css"]
})
export class ModalDialogTestComponent implements OnInit {

  modalRef: BsModalRef;

  constructor(private modalService: BsModalService) { }

  openModal(template: TemplateRef<any>) {
    this.modalRef = this.modalService.show(template,
      { animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false });
  }

  closeModal() {
    this.modalRef.hide();
  }
}
توسط سرویس BsModalService که به سازنده‌ی کلاس کامپوننت تزریق شده‌است، می‌توان به متد show آن دسترسی یافت. این متد یک ng-template را قبول می‌کند. بنابراین در قالب این کامپوننت باید قسمتی را که قرار است به صورت modal نمایش داده شود، توسط یک ng-template تعریف کرد.
سپس با فراخوانی متد this.modalService.show می‌توان این قالب را نمایش داد. خروجی این متد ارجاعی را به این modal بازگشت می‌دهد. از این ارجاع می‌توان در جهت بستن آن استفاده کرد (مانند متد closeModal).

بنابراین در ادامه، قالب کامپوننت مثال این قسمت، یک چنین شکلی را پیدا می‌کند:
<h1>Displaying modal bootstrap dialogs</h1>

<button type="button" class="btn btn-info" (click)="openModal(template1)">Create template modal</button>

<ng-template #template1>
  <div class="modal-header">
    <h4 class="modal-title pull-left">Modal</h4>
    <button type="button" class="close pull-right" aria-label="Close" (click)="closeModal()">
      <span aria-hidden="true">&times;</span>
    </button>
  </div>
  <div class="modal-body">
    This is a modal.
  </div>
</ng-template>
در اینجا محتوای modal داخل یک ng-template قرار گرفته‌است و این قالب توسط یک template reference variable به نام template1 مشخص شده‌است. این نام را در متد openModal(template1) استفاده خواهیم کرد تا به متد show سرویس نمایش modal منتقل شود.



طراحی یک کامپوننت عمومی مودال جهت دریافت تائید انجام عملیات

در ادامه می‌خواهیم توسط یک modal dialog، کار دریافت تائید و یا لغو انجام یک عملیات را انجام دهیم. چون این کامپوننت عمومی قرار است در بیش از یک ماژول استفاده شود، بنابراین نیاز است آن‌را در Shared Module ثبت کرد. به همین جهت این کامپوننت را به نحو ذیل در پوشه‌ی Shared ایجاد می‌کنیم:
 ng g c Shared/ConfirmModal --skip-import
پرچم skip-import نیز ذکر شده‌است، چون قصد نداریم به صورت مستقیم از طریق درج selector آن در صفحه، با آن کار کنیم. سرویس سفارشی مودالی که برای این منظور تدارک خواهیم دید، کار نمایش آن‌را انجام می‌دهد.
import { ConfirmModalComponent } from "./confirm-modal/confirm-modal.component";

@NgModule({
  imports: [
  ],
  entryComponents: [
    ConfirmModalComponent
  ],
  declarations: [
    ConfirmModalComponent
  ]
})
export class SharedModule {}
نحوه‌ی درج و تعریف این کامپوننت اندکی متفاوت است. چون این کامپوننت قرار است «به صورت پویا» توسط متد show سرویس BsModalService نمایش داده شود (پارامتر اول آن می‌تواند یک قالب و یا یک کامپوننت کامل باشد)، باید در قسمت entryComponents و declarations مربوط به SharedModule درج شود. آن‌را در قسمت exports ذکر نمی‌کنیم، چون قرار نیست با درج مستقیم selector آن در صفحه، آن‌را نمایش دهیم.

این کامپوننت دریافت تائید کاربر به صورت ذیل تعریف می‌شود:
import { Component } from "@angular/core";

@Component({
  selector: "app-confirm-modal",
  templateUrl: "./confirm-modal.component.html",
  styleUrls: ["./confirm-modal.component.css"]
})
export class ConfirmModalComponent {

  args: {
    title: string;
    message: string;
  };

  close: (val?: any) => void;

}
در اینجا args آن توسط سرویسی که در ادامه طراحی می‌کنیم، مقدار دهی خواهد شد (طراحی args در اینجا کاملا دلخواه است و در کامپوننت‌های مشابه دیگر می‌تواند متفاوت باشد). متد close آن نیز کار گزارش دهی به فراخوان را انجام می‌دهد.
قالب این کامپوننت نیز بدون استفاده از ng-template تعریف می‌شود:
<div class="modal-header">
  <h4 class="modal-title pull-left">{{ args?.title }}</h4>
  <button type="button" class="close pull-right" aria-label="Close" (click)="close()">
    <span aria-hidden="true">&times;</span>
  </button>
</div>
<div class="modal-body">
  <p>{{ args?.message }}</p>
</div>
<div class="modal-footer">
  <button class="btn btn-danger" (click)="close(true)">Yes</button>
  <button class="btn btn-primary" (click)="close()">Cancel</button>
</div>
چون این کامپوننت قرار است به صورت پویا توسط متد show بارگذاری شود، نیازی نیست محتوای قالب آن‌را توسط ng-template مخفی کرد و سپس نمایش داد. زمانیکه این کامپوننت بارگذاری شد، یعنی قصد داریم یک modal کامل را نمایش دهیم.

تا اینجا یک کامپوننت نمایش دریافت تائید انجام عملیات را تهیه کردیم. در ادامه نیاز است یک سرویس را جهت بارگذاری پویای اینگونه کامپوننت‌های مودال طراحی کنیم. این سرویس عمومی در پوشه‌ی Core و CoreModule ثبت خواهد شد:
 >ng g s Core/Modal
با این محتوا
import { Injectable } from "@angular/core";
import { BsModalService } from "ngx-bootstrap";

@Injectable()
export class ModalService {

  constructor(private bsModalService: BsModalService) { }

  show(component: any, args?: any, options?: any): Promise<any> {
    return new Promise(resolve => {
      options = options || {};
      const modal = this.bsModalService.show(component, options);
      let result: any;
      const sub = this.bsModalService.onHidden.subscribe(() => {
        sub.unsubscribe();
        resolve(result);
      });
      modal.content.args = args;
      modal.content.close = (val?: any) => {
        result = val;
        modal.hide();
      };
    });
  }
}
کار این سرویس، نمایش یک کامپوننت مودال مانند ConfirmModalComponent به صورت پویا است؛ از این جهت که متد this.bsModalService.show هم امکان نمایش یک ng-template را دارد و هم یک کامپوننت کامل را به صورت پویا.

یک مودال در سه حالت ممکن است بسته شود:
الف) کلیک بر روی دکمه‌ی close و یا cancel
ب) کلیک بر روی علامت ضربدر درج شده‌ی در یک سمت عنوان آن
ج) کلیک بر روی قسمتی از صفحه، خارج از مودال
در حالات ب و ج، رخ‌داد this.bsModalService.onHidden فراخوانی می‌شود. در حالت الف، همان متد close درج شده‌ی در کامپوننت فراخوانی می‌شود.
برای اینکه بتوان نتیجه‌ی عملیات را از طرف یک سرویس به کامپوننت فراخوان آن گزارش دهیم، یکی از روش‌ها، استفاده از Promiseها است که مشاهده می‌کنید. با فراخوانی resolve(result)، کار ارسال نتیجه‌ی فراخوانی متدهای close(true) و ()close صورت می‌گیرد (یا true و یا undefined).

خاصیت modal.content امکان دسترسی به خواص عمومی کامپوننت در حال استفاده را میسر می‌کند (content به کامپوننت بارگذاری شده اشاره می‌کند). اینجا است که می‌توان برای مثال به خاصیت args یک کامپوننت، مقادیری را نسبت داد و یا به متد close آن دسترسی یافت.

پس از افزودن این سرویس، محل تعریف آن در قسمت providers مربوط به CoreModule است تا در تمام برنامه قابل دسترسی شود:
import { ModalService } from "./modal.service";

@NgModule({
  providers: [
    ModalService
  ]
})
export class CoreModule {}
در پایان برای آزمایش این سرویس جدید، یک دکمه و یک برچسب را به قالب کامپوننت ModalDialogTestComponent ابتدای بحث اضافه می‌کنیم:
<button type="button" class="btn btn-danger" (click)="deleteRecord()">Delete record</button>
<div *ngIf="confirmResult" class="alert alert-info">{{confirmResult}}</div>
با این کدها:
import { ModalService } from "./../../core/modal.service";
import { ConfirmModalComponent } from "./../../shared/confirm-modal/confirm-modal.component";

export class ModalDialogTestComponent implements OnInit {

  confirmResult: string;

  constructor(private modalService: ModalService) { }

  deleteRecord() {
    this.confirmResult = "";
    this.modalService.show(
      ConfirmModalComponent,
      {
        title: "Confirm", message: "Do you want to delete this record?"
      },
      {
        animated: true, keyboard: true, backdrop: true, ignoreBackdropClick: false
      }).then(confirmed => {
        if (confirmed) {
          this.confirmResult = "Deleted!";
        } else {
          this.confirmResult = "Canceled!";
        }
      });
  }
}
در اینجا نحوه‌ی استفاده‌ی از این ModalService سفارشی را ملاحظه می‌کنید. ابتدا به سازنده‌ی کلاس تزریق شده‌است و سپس در متد deleteRecord توسط متد show آن، کامپوننت ConfirmModalComponent به صورت پویا بارگذاری شده‌است. همچنین خاصیت args آن نیز با خواص title و message سفارشی، مقدار دهی شده‌است. چون این متد یک Promise را باز می‌گرداند، می‌توان مشترک آن شد و نتیجه‌ی نهایی را از آن دریافت کرد و بر اساس آن تصمیم گرفت که آیا باید عملیاتی رخ‌دهد، یا خیر.


توسط this.modalService.show می‌توان انواع و اقسام کامپوننت‌های مودال را به صورت پویا بارگذاری کرد و نمایش داد.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
نظرات مطالب
مبانی TypeScript؛ ماژول‌ها
- با هربار تغییر فایل tsconfig.json، کامپایل دوباره‌ی پروژه را فراموش نکنید (مهم). از منوی build گزینه‌ی rebuild solution را انتخاب کنید. این rebuild، کار کامپایل مجدد فایل‌های ts. را هم انجام می‌دهد.
- commonjs بیشتر برای برنامه‌های nodejs استفاده می‌شود. اگر علاقمند باشید که با سیستمی شبیه به AngularJS 2.0 کار کنید، از یک module loader ویژه، به نام SystemJS استفاده کنید (که قابلیت بارگذاری خودکار ES6 modules, AMD, CommonJS را دارد). بنابراین فایل tsconfig.json را به این صورت تغییر دهید:
{
    "compileOnSave": true,
    "compilerOptions": {
        "target": "es5",
        "module": "system",
        "sourceMap": true
    }
}
بعد فایل index.html شما چنین شکلی را پیدا می‌کند:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>TypeScript HTML App</title>
    <link rel="stylesheet" href="app.css" type="text/css"/>

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.22/system.js"></script>
    <script type="text/javascript">
        System.defaultJSExtensions = true;
        System.import('app');
    </script>
</head>
<body>
    <h1>TypeScript HTML App</h1>

    <div id="content"></div>
</body>
</html>
در اینجا System.JS کار بارگذاری اولین ماژول برنامه یا همان app.js را به صورت خودکار انجام می‌دهد (و همچنین تمام ماژول‌های مرتبط با آن‌را). بنابراین دیگر نیازی به ذکر اسکریپت‌های برنامه در اینجا نیست (هیچکدام از آن‌ها، منهای موارد عمومی مثل خود system.js).
بعد فایل app.ts را هم به این صورت تغییر دهید، چون این کدها پس از onload اجرا می‌شوند:
import {Book} from "./testmd";

let book: Book = new Book();
console.log(book.bookName);
document.getElementById("content").innerText = book.GetbookNmae;