مطالب
یک نکته درباره Angular routeProvider
بعد از مطالعه پست‌های ^ و ^    نکته ای به ذهنم رسید که بیان آن از بنده و مطالعه آن توسط شما خالی از لطف نیست. اگر مثال‌های پیاده سازی شده در پست‌های ^ و ^ را با AngularJs نسخه 1.2 اجرا نمایید به طور حتم با خطا روبرو می‌شوید و نتیجه مورد نظر حاصل نمی‌شود. در این پست نیز توسط یکی از دوستان اشاره ای به این مطلب شد.
دلیل خطا این است که از نسخه 1.2 به بعد در Angular سیستم مسیر یابی به این شکل امکان پذیر نیست و بخش مسیریابی به یک فایل دیگر به نام angular-route.js منتقل شده است. در نتیجه اگر به سبک نسخه‌های قبلی Angular از سیستم مسیریابی استفاده نمایید با خطا مواجه خواهید شد و خطای مورد نظر هم مربوط به عدم توانایی در تزریق وابستگی routeProvider$ به ماژول مورد نظر است. حال راه حل چیست؟
کافیست در هنگام تعریف ماژول، ngRoute را به عنوان وابستگی ماژول تعیین نمایید. و از طرفی فایل اسکریپتی angular-route.js را بعد از angular.js فراخوانی کنید.
بررسی مثال:
کد‌های زیر مربوط به مثال‌های پست قبلی می‌باشد که شرح کامل آن در این پست است:
var myFirstRoute = angular.module('myFirstRoute', []);
   
myFirstRoute.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/pageOne', {
        templateUrl: 'templates/page_one.html',
        controller: 'ShowPage1Controller'
      }).
      when('/pageTwo', {
        templateUrl: 'templates/page_two.html',
        controller: 'ShowPage2Controller'
      }).
      otherwise({
        redirectTo: '/pageOne'
      });
}]);
 
 
myFirstRoute.controller('ShowPage1Controller', function($scope) {
    $scope.message = 'Content of page-one.html';
});
  
  
myFirstRoute.controller('ShowPage2Controller', function($scope) {
    $scope.message = 'Content of page-two.html';
});
برای هماهنگ کردن مثال بالا با نسخه 1.2 باید به روش زیر عمل نمود:
var myFirstRoute = angular.module('myFirstRoute',['ngRoute']);
   
myFirstRoute.config(['$routeProvider',
  function($routeProvider) {
    $routeProvider.
      when('/pageOne', {
        templateUrl: 'templates/page_one.html',
        controller: 'ShowPage1Controller'
      }).
      when('/pageTwo', {
        templateUrl: 'templates/page_two.html',
        controller: 'ShowPage2Controller'
      }).
      otherwise({
        redirectTo: '/pageOne'
      });
}]);
تنها تغییر، مشخص کردن ngRoute به عنوان وابستگی ماژول myFirstRoute است(خط اول). نکته دیگر لود فایل angular-route.js قبل از فراخوانی فایل بالا و بعد از فراخوانی فایل angular.js است:
<body ng-app="app">
   
    <div>
        <div>
        <div>
            <ul>
                <li><a href="#pageOne"> Show page one </a></li>
                <li><a href="#pageTwo"> Show page two </a></li>
            </ul>
        </div>
        <div>
            <div ng-view></div>
        </div>
        </div>
    </div>
   
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
     <script src="angular-route.js"></script>
    <script src="app.js"></script>
   
  </body>

مطالب
مسیریابی (Routing) در ASP.NET MVC 5.x
در برنامه‌های ASP.NET Web Forms، هر درخواست (URL)، به یک فایل با پسوند aspx منطبق می‌شود. بطور مثال آدرس http://domain/studentsinfo.aspx بایستی با یک فایل فیزیکی به نام  studentsinfo.aspx مطابقت داشته باشد. این فایل حاوی code و markup برای پاسخگویی به درخواست ارسالی و نمایش اطلاعات در مرورگر می‌باشد.
Asp.net با معرفی سیستم مسیریابی (Routing)، عملیات نگاشت آدرس‌ها به فایل‌های فیزیکی را حذف کرد. مسیریابی امکانی را فراهم می‌کند تا با طراحی الگوی URL، درخواست‌ها را به مدیریت کننده‌ی درخواست‌ها نگاشت کنیم. این مدیریت کننده‌ی URL‌ها می‌تواند یک فایل و یا یک کلاس باشد. در برنامه‌های وب فرم این مدیریت کننده URL یک فایل فیزیکی است و در برنامه‌های MVC یک کلاس (کنترلر) و متد(اکشن) است. بطور مثال درخواست http://domain/students می‌تواند به آدرس http:domain/studentsinfo.aspx در یک برنامه وب فرم نگاشت شود و یا در یک برنامه MVC به کنترلر Student و اکشن Index .

نکته : مسیریابی مربوط به فریم ورک MVC نمی‌باشد ، از مسیر یابی هم در WebForm application و هم در MVC Application استفاده می‌شود.


مسیر (Route) :
Route، الگوی URL و اطلاعات مدیریت کننده‌ی URL را تعریف می‌کند. تمامی Route‌‌های تعریف شده‌ی در یک برنامه، در جدولی به نام RouteTable ذخیره می‌شوند. اطلاعات این جدول توسط موتور مسیریابی (Routing Engine) برای پیدا کردن مدیریت کننده‌های URL‌ها مورد استفاده قرار می‌گیرد.
تصویر زیر فرآیند مسیریابی را نشان می‌دهد:



پیکربندی مسیر(Route Configuration) :
در برنامه‌های MVC می‌بایست حداقل یک Route، پیکربندی و تعریف شده باشد. شما می‌توانید یک Route دلخواه را در کلاس RouteConfig که در پوشه App_Start پروژه قرار گرفته است، تعریف کنید. شکل زیر طریقه پیکربندی یک Route را در کلاس RouteConfig، نشان می‌دهد:
 



همانطور که در شکل بالا مشاهده می‌کنید برای پیکره بندی Route از متد الحاقی MapRoute از مجموعه RouteCollection استفاده شده است.

ساختار Route تعریف شده :
 • نام:  "Default"
 • الگوی درخواست: {Id}/{Action}/{Controller}.
 • پارامتر‌های پیش فرض:  این بخش در مواقعی که کنترلر، اکشن و یا مقدار Id، در آدرس ارسالی وجود نداشته باشد مورد استفاده قرار می‌گیرد.

نکته : RouteCollection خصوصیتی از کلاس RouteTable می‌باشد.

الگوی درخواست (URL Pattern)  :
الگوی URL باید بعد از نام دامنه قرار بگیرد. بطور مثال الگوی "{controller}/{action}/{id}" شبیه چنین درخواستی می‌باشد:  
 localhost:123/{controller}/{action}/{id}
هر چیزی بعد از نام دامنه ("/localhost:1234") بعنوان کنترلر در نظر گرفته خواهد شد. به همین ترتیب هر چیزی بعد از نام کنترلر، بعنوان اکشن و پس از آن مقدار پارامتر id .
 

اگر درخواست ارسالی بعد از نام دامنه، فاقد اطلاعات کنترلر و اکشن باشد، کنترلر و اکشن پیش فرض تعریف شده، جایگزین خواهند شد. بطور مثال درخواست localhost:1234 توسط کنترلر پیش فرض Home و متد Index مدیریت خواهد شد (با توجه به الگوی تعریف شده بالا):
جدول زیر وضعیت بررسی URL‌ها بر اساس Route  تعریف شده‌ی فوق را نشان می‌دهد:

Id
Action
Controller URL
 null  Index    HomeController     http://localhost/home 
 123   Index   
 HomeController   
 http://localhost/home/index/123 
 null  About  HomeController  
  http://localhost/home/about 
 null  contact  HomeController   
  http://localhost/home/contact 
 null  Index  StudentController   
 http://localhost/student 
 123  Edit  StudentController   
  http://localhost/student/edit/123 

مسیر‌های چندگانه (Multiple Route) :
شما براحتی و از طریق MapRoute می‌توانید چندین Route سفارشی را تعریف کنید. برای تعریف یک Route، حداقل دو پارامتر Name و الگوی URL الزامی است. بخش پارامتر‌های پیش فرض در تعریف یک Route، اختیاری است.
مثال: قصد داریم یک Route سفارشی را تعریف کنیم تا هر درخواستی، با الگوی domainName/students از طریق آن مدیریت شود:
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Student",
url: "students/{id}",
defaults: new { controller = "Student", action = "Index" }
);
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
با تعریف Route فوق، کلیه درخواست‌هایی که با domainName/students شروع می‌شوند، باید بوسیله‌ی StudentController مدیریت شوند. همانطور که مشاهده می‌کنید، در الگوی URL فوق هیچ {action} ای را معرفی نکرده‌ایم. به این خاطر که قصد داریم هر درخواستی که با student شروع می‌شود از متد Index نوشته شده در کنترلر student استفاده کند.
فریم ورک MVC، کلیه Route ‌های تعریف شده را به ترتیب مورد بررسی قرار خواهد داد. بدین معنی که با آمدن هر درخواست، اولین Route در جدول Route‌ها را بررسی کرده و اگر درخواست با Students/ شروع نشده بود، به سراغ مسیر تعریف شده بعدی می‌رود.

جدول زیر چگونگی نگاشت URL‌های مختلف را از طریق Route  تعریف شده Student، نشان می‌دهد:

 Id  Action  Controller URL
 123 Index
 StudentController   
  http://localhost/students/123 
 123 Index
 StudentController   
  http://localhost/students/index/123 
 123 Index
 StudentController   
  http://localhost/students/index/123 

محدود کردن مسیر‌ها (Route Constraints) :
به Route  تعریف شده زیر دقت کنید :
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Product",
url: "{Product}/{productid}",
defaults: new { controller = "Product", action = "Details" }
);
اکشن مورد استفاده نهایی هم به شکل زیر می‌باشد :
public class ProductController : Controller
{
  // GET: Product
  public ActionResult Details(int prodcutId)
  {
       return View();
  }
}
درخواست‌های ارسالی با فرمت زیر، بدون مشکل و توسط Route تعریف شده‌ی فوق مدیریت خواهند شد:
/Product/24
/Product/4
 اما ارسال درخواست‌هایی با فرمت زیر، سبب بروز خطا خواهد شد:
/Product/apple
/Product/kish

بررسی علت بروز خطا:

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

برای حل مشکل فوق باید بر روی الگوی تعریف شده، محدودیت ایجاد کنیم.
نحوه ایجاد محدودیت بر روی پارامتر id :
routes.MapRoute(
name: "Product",
url: "{Product}/{productid}",
defaults: new { controller = "Product", action = "Details" },
constraints: new { productid = @"\d+" }
);
در صورتیکه مقداری غیر عددی، به عنوان پارامتر id ارسال شود، درخواست توسط Route فوق پردازش نخواهد شد و سیستم مسیریابی مجددا به دنبال یک Route که شرایط درخواست را تامین کند، می‌گردد. در صورت پیدا نشدن یک Route برای پاسخ‌دهی به این درخواست، خطای "The resource could not be found" نمایش داده خواهد شد.

ثبت مسیر (Register Route) :

بعد از پیکربندی کلیه Route‌ها در کلاس RouteConfig، باید Route‌ها از طریق رویداد Application_Start موجود در فایل Global.asx ثبت گردند.
بعد از این مرحله کلیه Route‌های تعریف شده به RouteTable اضافه خواهند شد.
public class MvcApplication : System.Web.HttpApplication
{
  protected void Application_Start()
  {
  RouteConfig.RegisterRoutes(RouteTable.Routes);
  }
}
شکل زیر، فرآیند ثبت یک Route را نشان می‌دهد:

مطالب
طراحی یک گرید با Angular و ASP.NET Core - قسمت دوم - پیاده سازی سمت کلاینت
در قسمت قبل، کار پیاده سازی سمت سرور نمایش اطلاعات یک گرید، به پایان رسید. در این قسمت می‌خواهیم از سمت کلاینت، اطلاعات صفحه بندی و مرتب سازی را به سمت سرور ارسال کرده و همچنین نتیجه‌ی دریافتی از سرور را نمایش دهیم.



پیشنیازهای نمایش اطلاعات گرید به همراه صفحه بندی اطلاعات

در مطلب «Angular CLI - قسمت ششم - استفاده از کتابخانه‌های ثالث» نحوه‌ی نصب و معرفی کتابخانه‌ی ngx-bootstrap را بررسی کردیم. دقیقا همان مراحل، در اینجا نیز باید طی شوند و از این مجموعه تنها به کامپوننت Pagination آن نیاز داریم. همان قسمت ذیل گرید تصویر فوق که شماره صفحات را جهت انتخاب، نمایش داده‌است.
بنابراین ابتدا فرض بر این است که دو بسته‌ی بوت استرپ و ngx-bootstrap را نصب کرده‌اید:
> npm install bootstrap --save
> npm install ngx-bootstrap --save
در فایل angular-cli.json. شیوه‌نامه‌ی بوت استرپ را نیز افزوده‌اید:
  "apps": [
    {
      "styles": [
    "../node_modules/bootstrap/dist/css/bootstrap.min.css",
        "styles.css"
      ],
پس از آن باید به‌خاطر داشت که کامپوننت نمایش صفحه بندی این مجموعه PaginationModule نام دارد و باید در نزدیک‌ترین ماژول مورد نیاز، ثبت و معرفی شود:
import { PaginationModule } from "ngx-bootstrap";

@NgModule({
  imports: [
    PaginationModule.forRoot()
  ]
برای نمونه در این مثال، ماژولی به نام simple-grid.module.ts دربرگیرنده‌ی گرید مطلب جاری است و به صورت ذیل به برنامه اضافه شده‌است:
 >ng g m SimpleGrid -m app.module --routing
بنابراین تعریف PaginationModule باید به قسمت imports این ماژول اضافه شود و تعریف آن در app.module.ts تاثیری بر روی این قسمت نخواهد داشت.

کامپوننتی هم که مثال جاری را نمایش می‌دهد به صورت ذیل به ماژول SimpleGrid فوق اضافه شده‌است:
 >ng g c SimpleGrid/products-list


تهیه معادل‌های قراردادهای سمت سرور در سمت Angular

در قسمت قبل، تعدادی قرارداد مانند پارامترهای دریافتی از سمت کلاینت و ساختار اطلاعات ارسالی به سمت کلاینت را تعریف کردیم. اکنون جهت کار strongly typed با آن‌ها در سمت یک برنامه‌ی تایپ اسکریپتی Angular، کلاس‌های معادل آن‌ها را تهیه می‌کنیم.

ساختار شیء محصول دریافتی از سمت سرور
 >ng g cl SimpleGrid/app-product
با این محتوا
export class AppProduct {
  constructor(
    public productId: number,
    public productName: string,
    public price: number,
    public isAvailable: boolean
  ) {}
}
که در اینجا هر کدام از خواص ذکر شده، معادل camel case نمونه‌ی سمت سرور خود هستند (چون JSON.NET در ASP.NET Core، به صورت پیش فرض یک چنین خروجی را تولید می‌کند).

ساختار معادل پارامترهای صفحه بندی و مرتب سازی ارسالی به سمت سرور
 >ng g cl SimpleGrid/PagedQueryModel
با این محتوا
export class PagedQueryModel {
  constructor(
    public sortBy: string,
    public isAscending: boolean,
    public page: number,
    public pageSize: number
  ) {}
}
در اینجا همان ساختار IPagedQueryModel سمت سرور را مشاهده می‌کنید. از آن جهت مشخص سازی جزئیات صفحه بندی و نحوه‌ی مرتب سازی اطلاعات، استفاده می‌شود.

ساختار معادل اطلاعات صفحه بندی شده‌ی دریافتی از سمت سرور
 >ng g cl SimpleGrid/PagedQueryResult
با این محتوا
export class PagedQueryResult<T> {
  constructor(public totalItems: number, public items: T[]) {}
}
این ساختار جنریک نیز دقیقا معادل همان PagedQueryResult سمت سرور است و حاوی تعداد کل ردیف‌های یک کوئری و تنها قسمتی از اطلاعات صفحه بندی شده‌ی آن می‌باشد.

ساختار ستون‌های گرید نمایشی
 >ng g cl SimpleGrid/GridColumn
با این محتوا
export class GridColumn {
  constructor(
    public title: string,
    public propertyName: string,
    public isSortable: boolean
  ) {}
}
هر ستون نمایش داده شده، دارای یک برچسب، خاصیتی مشخص در سمت سرور و بیانگر قابلیت مرتب سازی آن می‌باشد. اگر isSortable به true تنظیم شود، با کلیک بر روی سرستون‌ها می‌توان اطلاعات را بر اساس آن ستون، مرتب سازی کرد.


تهیه سرویس ارسال اطلاعات صفحه بندی به سرور و دریافت اطلاعات از آن

پس از تدارک این مقدمات، اکنون کار تعریف سرویسی که این اطلاعات را به سمت سرور ارسال می‌کند و نتیجه را باز می‌گرداند، به صورت ذیل خواهد بود:
 >ng g s SimpleGrid/products-list -m simple-grid.module
این دستور سبب ایجاد کلاس ProductsListService شده و همچنین قسمت providers ماژول simple-grid را نیز بر این اساس به روز رسانی می‌کند.
پیش از تکمیل این سرویس، نیاز است متدی را جهت تبدیل یک شیء، به معادل کوئری استرینگ آن تهیه کنیم:
  toQueryString(obj: any): string {
    const parts = [];
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const value = obj[key];
        if (value !== null && value !== undefined) {
          parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
        }
      }
    }
    return parts.join("&");
  }
در قسمت قبل امضای متد GetPagedProducts دارای ویژگی HttpGet است. بنابراین، نیاز است اطلاعات را به صورت کوئری استرینگ از سمت کلاینت دریافت کند و متد toQueryString فوق به صورت خودکار بر روی تمام خواص یک شیء دلخواه حرکت کرده و آن‌ها را تبدیل به یک رشته‌ی حاوی کوئری استرینگ‌ها می‌کند.
[HttpGet("[action]")]
public PagedQueryResult<Product> GetPagedProducts(ProductQueryViewModel queryModel)
برای نمونه متد toQueryString فوق است که سبب ارسال یک چنین درخواستی به سمت سرور می‌شود:
 http://localhost:5000/api/Product/GetPagedProducts?sortBy=productId&isAscending=true&page=2&pageSize=7

پس از این تعریف، سرویس ProductsListService  به صورت ذیل تکمیل خواهد شد:
@Injectable()
export class ProductsListService {
  private baseUrl = "api/Product";

  constructor(private http: Http) {}

  getPagedProductsList(
    queryModel: PagedQueryModel
  ): Observable<PagedQueryResult<AppProduct>> {
    return this.http
      .get(`${this.baseUrl}/GetPagedProducts?${this.toQueryString(queryModel)}`)
      .map(res => {
        const result = res.json();
        return new PagedQueryResult<AppProduct>(
          result.totalItems,
          result.items
        );
      });
  }
در اینجا از متد toQueryString، جهت تکمیل متد get ارسالی به سمت سرور استفاده شده‌است تا پارامترها را به صورت کوئری استرینگ‌ها تبدیل کرده و ارسال کند.
سپس در متد map آن، res.json دقیقا همان ساختار PagedQueryResult سمت سرور را به همراه دارد. اینجا است که فرصت خواهیم داشت نمونه‌ی سمت کلاینت آن‌را که در ابتدای بحث تهیه کردیم، وهله سازی کرده و بازگشت دهیم (نگاشت فیلدهای دریافتی از سمت سرور به سمت کلاینت).


تکمیل کامپوننت نمایش گرید

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


  <table class="table table-striped table-hover table-bordered table-condensed">
    <thead>
      <tr>
        <th class="text-center" style="width:3%">#</th>
        <th *ngFor="let column of columns" class="text-center">
          <div *ngIf="column.isSortable" (click)="sortBy(column.propertyName)" style="cursor: pointer">
            {{ column.title }}
            <i *ngIf="queryModel.sortBy === column.propertyName" class="glyphicon"
              [class.glyphicon-sort-by-order]="queryModel.isAscending" [class.glyphicon-sort-by-order-alt]="!queryModel.isAscending"></i>
          </div>
          <div *ngIf="!column.isSortable" style="cursor: pointer">
            {{ column.title }}
          </div>
        </th>
      </tr>
    </thead>
در اینجا ابتدا بررسی می‌شود که آیا یک ستون قابلیت مرتب سازی را دارد، یا خیر؟ اگر اینطور است، در کنار آن یک گلیف آیکن مرتب سازی درج می‌شود. اگر خیر، صرفا متن عنوان آن نمایش داده خواهد شد. می‌شد تمام این موارد را به ازای هر ستون به صورت مجزایی ارائه داد، اما در این حالت به کدهای تکراری زیادی می‌رسیدیم. به همین جهت از یک حلقه بر روی تعریف ستون‌های این گرید استفاده شده‌است. آرایه‌ی این ستون‌ها نیز به صورت ذیل تعریف می‌شود:
export class ProductsListComponent implements OnInit {
  columns: GridColumn[] = [
    new GridColumn("Id", "productId", true),
    new GridColumn("Name", "productName", true),
    new GridColumn("Price", "price", true),
    new GridColumn("Available", "isAvailable", true)
  ];

همچنین در کدهای قالب این کامپوننت، مدیریت کلیک بر روی یک سر ستون را نیز مشاهده می‌کنید:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);

  sortBy(columnName) {
    if (this.queryModel.sortBy === columnName) {
      this.queryModel.isAscending = !this.queryModel.isAscending;
    } else {
      this.queryModel.sortBy = columnName;
      this.queryModel.isAscending = true;
    }
    this.getPagedProductsList();
  }
}
در این‌حالت اگر ستونی که بر روی آن کلیک شده، پیشتر مرتب سازی شده‌است، صرفا خاصیت صعودی بودن آن برعکس خواهد شد. در غیراینصورت، نام خاصیت درخواستی مرتب سازی و جهت آن نیز مشخص می‌شود. سپس مجددا این گرید توسط متد getPagedProductsList رندر خواهد شد.

کار رندر بدنه‌ی اصلی گرید توسط همین چند سطر در قالب آن مدیریت می‌شود:
    <tbody>
      <tr *ngFor="let item of queryResult.items; let i = index">
        <td class="text-center">{{ itemsPerPage * (currentPage - 1) + i + 1 }}</td>
        <td class="text-center">{{ item.productId }}</td>
        <td class="text-center">{{ item.productName }}</td>
        <td class="text-center">{{ item.price | number:'.0' }}</td>
        <td class="text-center">
          <input id="item-{{ item.productId }}" type="checkbox" [checked]="item.isAvailable"
            disabled="disabled" />
        </td>
      </tr>
    </tbody>
  </table>
اولین ستون آن، اندکی ابتکاری است. در اینجا شماره ردیف‌های خودکاری در هر صفحه درج خواهند شد. این شماره ردیف نیز جزو ستون‌های منبع داده‌ی فرضی برنامه نیست. به همین جهت برای درج آن، توسط let i = index در ngFor، به شماره ایندکس ردیف جاری دسترسی پیدا می‌کنیم. سپس توسط محاسباتی بر اساس تعداد ردیف‌های هر صفحه و شماره‌ی صفحه‌ی جاری، می‌توان شماره ردیف فعلی را محاسبه کرد.

در اینجا حلقه‌ای بر روی queryResult.items تشکیل شده‌است. این منبع داده به صورت ذیل در کامپوننت متناظر مقدار دهی می‌شود:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;
  isLoading = false;
  queryModel = new PagedQueryModel("productId", true, 1, this.itemsPerPage);
  queryResult = new PagedQueryResult<AppProduct>(0, []);

  constructor(private productsListService: ProductsListService) {}

  ngOnInit() {
    this.getPagedProductsList();
  }

  private getPagedProductsList() {
    this.isLoading = true;
    this.productsListService
      .getPagedProductsList(this.queryModel)
      .subscribe(result => {
        this.queryResult = result;
        this.isLoading = false;
      });
  }
}
ابتدا سرویس ProductsListService را که در ابتدای بحث تکمیل شد، به سازنده‌ی این کامپوننت تزریق می‌کنیم. به کمک آن می‌توان در متد getPagedProductsList، ابتدا queryModel جاری را که شامل اطلاعات مرتب سازی و صفحه بندی است، به سرور ارسال کرده و سپس نتیجه‌ی نهایی را به queryResult انتساب دهیم. به این ترتیب تعداد کل رکوردها و همچنین آیتم‌های صفحه‌ی جاری دریافت می‌شوند. اکنون حلقه‌ی ngFor نمایش بدنه‌ی گرید، کار تکمیل صفحه‌ی جاری را انجام خواهد داد.

قسمت آخر کار، افزودن کامپوننت نمایش شماره صفحات است:


  <div align="center">
    <pagination [maxSize]="8" [boundaryLinks]="true" [totalItems]="queryResult.totalItems"
      [rotate]="false" previousText="&lsaquo;" nextText="&rsaquo;" firstText="&laquo;"
      lastText="&raquo;" (numPages)="numberOfPages = $event" [(ngModel)]="currentPage"
      (pageChanged)="onPageChange($event)"></pagination>
  </div>
  <pre class="card card-block card-header">Page: {{currentPage}} / {{numberOfPages}}</pre>
در اینجا از کامپوننت pagination مجموعه‌ی ngx-bootstarp استفاده شده‌است و یک سری از خواص مستند شده‌ی آن‌، مقدار دهی شده‌اند؛ مانند متن‌های صفحه‌ی بعد و قبل و امثال آن. مدیریت کلیک بر روی شماره‌های آن، در کامپوننت جاری به صورت ذیل است:
export class ProductsListComponent implements OnInit {
  itemsPerPage = 7;
  currentPage: number;
  numberOfPages: number;

  onPageChange(event: any) {
    this.queryModel.page = event.page;
    this.getPagedProductsList();
  }
}
علت تعریف دو خاصیت اضافه‌ی currentPage و numberOfPages، استفاده‌ی از آن‌ها در قسمت ذیل این شماره‌ها (خارج از کامپوننت نمایش شماره صفحات) جهت نمایش page 1/x است.
هر زمانیکه کاربر بر روی شما‌ره‌ای کلیک می‌کند، رخ‌داد onPageChange فراخوانی شده و در این‌حالت تنها کافی است شماره صفحه‌ی درخواستی queryModel جاری را به روزرسانی کرده و سپس آن‌را در اختیار متد getPagedProductsList جهت دریافت اطلاعات این صفحه‌ی درخواستی قرار دهیم.


کدهای کامل این قسمت را از اینجا می‌توانید دریافت کنید.
اشتراک‌ها
شرکت در نظرسنجی امکانات بومی سازی تیم Angular
Hello! I work for the Angular core team on internationalization (i18n) and we are trying to get a better idea of the things that we need to improve/focus on in Angular regarding i18n. I created this survey: https://goo.gl/forms/0BIXXVP58RqbzzBM2 If you care about internationalization, please take 5mn to do it! Thanks!
شرکت در نظرسنجی امکانات بومی سازی تیم Angular
اشتراک‌ها
زمانه angular 2

I’ve been avoiding even thinking about the latest rev of Angular as every time I did look at it, I came away with a headache. It was weird and the docs were even weirder. I saw plenty of blog posts on the topic, but in general they dealt with one small slice of Angular 2 and were too confusing for me to grok. 

زمانه angular 2
اشتراک‌ها
همکاری تیم TypeScript و AngularJS

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

همکاری تیم TypeScript و AngularJS
بازخوردهای دوره
پیاده سازی دکمه «بیشتر» یا «اسکرول نامحدود» به کمک jQuery در ASP.NET MVC
من از Path.js استفاده کردم و مشکلی در پیاده سازی آن نداشتم حال نرم افزار باید به صورت چندزبانه استفاده بشه و مشکل اینجاست وقتی نوع زبان (fa | en) در اول Routing میاد وارد صفحه مورد نظر نمیشه !
1. نحوه پیاده سازی زبان در استفاده از Path.js به چه صورتی هست؟
2. در صورت نداشتن زبان نباید وارد این صفحه شود این بخش را چگونه مدیریت کنم؟
با تشکر
بازخوردهای دوره
بایدها و نبایدهای استفاده از IoC Containers
- همیشه بجای ()new MyClass می‌توان نوشت ()<>ObjectFactory.GetInstance<MyClass. در این حالت به صورت خودکار تا n سطح، تمام وابستگی‌های MyClass به صورت خودکار وهله سازی می‌شوند.
- همچنین بحث مفصلی در مورد مسیریابی و تزریق وابستگی‌ها در ASP.NET MVC در اینجا: «Improving ASP.NET MVC Routing Configuration »